Blog
You Keep Me Anchored
Posted
Stop Shoving! isn’t going to just be a rail station simulator that passively allows the user to watch a bunch of NPCs push each other around. While I won’t give away the entire gameplay loop this early, a core mechanic will center on the player’s modifications to the platforms and surrounding areas which will impact NPC behaviors in various ways.
To support that, I have to build all the in-game functionality to modify the level on the fly. Being one person, I don’t have the time (let alone attention span) to embed the equivalent of a full featured 3D world level editor that gives players unfettered access to the level meshes, physics bodies, and event processing code to remake each level. It also would make for a tremendous and off-putting learning curve. So I’ll do what a thousand games before have done, which is let the player drop predefined objects onto the level in predictable and simple ways.
Countless games give players a way to place blocks on a grid whether that grid is squares or hexagons[1] and those blocks are city zoning districts or dirt and wood voxels. Why fix what isn’t broken?
The wrinkle is the blocks are going to be a variety of irregular shapes and they’ll be actively interfering with the physics processes of an arbitrary number of NPCs in different ways during different phases of the player’s use of them to modify the level. Plenty will have simple square footprints, or at least rectangular. But they won’t all be the same dimensions along a side. And there are some, maybe many, that will be more complex polygons. Some may have insets or outsets at various heights, others with passages through them that may or may not allow blocks to be placed within. Some will immediately instantiate and trigger NPCs to adjust behaviors right away, others will require a delayed phase-in that impacts NPC actions differently as the phases progress.
How to go about something like this in Godot? Let’s start with just the placement part of it and leave the impacts to NPC behavior for later.
The GridMap node and associated functionality hint at a promising starting point. And the approach I’m taking does lean on them a little bit. For now, the station structures (the platform, entry points, track beds, walls, and so on) are assembled using a grid map. It makes painting a library of meshes into a 3D space and generating collision objects quick and easy (compared to instancing and transforming a ton of individual StaticBody3D nodes, at least). The grid map level design approach also makes it much easier to modify and maintain the station structures as I slowly fill out the protoyping mesh library during development.
But there’s a lot of gotchas in then using that GridMap as the editable base for the player in-game. Each cell in a GridMap can contain only a single MeshInstance3D and those cells, without writing a new class that extends GridMap and implements this, don’t have the ability to track metadata on what a user is allowed to do inside that cell. I don’t want the player to add a bench that floats in the air. Or a vending machine that juts out in front of where a train needs to pass.[2] I also don’t want the player to just be running about replacing platform floor meshes with empty voids.
So, do I write a PartiallyPlayerEditableGridMapWithCellMetadata class that extends GridMap with all these constraints? Or do I do what I’m doing, which is build the static elements with a GridMap and then layer a bunch of anchor points through the areas that players can modify, and provide a library of StaticBody3D objects which can be attached to sets of these nodes? It’s the latter, as the screenshot above probably gave away. This is working well during the early stages of development. One grid map contains the static elements of the level, and a second grid map is used to paint anchors everywhere necessary (and removing ones that should no longer exist because I’ve decided to add a staircase or a pillar or something else where previously there was space for the player to add their own objects).
It does add a bit of work in the scene instantiation, where the level’s _ready() method needs to scan the second grid for non-empty cells and instantiate an SSG_AnchorPoint node for each. I have an optimization todo for down the road to bake all this into a much more efficient data structure at build time and that during runtime doesn’t use Area3D methods like has_overlapping_bodies() to figure out whether anchors are aligned with available anchor points. But it’s a one-pass operation at level instantiation and during testing has been performant enough even when I’ve littered a station with a couple thousand anchor points.
It’s Good Enough to unblock me to move on to the more interesting parts of the simulator, which includes working on the NPC behavior strategies to navigate around obstacles and each other (or in the case of aggressive NPCs with low social cohesion, navigating into each other), and probably as the next big step after that working on NPC-interactable player objects (beyond collisions).
Indisputably superior to squares in every conceivable way. Give me a monochrome hextile srpg with no storyline and an all-vuvuzela soundtrack and I’ll play the hell out of it. That’s how horny for hexagons I am. ↩︎
Well, okay, maybe there are some exciting opportunities in a creative gameplay mode. ↩︎
Reading List
Entries are not endorsements of every statement made by writers at those sites, just a suggestion that there may be something interesting, informative, humorous, thought-provoking, and/or challenging.
-
Wide-Ranging and General Interest
-
Arts, Media & Culture
-
Politics, Law & News
-
Religion, Philosophy & Ethics
-
Science, Tech & Criticism