Firing a shot from within a tunnel.
Here’s some footage. I spent a good chunk of this month catching up on bugs, so I don’t have much to show this time.
- Worked on backlog of mock-ups, bugs, and feature ideas
- Committed to shot-recall when the player’s arms are outstretched
- Implemented crouch-walking and hanging from hooks / rings
- Added context-dependent “crouched while mid-air” state
- Added an ‘anchor’ modifier button that allows the player to aim without walking while grounded.
- Implemented fire explosions and columns of fire, and made some new projectile types
- Expanded “attach points” for animation frames from 1 to 8
- Reworked the level context model and implemented frozen room-to-room transfers
Changes to Controls
I touched on this last month, but have now committed to it as a feature. When you hold attack after having thrown a shot, it will be pulled back to you slowly. This allows you to correct your shot trajectory after the fact. I added some small white arrows around the shot to help indicate what’s happening.
The player can now walk slowly while crouched, allowing travel through tunnels and some more flexibility when avoiding hazards.
This was a bit tricky to get working due to a technical limitation / design flaw in the platforming toolkit. Platform code operates on an actor’s first hitbox only, and the hitbox is always assumed to be centered directly on the actor’s XY coordinate. This is entirely down to legacy technical reasons: the engine used to support only one hitbox per actor, always centered, and one of the compromises I made in trying to convert the game over to multi-hitboxes was to keep changes in the platformer code to a minimum. (I am considering taking some time to fix this in September.)
Prior to crouch-walking, the player’s vulnerable hitbox would shrink while crouching, but his environment hitbox never shrank. So the player would never enter a tunnel, even if something like a conveyor floor was pushing him towards it.
Changing env-box sizes after an actor starts up is a bit unreliable because if the box intersects a solid obstacle as a result of a size change, it can cause wildly incorrect collision responses to trigger. The player code generally allows the user to go from standing to crouching without any restrictions, but going the other way, from crouching to standing, requires checking that there is sufficient headroom to be able to stand.
I considered adding a sliding state, but I wasn’t sure how firing shots would work. Generally, in a video game sliding state, your character is locked in one direction for the duration of the slide. Would you be permitted to fire shots behind yourself, and would you be able to grab shots? Should the state immediately end upon becoming airborne, and should the player be able to cancel a slide early by jumping? It seemed like a can of worms.
I added a third action button which anchors the player to the ground while aiming. It might be helpful when standing near ledges. Crouching used to halt the player horizontally, and now it doesn’t, so this is a replacement.
These behave like hang-bars, but the player connects to a single point. I tried to draw a sprite for this in the past, but I ran into issues with how the player’s arms looked in the pose. I decided that the utility of having this outweighs a bad pose, and just went ahead with it.
Explosions and new projectiles
I made a multi-object explosion using the flamethrower projectile graphic. I also made a few new projectile types: gusts (push the player), bouncing breakable bullets, and grenades (which use the same explosion.)
The multi-actor explosion implementation.
World Model and Context Change
The continuous world-site-room model wasn’t working out. It was functional in the sense that it could load new rooms and discard old ones without resetting the scene context, but it also wasn’t helping in a few important areas:
- It led to situations where actors could fall out of the level, unless great care was taken to contain them with solid structures. For example, some enemies may be placed between two walls that only the player is capable of jumping over. This leads to time spent making containment structures instead of working on the enemy encounters themselves. The structures may also become ineffective over time as the enemies are tweaked and modified.
- When straddling the line between two areas that the camera pans between, there are situations where enemies in the off-camera region can snipe the player or otherwise surprise them from a blind spot. While no enemy spawns may have been set up close to the blind spot, a persistent enemy could still wander through the off-screen level and arrive at that position without the player being aware.
- Doors are hard. They were supposed to help with blind spots by blocking enemies while letting the player pass through, kind of like how a stile allows a person to step over a fence while not allowing livestock to pass. The plan was that even if a door was opened, enemies would be unable to walk through them, and their shots would self-destruct on contact with the door region as well. But it looks terrible, and as big as these doors are, there still isn’t enough safe space for the player when entering a new visual region.
- The player could also start walking through a doorway, cause the “door close” state to initiate, then run backwards and get the door to open again. It looked dumb, and could have implications for scripting callback triggers (like, does the ‘door_closed’ event fire every time this happens?)
There wouldn’t really be a problem if enemies only spawned at the edge of the screen, ceasing to exist when walking off-camera. That isn’t the kind of game I have on my hands, though. So after thinking about it, I decided to gut most of the world-site-room model and to add managed room transitions.
By managed, I mean that when you walk up to a door, nearly everything freezes, the door opens, and the player automatically walks through as the camera pans over. Then the door closes, the game unfreezes, and you continue on your way. I didn’t want to do this because I thought it would break immersion and break the flow of gameplay, but it is so, so much more preferable to getting sniped by off-screen stragglers. It also makes vertical room transitions much less clunky, as I don’t need to worry about the player hanging out in the dead-zone space taken up by the status bar at the bottom of the screen.
How has the world-site-room model changed? It used to allow for loading a set of room instances (as specified by a “site”) into the scene context. You would then activate and deactivate them as needed. This is no longer supported: if a room is part of a scene, it is active. I also removed the ability for sites to hold multiple instances of the same room definition.
Sites were imagined as holding maybe a half-dozen or so neighboring rooms, and they would contain information on how to seamlessly link to other sites. Their scope has diminished: now they are essentially just a room that can be transferred to. A site may also have additional sub-rooms hanging off of it, but the vast majority of them will be just a single room.
The room loading and unloading now takes place while the main scene is frozen. The transfer goes something like this:
- Player touches door, initiating the transfer sequence
- The action scene freezes, and the controller/director scene above it takes over
- Controller opens the door, if applicable
- Controller makes lists of “departing” actors and rooms, which will be removed later
- Controller adds new rooms and actors for the destination site, which is just off-screen
- Controller moves the player through the door and pans the camera
- Controller closes the door, if applicable
- Controller unloads “departing” actors and rooms
- Action scene unfreezes
I may leave in a very short pause before and after the move, which might help mask any CPU spikes occurring as a result of loading new room data.
Bug of the Month
In rare cases, a player-shot that collided with the tip of a pyramid-like hill structure would bounce back in the wrong direction.
I noticed this bug totally by chance, and reproducing it meant firing dozens and dozens of test shots while tweaking the player’s X position in tiny increments via debug hotkeys. While I imagine it was a pretty rare occurrence, it was still a highly visible and irritating “what the heck just happened?” event. Here is a diagram as it’s difficult to explain in words:
There are one of two expected responses, depending on which side of the pyramid the actor’s bottom-center coordinate is touching. The observed behavior where it shoots back downhill is the result of tunnelling, which is when a collision object phases through another object unaffected due to the speed at which it’s moving. Since this game’s physics system is running on a fixed timestep, an object moving fast enough could skip over things that were in its path.
Normally such an object would have to be moving incredibly fast for this to happen — faster than the default max velocity cap which is set for every in-game actor. Coupled with this game’s ill-advised internal update frequency of 256 ticks per second, an object would need to be moving 2048 pixels per second (over 4 in-game screens per second) to tunnel through half of a solid 16×16 tile. The pyramid structure presents an opportunity for tunnelling, since the code that checks actors against sloped floors only takes the actor’s bottom-center coordinate into account. At the very top of the pyramid, it is quite possible to tunnel through one of the two sloped tiles.
So the actor tunnels through the closest sloped tile, then collides with the tile next to it which has a different facing direction. The actor bounces off of this tile, which almost looks like it bumped into a sloped ceiling instead of a sloped floor. Then it collides with the tile that it originally skipped over and bounces again (not incorrectly, necessarily), now with a trajectory sending it away from the pyramid and back to the original thrower of the projectile.
The solution I went with was to compare the actor’s velocity angle against the surface normal. If the actor’s angle is within 90 degrees of the normal, then the angled response code doesn’t run (though the position-correction code still applies.) To do the comparison, I get a normalized vector of the player’s velocity, and get the dot product of it with the surface normal. The dot product is 1 if both vectors are facing the same direction, -1 if they’re facing opposite directions, and 0 if they are perpendicular. So in my case, if the dot product is greater than zero, then return early from the angledBounce() function.
Time will tell if this somehow introduces even worse platforming physics bugs. If that happens, then the plan B is to just never have pyramid structures in the game.
Most likely, September will be all about cleaning up issues that I’ve introduced as a result of this month. I need to get the room-transfer sequence stable, and then I have to convert existing maps to use those transfers. It sucks but it’s for the best.
I removed depth tagging for rooms and actors (ie room-over-room) because I thought it was too difficult to set up, but now with the new world context, I’m regretting that decision. I’ll probably work on re-adding it once some other issues are taken care of.
Crouch-walking was working around the 16th, but I just noticed today that it’s broken. Great.
I’ll write another devlog post around the end of September.
Codebase Issue Tickets: 48 (+3)
Total LOC: 114953 (+2068)*
Core LOC: 35624 (-891)
Estimated Play-through Time: N/A (Existing maps need to be converted to new world context system.)
(*) I’m guessing most of this increase is a result of new animDefs for crouch-walking.