Workpad

    Started working on world 2, and one of the main mechanics of this world: quicksand. It wonā€™t kill the player directly, but it will make it difficult for them to manoeuvre, and getting too low could cause death. Might be one of the more annoying mechanics in the game, but thatā€™s kind of the point.

    The results of my first play-test are in. And overall, they were pretty positive: movement was good, hit-boxes were fair, and it was described as ā€œquite fun,ā€ which was better than I was hoping for.

    One thing Iā€™ll need to look out for is telegraphing secrets. The number of secrets is indicated at the end of the level, and based on the play-testerā€™s feedback, they seemed to have spent a lot of time running against walls trying to find them. There is one secret in the level 1-1 that I thought was telegraphed well, and I can confirm that the player found them all. But I will concede the others required the player to make a leap of faith, and fall into an area that the player will usually want to avoid, which is pretty unfair. So Iā€™ll need to fix that.

    A bit more on the Godot game this morning, this time working on background tiles artwork. Made some grey masonry tiles for the end castle sequences. Also tried some background rocks for underground areas. Iā€™m pretty rubbish at anything organic, but they didnā€™t turn out too bad.

    Auto-generated description: Two rectangular pixel art frames with stone textures, one in brown and the other in gray, are displayed with matching filled versions inside them.
    Right side has the background tiles surrounded with their complementary foreground tiles on the left.

    Added a few final things to my Godot game, such as a really boring title and end-title screen, before preparing a release for play testers (or play tester, Iā€™ve got exactly one lined up). I think weā€™re ready.

    Auto-generated description: A game welcome screen introduces Princess Real-estate, instructing players to collect coins and avoid hazards to reach a castle.

    A bit more on Godot this evening, mainly working on pausing the game, and the end-of-level sequence. Have got something pretty close to what I was looking for: a very Mario-esc sequence where the player enters a castle, it start auto-walking the character, and the level stats show up and ā€œspinā€ for a bit. Not too bad, although I may need to adjust the timing and camera a little to keep the stats from being unreadable.

    A bit more Godot work this evening. I wanted to add a foreground layer of tiles that obscured in the player. This is for making false walls to hide secrets around the level. It took me a while to work out how to turn off collision of this foreground layer: there wasnā€™t really any way to do so within the designer.

    Fortunately, this Github comment showing how to do so using a script worked like a charm:

    extends TileMap
    
    const foreground_layer = 1
    
    func _use_tile_data_runtime_update(layer: int, coords: Vector2i) -> bool:
    	return layer == foreground_layer
    	
    func _tile_data_runtime_update(layer: int, coords: Vector2i, tile_data: TileData) -> void:
    	tile_data.set_collision_polygons_count(0, 0)
    

    Only limitation seems to be that it will disable collision for the whole layer, but thatā€™s perfectly fine with me.

    Spent more time on my Godot platformer yesterday, mainly rebuilding the first level from scratch. The previous version was rush and was just not fun (it didnā€™t look good either, but I didnā€™t dress it up yet). This new one is much nicer, and allowed me to use a few new mechanics Iā€™ve built.

    I still need to build out the level ending sequence. Iā€™ve got less than the basics at the moment: a drawbridge descends and thatā€™s pretty much it. I need to add the conditions for when the bridge descends (specifically, a minimum number of coins), stopping the player movement for a brief scripted sequence, then transition to the next level. I think I know how I want to do this, itā€™s just a matter of implementing it. Once level 1 is dressed up and working, I think thatā€™ll be the next thing I do.

    Iā€™m enjoying working on this project so far, although part of me is a little afraid that Iā€™m spending time on something that just isnā€™t good, like thereā€™s some sunk cost with the time Iā€™m spending on this thatā€™s better put to use on something else. Of course, when I give in to these feelings, I usually just spend the time watching TV. So which activity is the real waste of time? Is producing something that may be crap (or worse, just boring) better than not producing anything at all?

    Anyway, not should how this became a feeling post. This game might be rubbish, but I still enjoy working on it: Godot is actually quite fun to use. Surely that is reason enough to continue.

    Was looking at how I could add hazards to my Godot project, such as spikes. My first idea was to find a way to detect collisions with tiles in a TileMap in Godot. But there was no real obvious way to do so, suggesting to me that this was not the way I should be going about this. Many suggested simply using an Area2D node to detect when a play touches a hazard.

    I was hesitant to copy and paste the scene node I had which handled the collision signal and kill the player ā€” the so-called ā€œkill zoneā€ scene ā€”but today I learnt that itā€™s possible to add multiple CollisionShape2D nodes to an Area2D node. This meant I needed only a single ā€œkill zoneā€ scene node, and just draw out the kill zones over the spikes as children. The TileMap simply provides the graphics.

    Auto-generated description: A game development interface displaying level design with a grid layout, tiles, and collision shapes is shown.

    This discovery may seem a little trivial, but Iā€™d prefer to duplicate as few nodes as a can, just so Iā€™ve got less to touch when I want to change something.

    Tried opening my Godot project this morning and was greeted with the following error:

    Auto-generated description: A computer screen displays a development environment with an error message pop-up about an invalid or missing scene file.
    scene/resources/resource_format_text.cpp:284 - res://scenes/falling_platform.tscn:14 - Parse Error: 
    Failed loading resource: res://scenes/falling_platform.tscn. Make sure resources have been imported by opening the project in the editor at least once.
    Failed to instantiate scene state of "res://scenes/falling_platform.tscn", node count is 0. Make sure the PackedScene resource is valid.
    Failed to load scene dependency: "res://scenes/falling_platform.tscn". Make sure the required scene is valid.
    

    Traced it back to the technique I was using to respawn the falling platform. Looks like Godot didnā€™t like the two preloads I included:

    # file: scripts/falling_platform.gd
    
    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    

    This resulted in a parse error and Godot thinking the level scene was corrupted. In retrospect, this kinda makes sense. What I doing was technically a circular dependency, where the scene and script was trying to preload itself. I was hoping Godot was smart enough to recognise this, but I guess not.

    So I had to change the respawn code. I modified it to make use the duplicate method. Hereā€™s the revised version:

    func respawn():	
        var dup = self.duplicate()
        dup.global_position = init_global_position	
    
        # Duplicate also copies the velocity so zero it out here
        dup.velocity = Vector2(0, 0)
        get_tree().current_scene.add_child(dup)
    

    After some limited testing, this seems to work. One good thing about this approach is that it looks like duplicate copies the script, so I no longer need to do anything special here.

    So I guess the lesson here is donā€™t try to preload the script within itself.

    Adventures In Godot: Respawning A Falling Platform

    My taste of going through a Godot tutorial last week has got me wanting more, so Iā€™ve set about building a game with it. Thanks to my limited art skills, Iā€™m using the same asset pack that was used in the video, although I am planning to add a bit of my own here and there.

    But itā€™s the new mechanics I enjoy working on, such as adding falling platforms. If youā€™ve played any platformer, you know what these look like: platforms that are suspended in air, until the player lands on them, at which point gravity takes a hold and they start falling, usually into a pit killing the player in the process:

    The platforms are built as a CharacterBody2D with an Area2D that will detect when a player enters the collision shape. When they do, a script will run which will have the platform ā€œslippingā€ for a second, before the gravity is turned on and the platform falls under itā€™s own weight. The whole thing is bundled as a reusable scene which I could drag into the level Iā€™m working on.

    Auto-generated description: A game development interface with a sprite and code editor is shown, from a software environment like Godot.

    I got the basics of this working reasonably quickly, yet today, I had a devil of a time going beyond that. The issue was that I wanted the platform to respawn after it fell off the map, so that the player wouldnā€™t get soft-locked at at an area where the platform was needed to escape. After a false-start trying to reposition the platform at itā€™s starting point after it fell, I figured it was just easier to respawn the platform when the old one was removed. To do this I had to solve two problems:

    1. How do I get the platform starting point?
    2. How can I actually respawn the platform?

    Getting The Starting Point

    A Node2D object has the property global_position, which returns the position of the object based on the world coordinates. However, it seems like this position is not correct when the _init function of the attached script is called. I suspect this is because this function is called before the platform is added to the scene tree, when the final world coordinates are known.

    Fortunately, there exists the _ready notification, which is invoked when the node is added to the scene tree. After some experimentation, I managed to confirm that global_position properly was correct. So tracking the starting point is a simple as storing that value in a variable:

    var init_global_position = null
    
    func _ready():
    	init_global_position = global_position
    

    Another option is to use the _enter_tree() notification. From the documentation, it looks like either would probably work here, with the only difference being the order in which this notification is invoked on parents and children (_enter_tree is called by the parent first, whereas _ready is called by the children first).

    Respawning The Platform

    The next trick was finding out how to respawn the platform. The usual technique for doing so, based on the results of my web searching, is to load the platform scene, instantiate a new instance of it, and added it to the scene tree.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        add_child(dup)
    

    Many of the examples Iā€™ve seen online added the new scene node as a child of the current node. This wouldnā€™t work for me as I wanted to free the current node at the same time, and doing so would free the newly instantiated child. The fix for this was easy enough: I just added the new node as a child of the current scene.

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    I still had to reposition the new node to the spawn point. Fortunately the global_position property is also settable, so it was simply a matter of setting that property before adding it to the tree (this is so that itā€™s correct when the newly instantiated node receives the _ready notification).

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    This spawned the platform at the desired positioned, but there was a huge problem: when the player jumped on the newly spawn platform, it wouldnā€™t fall. The Area2D connection was not invoking the script to turn on the gravity:

    It took me a while to figured out what was going on, but I came to the conclusion that the packed scene was loading properly, but without the script attached. Turns out a Script is a resource separate from the scene, and can be loaded and attached to an object via the set_script method:

    @onready var FallingPlatform = preload("res://scenes/falling_platform.tscn")
    @onready var FallingPlatformScript = preload("res://scripts/falling_platform.gd")
    
    func respawn():	
        var dup = FallingPlatform.instantiate()
        dup.set_script(FallingPlatformScript)
    
        dup.global_position = init_global_position
        get_tree().current_scene.add_child(dup)
        queue_free()
    

    Finally, after figuring all this out, I was able to spawn a new falling platform, have it positioned at the starting position of the old platform, and react to the player standing on it.

    The time it took to work this out is actually a little surprising. I was expecting others to run into the same problem I was facing, where they were trying to instantiate a scene only to have the scripts not do anything. Yet it took me 45 minutes of web searching through Stack Overflow and forum posts that didnā€™t solve my problem. It was only after a bit of experimentation and print-debugging on my own that I realised that I actually had to attached the script after instantiating the node.

    To be fair, I will attribute some of this to not understanding the problem at first: I actually thought the Area2D wasnā€™t actually being instantiated at all. Yet not one of the Stack Overflow answers or forum post floated the possibility that the script wasnā€™t being loaded alongside the scene. This does suggest to me that my approach may not be optimal. There does exist a ā€œLocal to Sceneā€ switch in the script inspector that could help, although turning it on doesnā€™t seem to do much. But surely there must be some way to instantiate the script alongside the scene.

    Anyway, thatā€™s for later. For now, Iā€™m happy that Iā€™ve got something that works.

    Prototyped a game I had in mind, sort of a 2D Sokoban-like thing, where you control a robot with a retractable pushing arm that is to push gems to a ā€œreceiverā€ tile. Not entirely sure if itā€™s fun enough to actually build.

    Used PixiJS to build it. Not a bad framework.

    Making A Small Two-Letter Country Code Lookup Page

    A small evening project where I made a simple site designed for Vivalidiā€™s sidebar to quickly lookup two-letter country codes defined in ISO-3166-1 alpha 2.

    Dusted off Podcast Favourites (last commit 25 April 2022) and fixed a longstanding issue of thumbnails being lost when theyā€™re changed in the feed. Editing the feed properties will now force a refresh of the thumbnail URLs. Didnā€™t need to change anything else, which was a nice change.

    Auto-generated description: A podcast favourites list showing episodes with titles and descriptions from the ATP - Members Feed.

    UCL: Some Updates

    Made a few minor changes to UCL. Well, actually, I made one large change. Iā€™ve renamed the foreach builtin to for.

    I was originally planning to have a for loop that worked much like other languages: you have a variable, a start value, and an end value, and youā€™d just iterate over the loop until you reach the end. I donā€™t know how this wouldā€™ve looked, but I imagined something like this:

    for x 0 10 {
        echo $x
    }
    # numbers 0..9 would be printed.
    

    But this became redundant after adding the seq builtin:

    foreach (seq 10) { |x|
        echo $x
    }
    

    This was in addition to all the other useful things you could do with the foreach loop1, such as loop over lists and hashes, and consume values from iterators. Itā€™s already a pretty versatile loop. So I elected to go the Python way and just made it so that the for loop is the loop to use to iterate over collections.

    This left an opening for a loop that dealt with guards, so I also added the while loop. Again, much like most languages, this loop would iterate over a block until the guard becomes false:

    set x 0
    while (lt $x 5) {
        echo $x
        set x (add $x 1)
    }
    echo "done"
    

    Unlike the for loop, this is unusable in a pipeline (well, unless itā€™s the first component). I was considering having the loop return the result of the guard when it terminates, but I realised that would be either false, nil, or anything else that was ā€œfalsy.ā€ So I just have the loop return nil. That said, you can break from this loop, and if the break call had a value, that would be used as the result of the loop:

    set x 0
    while (lt $x 5) {
        set x (add $x 1)
        if (ge $x 3) {
            break "Ahh"
        }
    } | echo " was the break"
    

    The guard is optional, and if left out, the while loop will iterate for ever.

    The Set! Builtin

    Many of these changes come from using of UCL for my job, and one thing I found myself doing recently is writing a bunch of migration scripts. This needed to get data from a database, which may or may not be present. If itā€™s not, I want the script to fail immediately so I can check my assumptions. This usually results in constructs like the following:

    set planID (ls-plans | first { |p| eq $p "Plan Name" } | index ID)
    if (not $planID) {
        error "cannot find plan"
    }
    

    And yeah, adding the if block is fine ā€” I do it all the time when writing Go ā€” but it would be nice to assert this when youā€™re trying to set the variable, for no reason other than the fact that youā€™re thinking about nullability while writing the expression to fetch the data.

    So one other change I made was add the set! builtin. This will basically set the variable only if the expression is not nil. Otherwise, it will raise an error.

    set! planID (ls-plans | first { |p| eq $p "Missing Plan" } | index ID)
    # refusing to set! `planID` to nil value
    

    This does mean that ! and ? are now valid characters to appear in identifiers, just like Ruby. I havenā€™t decided whether I want to start following the Ruby convention of question marks indicating a predicate or bangs indicating a mutation. Not sure thatā€™s going to work out now, given that the bang is being used here to assert non-nullability. In either case, could be useful in the future.


    1. And the seq builtin ā†©ļøŽ

    Idea for UCL: Methods

    Iā€™m toying with the idea of adding methods to UCL. This will be similar to the methods that exist in Lua, in that theyā€™re essentially functions that pass in the receiver as the first argument, although methods would only be definable by the native layer for the first version.

    Much like Lua though, methods would be invokable using the : ā€œpairā€ operator.

    strs:to-upper "Hello"
    --> HELLO
    

    The idea is to make some of these methods on the types themselves, allowing their use on literals and the result of pipelines, as well as variables:

    set h "Hello"
    $hello:to-upper
    --> HELLO
    
    "Hello":to-upper
    --> HELLO
    
    (cat "Hello " "world"):to-upper
    --> HELLO WORLD
    

    The use of methods would be purely conveience. One could conceive of a type, like a CSV table, where thereā€™s a need to perform a series of operations over it.

    The potential downside of using : is that it may make defining dictionaries more ambiguous. When the parser sees something that could either be a list or a dictionary, it does a scan to search for any pairs that may exist. If so, it treats the literal as a dictionary. But would this work with using : as the method separator?

    ["Hello":to-upper]
    --> Is this a dictionary {"Hello":"to-upper"}, or the list ["HELLO"]?
    

    So that would require something. Might be that I need to require method invocations to occur within parenthesis for list literals.

    Ambiguities like this aside, Iā€™m planning to keep it simple for now. Methods will not be user definable within UCL out of the gate, but would be effectively another interface available to native types. For the builtin ones that exist now, itā€™ll most likely be little more than syntactic sugar over the standard library functions:

    $hello:to-upper
    
    # equivalent to
    
    strs:to-upper $hello
    

    Speaking of the standard library, the use of : as a module-function separator may need some changes. A the moment itā€™s a bit of a hack: when a module is loaded, any procs defined within that module is stored in the environment with the operator: strs:to-upper for example. The parser is sophisticated enough to recognised : as a token, but when it parses two or more identifiers separated with :, it just joins them up into a single identifier.

    Whatā€™s needed is something else, maybe using something based on methods. The current idea is to define modules as being some special form of object, where the ā€œmethodsā€ are simply the names of the exported symbol.

    I was curious to know whether the language was capable of doing something similar now. Itā€™s conceivable that a similar concept could be drafted with procs returning dictionaries of procs, effectively acting as a namespace for a collection of functions. So a bit of time in the playground resulted in this experiment:

    proc fns {
      return [
        upper: (proc { |x| strs:to-upper $x })
        lower: (proc { |x| strs:to-lower $x })
      ]
    }
    
    (fns).upper "Hello"
    --> HELLO
    
    (fns).lower "Hello"
    --> hello
    

    A good first start. This is theoretically possible in the language that exists at the moment. Itā€™s not perfect thought. For one thing, the call to fns needs to be enclosed in parenthesis in order to invoke it. Leaving them out results in an error:

    fns.upper "Hello"
    --> error: command is not invokable
    

    The same is true when using variables instead of procs. I tried this experiment again using variables and it came to the same limitations:

    set fns [
      upper: (proc { |x| strs:to-upper $x })
      lower: (proc { |x| strs:to-lower $x })
    ]
    
    ($fns).upper "Hello"
    --> HELLO
    
    $fns.upper "Hello"
    --> error: command is not invokable
    

    Obviously the parser needs to be changed to add additional support for the : operator, but I also need to fix how strong . binds to values too. But I think this may have legs.

    UCL: Iterators

    Still working on UCL in my spare time, mainly filling out the standard library a little, like adding utility functions for lists and CSV files. Largest change made recently was the adding iterators to the mix of core types. These worked a lot like the streams of old, where you had a potentially unbounded source of values that could only be consumed one at a time. The difference with streams is that there is not magic to this: iterators work like any other type, so they could be stored in variables, passed around methods, etc (streams could only be consumed via pipes).

    I augmented the existing high-level functions like map and filter to consume and produce iterators, but it was fun discovering other functions which also became useful. For example, there exists a head function which returned the first value of a list. But I discovered that the semantics also worked as a way to consume the next element from an iterator. So thatā€™s what this function now does. This, mixed with the fact that iterators are truthy if theyā€™re got at least one pending value, means that some of the builtins could now be implemented in UCL itself. Like the example below, which could potentially be used to reimplement itrs:to-list (this is a contrived example, as foreach would probably work better here).

    proc to-list { |itr lst|
       if $itr {
          lists:add $lst (head $itr)
          return (to-list $itr $lst)
       }
       return $lst
    }
    
    to-list (itrs:from [1 2 3 4 5]) []
    

    But the biggest advantage that comes from iterators is querying large data-stores with millions of rows. Being able to write a UCL script which sets up a pipeline of maps and filters and just let it churn through all the data in itā€™s own time is the dream.

    list-customers | filter { |x| $x.HasPlan } | map { |x| $x.PlanID } | foreach echo
    

    Iā€™ve got a need for this in the internal backend tool that spurred the development of UCL, and Iā€™m looking forward to using iterators to help here.

    This weekā€™s distraction: building a Wordle clone. No particular reason for doing this other than I felt like building one, although I did miss the small time waster of the original Wordle, and watching a game show with my parents that had a similar concept just made those feelings stronger. Main difference between this and Wordle classic: board randomly selects between 4-letter, 5-letter, and 6-letter words; no daily limit or social-media sharing when you guessed the word correctly; and the biggest one: UK English spelling.

    Auto-generated description: A word puzzle game interface shows a grid with the words HOUSE, ALTAR, and POINT, with colour-coded tiles indicating correct and incorrect letter guesses.

    Some remarks on how this was built: I used 11ty to build the static site. It originally started as just a HTML page with some JavaScript, but I wanted to leave the option open for bundling and minifying the JS with Stimulus. The dictionary I got from Hunspell, which is apparently the spell checker Apple has based their work on. There is a little bit of Go to filter and sort the dictionary of words. The words are in sorted order for the binary search algorithm to check if a word exists or not. The puzzle order is predetermined and was done by ā€œshufflingā€ the indices in a separate array. Base styles are, of course, from simple.css.

    If youā€™re interested in checking it out, you can find it here. Just be aware that it may not be as polished as much of the other stuff you find out there. Turns out that I can tolerate a fair few shortcomings in things that I build for my own amusement.

    Learnt a very import thing about Stimulus outlets this evening: the outlet name must match the controller name of the outlet target. If this is not the case, the outlet will not bind and youā€™d be beside yourself struggling to find out why the outlet target cannot be found.

    From the docs:

    The outlet identifier in the host controller must be the same as the target controllerā€™s identifier.

    Took me 30 minutes and stepping through with code with the debugger to find this out.

    Started filling out the UCL website, mainly by documenting the core modules. It might be a little unnecessary to have a full website for this, given that the only person whoā€™ll get any use from it right now will be myself. But who knows how useful it could be in the future? If nothing else, itā€™s a showcase on what Iā€™ve been working on for this project.

    Iā€™ve been using UCL a lot recently, which is driving additional development on it. Spent a fair bit of time this evening fixing bugs and adding small features like string interpolation. Fix a number of grammar bugs too, that only popped up when I started writing multi-line scripts with it.

Older Posts ā†’
Lightbox Image