Category Archives: Devastro2

D2 log 014 – Video player

The main menu of the game is going to have an animated background. Currently I’m thinking it could be the end of the original trailer put on loop:

As a temporary playback solution I used a series of JPEG files. Not very efficient. When looking for a decent video playback library, I found this one:

A brand new “single-header” library for decoding MPEG1 video, by Dominic Szablewski. Simple API, easy to integrate. It’s great!

In fact, I spent more time looking for my .blend source file than writing code to use the library. In Blender, I switched render output from image to video and set the codec to MPEG1. It all worked the first time I ran the game.

D2 log 012 – Blender pipeline

The new Blender 2.8 is great. Unfortunately it broke my batch render script.

Fixing the script and the rendering setup were quite a challenge due to the major UI changes in 2.8, but eventually I figured it out. Even learned a few new tricks along the way. How did I manage to write the original script without the built-in scripting console? So helpful!

My Blender export pipeline looks like this:

  1. master.blend file with fixed camera & lighting setup
  2. extra .blend file for each asset
  3. link assets into master
  4. for each asset, render object & shadow into separate PNG

First draft of the new script for 2.8:

import bpy
import os
import subprocess

# Requires Blender 2.8
#
# /Applications/blender.app/Contents/MacOS/Blender -b -P render28.py
#
# Notes:
# 
# Node setup: see master.blend -> Compositing
# Render menu -> Film -> transparent
# Shadow catcher plane -> Object menu -> Visibility -> Shadow catcher
# Adding more objects: File -> Link... -> "Collection" from ext. file -> Move to Assets collection in master

basepath = os.path.normpath(os.path.dirname(os.path.abspath(__file__)))
master_blend = "blender28-batch-render-test-master.blend"

src = os.path.join(basepath, master_blend)

bpy.ops.wm.open_mainfile(filepath=src)

bpy.context.scene.render.resolution_percentage = 50
bpy.context.scene.render.resolution_x = 1280
bpy.context.scene.render.resolution_y = 960
bpy.context.scene.cycles.samples = 20

assets_name = "Assets" # name of collection in master.blend
assets_collection = None

for collection in bpy.data.collections:
	if collection.name == assets_name:
		assets_collection = collection
		break

if assets_collection is None:
	print( "Error: %s collection not found in master" % assets_name )
	quit()

# Save original filenames for output nodes
output_nodes = []
for node in bpy.context.scene.node_tree.nodes:
	if ( node.type == "OUTPUT_FILE" ):
		output_nodes.append( ( node, node.file_slots[0].path ) )

for target in assets_collection.objects:
	# Show only the current object
	for obj in assets_collection.objects:
		obj.hide_render = obj.name != target.name

	# Set output filename
	for node, path in output_nodes:
		node.file_slots[0].path = target.name + "_" + path

	bpy.ops.render.render(animation=False)

Master & test .blend files here: blender28-batch-render-test.zip

Example output:

Object
Object

Shadow
Shadow

Composite
Composite

Additionally, I created a simple pipeline for rendering weapon silhouettes for the HUD. With a single click, it pulls in each weapon model, renders it from a side-view, then uses just the alpha to write a single-color transparent PNG.

And similar setup for pickup icons, with a subtle “glow” pass.

D2 log 010 – IMGUI

After experimenting with Cocoa-based editors, I’ve gone back to IMGUI. The panels I had built with Cocoa were beautiful (and I love native Mac apps), but it was too much work.

Building functional interfaces is a lot faster with IMGUI.

Luckily enough I get to use IMGUI at my day job now and I’ve learned a lot since my last attempt to integrate it into the game.

Turns out the problem of IMGUI and my own UI elements fighting for input events can be solved quite easily. I just need to process things in the right order and honor the “IMGUI wants focus” flags.

D2 log 009 – Triggers

Added basic support for editable “triggers”. As I mentioned in the previous entry, the original Devastro used this approach for setting up win/lose conditions for each level.

Similar to Unreal Engine’s Blueprints – but less sophisticated, of course. Great for things like: “to win this level, the player needs to kill all enemies, destroy all saucers and find the red key”. I can also easily setup areas that will spawn more enemies when the player enters, events that happen when an item is picked up etc. all without writing any extra code.

The difficult part was to maintain inter-entity links – in the game, the editor and also on disk. The new entity system helped a lot – when saving a level to disk, I store the “index” part of the Entity ID and when loading, fill in the correct “generation” after all entities are loaded.

Triggers will help me add a lot of variety to the game using a limited set of tools. Can’t wait to explore all the possibilities.

D2 log 008 – More entity groundwork

After redoing the entity list I still wanted to improve handling game objects more.

Turned cameras into regular game Entities. They now use the safe handle-based referencing system to bind to other entities that they should “follow”. Also I can setup cameras easily in the editor without extra effort.

HUD overlays are now entities too. They link to the player via an Entity ID, get ammo & health info easily. No explicit wiring in main game code. Player dies – no problem.

The amount of code I was able to remove from the main game loop was quite substantial. I guess I should try to do more things like that. The original Devastro had a system of triggers also implemented as game entities. I used them for setting up conditions for victory – for example, there was a level where the player had to protect a herd of sheep.

This was setup completely in the editor by wiring the triggers for “alive” for each sheep into an “AND” node and wiring that into the “WIN” node. Pretty neat, now that I remember it… maybe I’ll use that approach again.

 

D2 log 007 – Cocoa

Working on the level editor I realized I’d really like my window size to match the phone format which means there won’t be enough space to fit the editing tools, such as entity list & properties, tile picker etc.

I could open a second window to render the editor stuff using the same renderer as the game and IMGUI is great but I already have some “imgui-style” widgets of my own and feel like mixing them together could lead to some hard to fix problems.

So I decided to use Cocoa, the native macOS UI framework. I’ll make a few floating panels independent of the main window. Clean separation, less trouble.

Starting with a simple entity list:

Grid view for selecting map tiles:

And a very early version of an entity property panel:

(Fields are generated dynamically for each entity type based on the property metadata).

Still a lot of work ahead to put it all together and wire it into the editor system, but already looking much better than my previous attempts.

D2 log 006 – Rain

Let it rain! There was a rain effect in the first game, so why not bring it over? Good opportunity to see how easy it is to add a new entity type… turns out it it’s really smooth! Only one file to edit and it was up and running, including a custom “numParticles” property in the editor & XML serialization process.

I’m pretty sure I’ll revisit this later and improve it with sound, lighting & thunder effects and little splashes on the ground. For now though, it sets the mood of the level quite well already.

D2 log 005 – Xcode templates

While adding some new code to the project I got annoyed by the default C++ file templates that Xcode used. The header it creates contains old-style #ifdef guard and has a .hpp extension. Every time I use the template I obsessively delete all that stuff and start over with a simple #pragma once.

Why not make it the default? Turns out it’s not hard to create custom templates. They go into ~/Library/Developer/Xcode/Templates/File Templates/Source/ and have this kind of structure:

I’ve made the __FILEBASENAME__ files almost empty, because that’s what I want: