Devlog 2024-02-11

Had a pretty productive week! I breezed through making a level and then… didn’t quite breeze through drawing a large piece of art but it went better than I expected!

Checklist

  • Evil Island changes:
    • started and finished the first level of the game!
    • added a smart button display for text boxes
    • drew a title screen

The First Level

The first level of the game is done (minus some inevitable tweaking of course)! You start out on a ship, with some (optional) tutorial info terminals. I’ll need to see how well it explains things for actual players, but I’m pretty happy with it so far.

I broke a rule I set for myself going into this level, which was that I shouldn’t need to draw any new art assets. I knew I was going to have to bend that rule anyway because this is the only ship in the game, but I figured maybe I could get away without too much new art. Unfortunately things got away from me and I ended up adding… nearly 100 tiles to my set. Now! Many of those were just larger multi-tile pieces, or re-colours of something else. Even so, it was a lot more than I expected to have to draw. I do like how it all turned out, though, so hopefully it’s worthwhile. I might go back and add some of the new tiles somewhere else in the game… we’ll see!

On the programming side, though, the only novel things I needed for the level was to add a single boolean flag on my existing info terminals – I can now mark them so that they will trigger simply by touching them, rather than by hitting the “activate” button – some fancy text rewriting which I’ll take more about later. I use the activate-on-touch flag in exactly one place: the first info terminal you encounter will auto-trigger, because it tells you how to read info terminals.

Is This “Environmental Storytelling”?

I wanted to add some info terminals to the first level to explain the game. It’s not a complicated game, but it always rubs me the wrong way when a game just assumes you know how to play it already. I went back and forth on these a bit, though; I’m fine with breaking the fourth wall in this game, so I considered just having the in-world terminals literally tell you what to do without any “fluff”. I ended up writing them all, instead, as if they were part of an internal corporate wiki, which is mentioned later in the game as.

I’m not completely happy with it because (as you can see in the one about firearms, which is the second textbox for that terminal) it means there’s a lot more to read in there, and it’s not necessarily obvious to the player what’s “important”. I may leave it as-is, though – I’m nothing if not long-winded.

Rewriting Inputs in Text

As I talked about a few weeks ago I support remappable keyboard AND joypad controls for the game. This presented a bit of a problem when I wanted to tell the player what to press to do something. I decided to just do everyone’s favourite bit of hackery: string replacement.

Godot has a RichTextLabel type, which supports a subset of BBCode. To match that stylistically, I decided I’d just make a fake tag. In the actual text of the terminals, I’ve put a term like [button=play_drop_down] which matches the action in my InputMap and control remapping. Then, in my textbox display code, I replace that with the appropriate text or image.

The first step is to keep track of what the player is using right now. I want to support the player switching back and forth seamlessly (nothing worse than accidentally hitting a keyboard button at the wrong time and never getting controller prompts ever again…) I already have a class dealing with some other input stuff called InputStack, and that’s available easily wherever I want it, so I figured I’d just add it there with an _input callback.

It’s really straightforward, though: when an input event comes in, just check if it’s a keyboard event (or mouse event, though really that’s probably not necessary with this game) then save

class_name InputStack extends Object

enum ControlStyle { KEYBOARD, JOYPAD }
var last_control_style:ControlStyle = ControlStyle.KEYBOARD

func _input(evt):
	if evt is InputEventKey or evt is InputEventMouseButton or evt is InputEventMouseMotion:
		last_control_style = ControlStyle.KEYBOARD
	elif evt is InputEventJoypadButton or evt is InputEventJoypadMotion:
		last_control_style = ControlStyle.JOYPAD

I also pulled out the code I wrote for display of controller buttons when I wrote the remapping menu into its own class, ControllerButtons, which mostly just holds a static button, get_img_tag(). It simply returns an [img] tag showing a region in the controller image atlas I drew.

class_name ControllerButtons extends Object

static var button_atlas_lookup = [
	Vector2i(11,0), Vector2i(0,0), Vector2i(22,0), Vector2i(33,0),		# A, B, X, Y
	Vector2i(99,0), null, Vector2i(88,0), null,							# back, guide, start, left stick
	null, Vector2i(110,0), Vector2i(133,0), Vector2i(77,0),				# right stick, left shoulder, right shoulder, dpad up
	Vector2i(55,0), Vector2i(66,0), Vector2i(44,0)						# dpad down, dpad left, dpad right
]

static func get_img_tag(button_index) -> String:
	if button_index >= 0 and button_index < button_atlas_lookup.size() and button_atlas_lookup[button_index] is Vector2i:
		var v = button_atlas_lookup[button_index]
		return "[img region=%s,%s,11,11]res://asset/ui/controller_buttons.png[/img]" % [v.x, v.y]
	else:
		return "B" + button_index

Then, the actual replacement. Info terminals can have multiple pages, so I just process all of them when a terminal is activated. This means that if you switch inputs while reading the terminal, it won’t switch immediately, but I figured that was a reasonable tradeoff for simplicity’s sake.

func _proc_texts(texts:Array[String]) -> Array[String]:
    var out:Array[String] = []
	var input_config = G.config.val("input", G.game.default_input_map)

	for s in texts:
		for action_name in input_config.keys():
			if G.input_stack.last_control_style == InputStack.ControlStyle.JOYPAD:
				for b in input_config[action_name]:
					if b.begins_with("j"):
						var button_index = int(b.substr(1))
						s = s.replace("[button=%s]" % [action_name], ControllerButtons.get_img_tag(button_index))
			elif G.input_stack.last_control_style == InputStack.ControlStyle.KEYBOARD:
				for b in input_config[action_name]:
					if b.begins_with("k"):
						var keycode = int(b.substr(1))
						s = s.replace("[button=%s]" % [action_name], OS.get_keycode_string(keycode))
		out.push_back(s)
	return out

And so now, I can just drop a [button] tag into my info terminals and it works! I was also interested to do this work since this has been on my todo list for Black Mountain for quite some time, and there’s no reason I shouldn’t be able to port this over easily.

The Title Screen

With the level finished and working, it was time to tackle something I’ve been putting off… the title screen! I’ve been happy with how the art has developed for this game, and I really enjoyed the drawing I did for the shopkeeper, so I wanted to see if I could manage a full-sized title screen. It took a while, but I’m pretty happy with the results!

There’s a bit more to the “boot sequence” of the game, with some scrolling across art (it’s actually two screens high, but the top half is just some clouds in the sky) and some fading, but I’m still tweaking it. If the title screen reminds you of another classic game, it is intentional. :)

That’s it for this week! Next on the list is to clean up the start and ending sequences for the game – which will probably mean some more art, I’m not sure exactly! Have a good one.