Over the past few days I’ve been working on making the player character more mobile. One of the abilities I’m adding to do this is wall-jumping, where the player can push off of a wall to bounce up and away from it. This is intended to provide the same sort of acrobatic feeling you get from double-jumping, but with a bit more depth.

Getting wall-jumping working was fairly straightforward – I extended the jumping logic to allow jumping any time the player character is up against the wall, and the player is pushing the dpad in the direction of the wall. After that all I had to do was tune some values so that the wall-jump pushed the player away from the wall instead of straight up, and fix some edge cases so that the player can’t wall-jump while standing on the ground (among other things).

Getting wall-jumping to feel fluid and enjoyable, though, is a lot harder. It turns out that without good visual cues, it’s easy to accidentally hit the jump button too early, or start pushing away from the wall before you’ve made contact with it – both of these instinctive behaviors will make what looked like a correctly-performed wall jump fail and end up frustrating the player. To solve this, I had to make a few specific changes:

  • The first change was to add some ‘grace time’ to the input events used to perform a wall-jump. Instead of responding directly to the Jump button press to perform a wall-jump, I periodically check whenever the player is in contact with a wall to see if the jump button was pressed recently, or is still being held. This allows the player to hit the jump button a little bit early and still wall-jump when the player character actually makes contact with the wall.
  • Another change was to make the player character subtly ‘grip’ the wall whenever he first makes contact with it, as long as the player is holding the dpad in the direction of the wall. It’s not completely effective due to the lack of an animation, but the subtle pause in motion makes it easier to tell when it’s time to press the jump button to rebound off the wall.

I also ran into some problems that made wall-jumping feel kind of strange in practice:

  • Initially, making contact with a ceiling didn’t halt your ascent during a jump, so jumping in an enclosed space or jumping close to the ceiling (this is easy once wall-jumping is available) could cause the player character to basically ‘float’ up against a ceiling, which looked bizarre. I solved this by making the jump logic automatically set the player’s Y velocity to 0 whenever he encounters a ceiling while moving up.
  • The original physics for moving while airborne allowed you to change your horizontal velocity as if you were on the ground, which meant that you could essentially ‘climb’ up a flat surface by wall-jumping off it and then moving back towards it repeatedly.

Surface climbing via walljumping ended up being harder than I expected at first. My first solution was to limit the number of wall-jumps off a single surface to one – this solved the problem I cared about, but had the downside of making it very frustrating to mess up a wall jump and then be unable to jump off the surface again and try to recover.

The second solution was to restrict wall-jumps off the same surface – my first try at this was to simply prevent the player from wall-jumping off the same surface until his Y position was lower than the position he previously jumped off of – this prevented climbing, but felt really strange and arbitrary and wasn’t fun.

The third solution was a combination of the first two – instead of completely disabling repeat jumps off a surface, I made all the jumps after the first provide the player less of a ‘boost’ so that they couldn’t be used to climb, and combined that with a more forgiving version of the Y check. It felt less terrible than the previous two options, but it still sucked.

Finally, someone on IRC suggested retuning the physics to make it literally impossible to climb up a surface, instead of trying to use arbitrary restrictions to prevent it. So, I added a friction effect to the movement code that reduced the speed at which the player could alter his X velocity while midair, adjusted some of the velocities for the jumping code, and made the player fall faster. These three changes combined resulted in fairly satisfying physics for wall-jumping – the player could bounce back and forth between the walls of a shaft to ascend, but couldn’t get back to a wall after bouncing off of it fast enough to actually climb up the wall.

Lesson learned: If the code looks like a dirty hack, it’ll probably feel like a dirty hack when you’re playing it.

After this, I started looking into some bugs I had noticed related to how the player character behaved on slopes. There were weird glitches when crossing over the edges of slopes, or when jumping onto/off of slopes. This turned out to actually be a bit of a rat-hole – the method I was using to handle motion and slopes, while functional, had a bunch of nasty edge cases that I hadn’t noticed due to the fact that they were self-correcting: the player character would ‘glitch’ for a frame or two, and then the physics code would pick up on it and correct the player’s location.

The core problem turned out to be that I had tried to make my collision detection code *too* sophisticated: The code for computing the maximum distance the player could travel was attempting to use dot products and multiple projections to try and come up with a ‘pretty’ direction for the player to move in when obstructed, which had a tendency to occasionally generate direction vectors that, while mathematically valid, looked retarded. The solution was to go back to a simpler implementation of collision detection that predictably pushed the player in the opposite direction of his velocity in order to resolve collisions, so that his direction of motion remained consistent. Doing this meant that I had to complicate my movement code a little – previously, I applied X and Y velocity in a single collision pass; now I have to perform one pass per axis. But, in the end, this turned out to be a good thing anyway.

The second problem was that the player’s Y position when moving onto/off of slopes tended to be a bit off – either a couple pixels too high, or a couple pixels too low. This turned out to be a flaw in the approach I was taking to compute the correct location for the player: I was using a rounded bounding box for the player, in order to make it easier for him to travel up and down slopes, and combining that with the ComputeStandingY technique I mentioned in an earlier post. The problem was that I was using ComputeStandingY incorrectly, and passing it incorrect X coordinates when computing the correct Y for the player’s left and right edges. This problem was being disguised by the rounding on the bounding box, which happened to make the result Y coordinate more or less work out right in most cases.

The solution ended up being an aggressive simplification of the code: I killed a bunch of special cases, made the code more consistent (for example, fixing the use of incorrect X coordinates), and most importantly, got rid of the rounded bounding box and switched back to a rectangle. These changes combined with the ones I previously made to fix collision detection ended up resulting in a much more robust model for moving and colliding with the level, and the code ended up smaller as a bonus.

I also spent a bit adding the ability to reload the current level at runtime, without having to restart the game and re-run the XNA content pipeline. It only works on the PC, since the 360 doesn’t have a straightforward way for deploying files without the content pipeline, but it’s still pretty handy – instant reload (mapped to a hotkey) combined with a mouse cursor that lets me pick tile coordinates, allows me to quickly and easily prototype level geometry without the need for editing tools. The algorithm for doing a runtime reload turned out to be pretty simple – I load the new level in a background task, and once it’s done, on the main thread I unload all the level geometry and entities from the old level (which I still have), and then load in all the geometry and entities from the new level. As long as I haven’t placed geometry on top of the player, everything works out just about right, and I save about 30 seconds every time I make a change by not having to reload the game.

        protected IEnumerator<object> ReloadLevel () {
            var f = Future.RunInThread(
                (Func<string, Game, Level>)Level.Load,
                @"..\..\..\..\Content\test.level", this
            );
            yield return f;

            ApplyLevel(f.Result as Level);
        }
        protected void ApplyLevel (Level newLevel) {
            if (Level != null) {
                _LevelGeometry.Clear();

                foreach (var obj in Level.NamedObjects.Values) {
                    if (obj is IInteractable)
                        Interactables.Remove(obj as IInteractable);
                    if (obj is IGameComponent)
                        Components.Remove(obj as IGameComponent);
                }
            }

            Level = newLevel;
            Map = newLevel.Tiles;

            _LevelGeometry.AddRange(newLevel.Geometry);

            foreach (var obj in newLevel.NamedObjects.Values) {
                if (obj is LevelGeometry)
                    _LevelGeometry.Add(obj as LevelGeometry);
                if (obj is IInteractable)
                    Interactables.Add(obj as IInteractable);
                if (obj is IGameComponent)
                    Components.Add(obj as IGameComponent);
            }
        }

I also spent a little bit fiddling with the rendering code to see if I could make the player character look better on sloped surfaces. Right now, one of his feet floats about 4-8 pixels off the ground, which looks pretty silly. My experiments resulted in this:

While it does look nicer when he’s actually on the surface, I couldn’t find any way to make the transition between flat surfaces and sloped surfaces not look strange and unnatural, so I ended up giving up on this one. (It also looked really weird if you happened to jump off a sloped surface, and that also introduces some questions about the physics and why the player is jumping straight up instead of at an angle…)