My Authors
Read all threads
Part 2 of this experiment starts right now! Today, I plan to implement projectile, wall and pickup collisions. I'll generate the terrain procedurally, and I'll add a simple UI.

With this set of features, I want to build an MVP version of the game: loading a map, advancing through it, killing enemies and picking up items, and finally finding the exit.
The idea behind this milestone is twofold:

1. Test and evaluate the core gameplay loop. Show it to other users and gather early feedback.

2. (If this was a compo game) Have something that can be submitted at any time, in case I can't finish the game for any reason.
I start by copying the Collide component together with sys_collide from Goodluck's Trigger example. It supports AABB collision detection, which is enough for my use-case. I add colliders to the enemy and the projectile blueprint.
I modify sys_control_projectile to check if the projectile's collider has intersected with another collider last frame (remember: control systems run first), and I destroy both entities involved in the collision.
You can see in the video that my bullets kill enemies even if I miss. This is because the enemy colliders are too big. I've added sys_debug (also from Goodluck's Trigger example) which draws wireframes around invisible entities (purple) and colliders (green).
(The purple cross in the middle of the screen is the wireframe drawn around the camera itself! You can also see a light entity floating up in the air.)
Let's fix the collider size and actually make it slightly wider than the enemy mesh so that it's a bit easier to hit. It doesn't matter how tall the collider is since all movement happens on the same horizontal plane.
As a fun experiment, see what happens when I add the Collide component to the wall blueprint.

Destructible terrain!

It's a one-line change in the code, and I love how it illustrates the power of ECS and the idea of composing smalls bits of functionality.
I'm going to work on collisions with walls now. It's a more involved problem than projectile collisions where the only reaction to a collision is to destroy an entity. When colliding with walls, however, I don't want to destroy anything; just not let the player pass.
I'll need a collisions response system for this. sys_collide already computes the penetration vector of the intersection, so I can use it to move colliding entities apart. I'll call the system sys_physics, even though it doesn't do any actual physics integration.
I'll keep the wall colliders from the destructible terrain tweet above and I'll just try not to shoot to often for now :) I'll fix this later. The colliders are static: the AABB is computed once and then never updated again. They also never collide with other static colliders.
Here's sys_physics in all its entirety. I run it on all entities with the Transform and Collide components for now, but I'll probably need to get more granular soon. For each collision I move BOTH involved entities backwards (I'll need to change that soon as well).
There's one more interesting thing about collision response: I need to run sys_transform twice! First, to compose transformation matrices after sys_move moves entities around. sys_collide uses the matrices to update colliders' AABBs and find collisions.
And the second time, in order to commit the collision responses to transformation matrices. Otherwise, sys_render would still use matrices from before sys_collide and sys_physics, and consequently, it would render intersecting entities.
Not to worry, though. sys_transform will only update transforms with Dirty set to true, which on the second run are only the transforms of entities participating in collisions.
Here's what I've got after these changes. You can see right away that I need to change the fact that the collision response moves both entities involved in a collision. You can also see that even though the wall cubes move back, their static colliders don't.
For the above to work, I've added a collider to the player entity (i.e. the camera) so that I can collide with the walls. This creates a new problem: when I shoot, the projectile immediately collides with my own collider, and destroys the player entity, including the camera :)
I think the right solution to the problems above is to implement collision layers. I'll want projectiles to collide with walls and enemies, but not the player. I'll want the player to collide with walls. And I'll make walls not register any collisions on their own at all.
Just for fun, here's what happens if I change the wall colliders from static to dynamic. When I walk into a wall cube, the collider now updates its position together with the wall's transform. I think I know how I'll implement secret rooms later on :)
After a break, I came back to implement collision layers. I've used two bitmask fields: one to define which layers the collider is on, and the other one to define which layers it intersects with.
A projectile defines its collider data as:
I've also added a new Health component to the enemy blueprint. It keeps track of the entity's current hit points and destroys it if the HP goes down to 0.
I like this approach better than destroying entities in sys_control_projectile. First of all, I can now have entities that survive multiple hits. I can also imagine other damage dealers besides projectiles, live lava floor or explosions.
Here's my progress so far. The code is at github.com/piesku/peskens…. You can play the most recent version at github.com/piesku/peskens….
Collision detection is versatile and useful. So far, I've implemented shooting by adding colliders to bullets and enemies; and impassable terrain by adding colliders to the player and walls. I'll now add pickable items by adding colliders which trigger some logic.
I start by creating a new blueprint with a static collider and a renderer, and adding a new TerrainKind enum variant: Item = 3.
I then copy the Trigger component and sys_trigger from Goodluck's Trigger example. I could instead add a Collectible component, and then Enterable for the exit to the next level, but I like using Trigger as a catch-all for various one-off logic.
sys_trigger runs after sys_collide and checks if there has been any collisions with this trigger. If so, it dispatches the action stored in the Trigger component. Lastly, actions are defined in an enum and dispatching simply runs a switch statement with logic for each action.
Because the trigger logic is added to collectible items rather than the player, I put the item blueprint on the None layer (so that no other colliders can see it), and I set it to intersect only with the player.
This way, there's no risk of triggering the collectible with a stray bullet or by a walking enemy (once and if they can walk). The item's collider is effectively transparent to all other colliders (since it's on the None layer), but it does register some collisions itself!
Also, note how the logic of collecting is encapsulated within the trigger. The player entity doesn't even "know" that it has intersected with a collectible item. This approach helps avoid the common problem of so-called god classes, which handle all kinds of logic.
Let's test that it works with a good old alert().
Lastly, I edit the action handler to actually destroy the collectible items once it's, well, collected.
I can bow walk into a collectible item to pick it up, and it's properly destroyed.

In the next session, I'll work on storing the number of items collected so far and displaying it in the UI.
I'm going to work on a simple UI today. My goal is to display the current count of items collected since the start of the playthrough. I'm going to take advantage of Goodluck's TodoApp example's sys_ui. It's an immediate-mode UI system using innerHTML at its core.
Immediate-mode UIs work by running a loop (event- or time-based) which recreates the UI in every iteration of the loop. The UI is derived from the current state.
Immiediate-mode UIs are contrasted with retained-mode UIs in which the UI is created once and is then updated selectively in response to events (e.g. by the Controller in the MVC pattern).
If immediate mode sounds like React, it's because React is an interesting hybrid: it's an immediate mode component system build on top of a retained-mode renderer (DOM is inherently retained). The virtual DOM diffing and reconciliation is what makes this marriage possible.
Immediate-mode UIs are a good fit for games because games already have a loop running 60 times a second. Goodluck's sys_ui plugs into the main game loop, too. Your code can dispatch actions which mutate the game state, and sys_ui will then produce the updated HTML.
This is a bit like React and Redux, except that it's veeery primitive. The state is mutable and there's no virtual DOM. Instead, UI components produce stringified HTML which is then innerHTML-ed into the root element.
There's one (important!) optimization to this, however: if the stringified HTML produced this frame is the same as it was last frame, sys_ui doesn't touch the DOM at all. If the UI code is deterministic, then unless the state changes the UI will not update, saving CPU and memory.
sys_ui is an evolution of Innerself, a tiny library I wrote in 2017 for 'A moment lost in time,' our @js13kGames submission. It's the same concept packed into even less code and integrated tightly into Goodluck's core game loop.

github.com/stasm/innerself
Because of how it works, sys_ui is great for displaying rarely-changing game state and not so great for data that changes frequently. If you use it with player's input, you should add sanitization, too, e.g. on top of a <template> element.
In my demo, the number of collected items can change at most N times, where N is the total number of items available. This is definitely rare enough to be a good use-case for sys_ui. Let's get started!
I copy sys_ui from Goodluck's TodoApp example and create a src/ui folder for my UI components. I'm going to store the state directly on the Game instance as game.ItemsAvailable and game.ItemsCollected. I set them both to 0 initially.
When the scene function executes, I set them again—this time with correct values for this map. I'm hardcoding everything for now, risking that ItemsAvailable will go out of sync with the actual number of items on the map.
That's OK, however: the codebase is so small that I'll remember about it. A better solution would be to iterate the map array and count all "3s" (i.e. TerrainKind.Item), but it's not worth it. I'm going to change all this code soon anyways.
I then add a simple "App" UI component which reads the state off of the Game instance and returns a stringified HTML. The html template tag is a helper dealing with interpolating arrays and booleans.
Lastly, I modify the action handler to mutate the state the an item is picked up.
And here's the result!
sys_ui may be primitive, but it still offers one of the most important features for building UIs: composition! I can extract the score display into its own UI component and then nest it in the parent "App" component.
Add some flexbox magic, and I have this!
Next, I'll add a title screen. I want the player to see it when they launch the game, then press "Play Now" to start the first level. I start by defining an enum of states the UI can be in. I'll add more later, e.g. for the "You Win!" message.
I create a new Title UI component and change the role of the App component to that of a router. Depending on the state stored on the Game instance, it will display the UI corresponding to the current view.
This looks good enough for now, but there's something interesting going on. So far, the view state only affects the UI; I still only have one scene in which the camera can be controlled by the player. This means that I can play right away on the title screen!
I think this could be a nice feature, but for the sake of the demo, I want to do something more standard. Let's remove all interaction from the title screen. I'll do it by creating a second scene which looks similar, but doesn't add the ControlPlayer component to the camera.
The code building the maze on the title screen is similar to the one I wrote for the first map, except that I don't need the whole maze this time; the camera doesn't move so I can cheat a bit and remove walls that are hidden from this position.
I set sce_title as the initial scene after the game loads. I also need a way to switch to the other scene, sce_stage, which contains the interactive maze. I do it by adding a new Action which is dispatched when the player clicks the "Play Now"" button.
The $ global variable is defined as:

window.$ = dispatch.bind(null, game);

It allows the HTML produced by sys_ui to dispatch actions in order to mutate the state of the game. There's no encapsulation here; $ can be called by the user if they really want to.
And here's the result. I can now launch the game into the first maze by clicking the "Play Now" button, and the score overlay is only displayed when the game is interactive.
I'll also add some UI for when the player finishes the level. I'll need a trigger placed somewhere on the map which acts as an exit; when the player walks into it, I'll disable the input and show a summary of the playthrough.
I create a new blueprint for the exit trigger: it's just a static collider which intersects only with the player. I'll add a white wireframe to it for the time being, so that I can see it even when sys_debug is disabled.
I add a new action and remove the ControlPlayer bit from the player's component mask when the action is dispatched. I also destroy the exit entity, so that it only triggers the action once during its lifetime.
I can now walk into the exit collider and see it disappear. At the same moment the ability to control the camera is gone; you can't really tell from the video but I'm pressing the arrow keys all along—and yet I stand still.
I can now create the view for displaying the score at the end of the level. I rename View.Playing to View.LevelPlaying and add a new variant: View.LevelSummary. I set it in the Action.Exit handler.
I then create a case for it in the top-level App UI component and create a dedicated Summary UI component. Being able to re-use the Score component is a nice touch, but also not something I'll try to keep in the future. I'll just inline it if I need to make any changes.
This is what the summary view looks like after these changes. It's not much, but again, the purpose of this stage is to build and test the core gameplay look without getting distracted by the looks.
Also as part of the core gameplay loop, I'll add two more levels. My goal is to be able to play through the game from start to finish. I'll need a way to generate, store, load and change levels. Without these features players can't progress successfully.
When I started working on Peskenstein last week, my plan was to use procedural generation to build semi-random mazes. There are two elegant methods that I particularly like, and which I thought would be a good fit for the MVP: noise summing and recursive division.
Noise summing consists of adding outputs of multiple 2D noise functions, each more fine-grained and of lower amplitude. This tweet explains it best:
Here's a very simple example which doesn't even use a noise function; instead it's just two sin(x)*sin(y) functions.
As you can see above, you can generate a somewhat landscape-looking terrain with just a few sine functions. That's not a lot of code at all! I used this approach in my 2019 @js1k entry, HoMM1K: js1k.com/2019-x/demo/42…
Noise summing is OK for generating landscapes but not so great for mazes. In particular, extra work is needed to ensure that the generated terrain has a clear path from start to finish. So the other approach that we frequently use is the recursive division algorithm.
In the recursive division algorithm, you start with a single big room and you divide it into two smaller rooms by placing a wall with a passage between them. You then run the algorithm again on each of the two rooms, unless the sub-room is too small.
We used recursive division to generate levels in Backcountry, in which the random seed changes every 24 hours: piesku.com/backcountry/gr…
I could use recursive division in Peskenstein to complete developing the core gameplay loop, but I feel like I'd need to invest more time than I have right now to support some of the features I have in mind.
I could randomly place collectibles and enemies, but I'd like to be able to have a bit more say about where they go. I also want to be able to control where the player starts each level (and what they see when they start), as well as where the exit is located.
Again, I could modify the procedural generation such that it accounts for these requirements (e.g. by scanning the generated maze for a specified pattern of walls), but I don't want to dive into the rabbit hole of procgen just yet :)
Lastly, ever since I player around with dynamic colliders for walls, I've wanted to add secret rooms to my levels. This is going to be much easier if I design them myself!
Right now the maze is created from a dwo-dimensional array defined in the code. This was probably the fastest way I could do it when prototyping. Unfortunately, you have to squint really hard to see the intended level design by just looking at this code :)
Instead, I'd like to use a proper map editor, Tiled: mapeditor.org. Conveniently, it's capable of exporting maps to JSON. Exported maps look similar to my custom encoding above, except that they're one-dimensional.
I can either fetch JSONs at runtime or copy them into the source code, manually or using a build-time script. Since I want to have only 3 levels for now and I don't expect to iterate over their design a lot at this stage, I think the manual approach will suffice.
I create a new tileset and a map in Tiled. Green is the player's starting position, blue is the exit, yellow is a collectible items, and red is an enemy. It's the same layout of the maze as before, but now I can edit it in Tiled and export to JSON.
I do the same for the intro map, and I move map data into an array. I'll store the current map index on the Game instance, and change it to the index of the next map in the action handler.
Let's add another level. I'll make it bigger than the first one, with proper rooms and narrow passage between them. I'm happy I chose to design maps manually rather than generate them procedurally. This is a lot of fun!
I also add a button to the summary screen which allows me to advance to the next level once I complete the current one. When I finish the last level the button takes me back to the intro but doesn't change the UI to the title screen yet; I'll fix it later.
Here's what the second map looks like in-game! I'm happy with the result but I can tell right away that I'll need doors to make this more interesting and challenging. And secret rooms :)
The current version is up at piesku.com/peskenstein/pl….
Missing some Tweet in this thread? You can try to force a refresh.

Keep Current with Staś Małolepszy

Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!