Keyboard Movement and Collision

A collision example to show when to correct on the y axis

Series:
Posted on

Day 3

For this day, my plan was to implement keyboard control and basic collision detection/correction. WARNING: Thar be GIFs here!

Keyboard Controlled Movement

Turns out this was a lot easier than I thought it would be. First, I created a new module that listens for keydown and keyup events, checks if the event is for one of the arrow keys, then updates a module level controller object to flag which arrow keys are pressed. Then, I added a player prop (separate from all the other props rendered on screen), checked if the controller object had any arrow keys set to true and updated the player prop’s position.

At this point, I needed to test something I thought might kill performance: For every game tick, clear out the ‘in-air’ render group (the one with all the props in it), re-add all the props back in, as well as the player prop. Turns out, the browser didn’t really care and ran like normal 🙂

Here’s a gif showing the success:

GIF showing movement with no collision

I saw one bug right away with the guy and stool on the far right of the room. When the player prop is on the same y level, that guy and stool swap draw orders. My initial thought is that issue will go away if those two props aren’t constantly colliding with each other, but I can deal with that some other time.

Collision Time

There are a lot of fancy and performant ways to detect collisions, I did not implement one. For this project, my #1 Dev Rule is this:

First make it work, then leave it alone. If it gets in the way of something else, then make it work with that other thing and leave it alone again.

So I wanted to just put in place a basic means of detecting and correcting collisions and worry about performance later. Here’s how I detected collisions:

  1. Take the player’s collision block and an array of all the other prop’s collision blocks
  2. Loop over each prop’s collision block and stash it in an array of collisions if an AABB collision check with the player returns true
  3. Return the array of collisions

That code will not scale well. I could implement something like a Quadtree, which would go before step 2 to first determine which props are actually near the player and eliminates the need to check the player against all props on the screen, but that would go against my #1 Dev Rule. So I just made sure to wrap all that logic in a function called get_collisions() so when my collision detection gets in the way of performance, I can refactor without causing issues for other code.

Collision Correction

Now that I’m detecting collisions, I need to make sure the player can’t move into those collision blocks. In the future, I will want to tie more things into the collision detection so that, for example, the player will show a “Pushing” animation if he’s running into a wall, but the first step of that is to make the player stop when he collides with something.

So the premise is kind of simple: If the player’s collision block moves 2 pixels into another collision block, move the player back 2 pixels.

A collision example to show when to correct on the y axis

In this example the pink square is colliding with the blue square. The red lines show how much the pink square is colliding with the blue square on both the y axis and x axis. Since the y axis collision length is smaller than the x axis collision length, we will move the pink square down by the length of the shorter red line.

As I implemented that type of collision correction, I quickly determined that it didn’t really work for the pink square colliding on all sides of the blue square, so I ended up with something like this:

A collision example showing collision lengths between all sides

So for this, I’m comparing the distances between opposite sides on the same axis: pink top with blue bottom, pink left with blue right, etc.. I don’t know if this is any type of a standard, but it solves the issue better than what I found originally with the first example image above. So in this example image, I determine that the distance between pink left and blue right is the smallest distance and I move the pink square by that distance to correct the collision.

Here’s what it ended up looking like in the game:

The collision in game, not quite working right

So this is a direct result of my #1 Dev Rule for this project. To get movement working, I added a new x and y value to the player prop that contains the full x and y position with its decimal values. When drawing the player to the screen, I only want to draw on exact pixel values without any decimal (I’m pretty sure there’s a reason for this, but you’ll have to look it up to find out why), but to make sure movement is smooth I track the full x and y position as well. In my collision correction code, I’m only correcting the x and y value that’s used for drawing. So while the player doesn’t move while colliding, it’s actual position value keeps moving until the player ends up on the other side of a collision block.

Simple correction though, make sure the movement x and y values are corrected (this will be refactored later).

The collision code now works between two props!

Now I just need to add in all the other props again.

Collision between three props isn't working yet

Remember that #1 Dev Rule? Yeah, I was only correcting the first collision returned in a tick. In the gif above, I need to correct for two collisions…

Collision correction fully working

There we go, I’m no longer throwing out all collisions after the first collision! I can now rest easy.

Progress Summary

Successes:

  • Keyboard Movement
  • Player collides with props

Potential plan for the next day of work:

  • Add map collision data
  • Make player collide with map
  • Start refactoring position code so there are fewer x and y values floating around

Lessons learned:

  • That whole “Don’t prematurely optimize” saying keeps you productive, just make sure you code in a way that will let you optimize later

Leave a Reply

Your email address will not be published. Required fields are marked *