• Register

Skein is an old-school-style dungeon shooter currently in development. Taking a leaf from the Gauntlet school of games, Skein is a fast paced one or two player (co-op) game where you battle through hordes of monsters on a quest to save the soul of a talking sheep. The game features hundreds of enemies, a huge number of spells, 6 different playable characters and procedural dungeons with secret rooms, traps and all!

Post news Report RSS Moving On

Getting the player controls just right can be a tricky thing indeed, and today we'll explore some of the design decisions taken for creating and implementing control schemes in Skein.

Posted by on

With the basic design of my HUD finished, and the core dungeon generation code working well, it was time to move on and get stuck into the core gameplay mechanics. This would have to start with the player, as the decisions I took about how the player moves and how it reacts to the environment would later shape other gameplay features like the enemy AI, or item placing, etc... So, it was finally time to add a player object to the game world!

A Question Of Control


In the game Gauntlet, the controls were in the form of an eight way joystick and a couple of extra buttons for shooting, but that didn't mean I had to limit my game's controls to be the same. In fact, since this was going to be my own game and not a clone, I started to seriously think about other control schemes, with the main contenders for the final set being:

  • Left/right to rotate the player, up/down to move forwards backwards - The pros to this were that the player could rotate on the spot and "spray" bullets over an area, and it would permit a very "free" style of movement, but the cons were that it may be very fiddly on mobile, as I'd have to split the left/right and up/down buttons to either side of the display, and have the shoot button easily accessible (which I wasn't sure would be possible or even fun to use on mobile)

  • Absolute direction using a joystick, so the player points in the direction you push the stick - The pros to this were that the response is immediate and you could quickly turn around and change direction, but the cons were that if I do a desktop version, then a gamepad would be required as keyboards wouldn't have the "in-between" angles.

  • Eight-way direction only, like the original - The pros here were that it would make cross-platform simpler as keyboards can emulate it easily and it also meant that I could use an on-screen joystick for devices, feeing up one hand for shooting and spells, but the cons were that the player would have less control over where they shoot.

I dropped the first option immediately, as I really couldn't see how I could make it work correctly on mobile devices. I could create a virtual joystick and let it control forward/backwards/left/right, but that just didn't seem intuitive enough for a fast action game like the one I had planned, so that left me with the absolute control scheme and the eight-way control scheme to choose from. However, I could also pretty much forget about absolute controls too, since I really did want the game to work on desktop with the keyboard, and eight-way controls would make that aspect of things much simpler, and the more I thought about it the more realised that this just seemed "right" for things. So, it looked like I was going to go with the same controls as the arcade game that inspired me after all!

Joystick Woes

I had the control scheme worked out and I had the HUD more or less set to what I wanted to add them into (see last week), so I sat down to start coding. The first thing I did was open up my Player placeholder object and code in basic WASD movement controls to move the player around - simply adding subtracting on the x/y axis when a key is detected - then I went about adding in a collision event with the wall objects to stop the player passing through them - again, I simply used the move_ouside_all() function to quickly resolve this, which is not perfect by any means but it was fine for this early prototype stage.

After testing these rudimentary controls (I knew was going to have to iterate over them as we go along, but they were fine as a base to progress from), I started coding a virtual joystick for the game. This wasn't too tough as I had already done a tutorial on it (available from the Tutorials Tab when you start GMS), so it only took a few minutes to get basic touch-screen movement in there too.

I compiled this to my Android device and tested... and wasn't too happy with what I found! Personally I'm not a fan of virtual joysticks, and testing my project just reminded me why! While they are easy enough to program, they are quite hard to get right. I kept finding my finger slipping out of range of the joystick, and with it being at the bottom left of the screen, excessive movement to the down or left took my finger out of the display too. These are things that can be solved using dynamic joysticks - ie: setting the joystick position to wherever the player touches and accepting input over any range - so I started to change my code to this method and see how it feels (if you want an example of this, check out ButterScotch Shennanigans game "Quadropus Rampage" for an excellent floating joystick, which you can select from the options).

Since I wasn't really convinced by this method my mind was chewing the problem over all the while I was programming. Then, when I was about finished I had an idea that afterwards seemed blindingly obvious...

Virtual Keys FTW

I was looking at my HUD mock up again when I realised that Virtual Keys would do the job for me. In the mock up I had marked the eight directions I wanted the player to move in with little boxes, and that initially made me discard VKs as the solution, since I was thinking that I'd need eight of them, which just seemed like over-complicating things.


However, thinking about it I realised that I could use VKs for what I wanted if I made them overlap! Since virtual keys are placed on the GUI layer, if you set two keys to the same position then both of them will trigger when the user touches the screen, so logically I could make rectangular virtual keys over a square area, and the overlapping parts would be a combined key-press, simulating the exact same input that I'd expect from the keyboard of a desktop computer.


Hopefully you can see that each colour in the above image corresponds to a VK (yellow = up, green = left, blue = down and red = right), so if I touch the top left of the area, I'm essential pressing the same as W + A on the keyboard. Add to this the fact that it naturally forms a "dead zone" in the middle and you have a solution that works beautifully for what I wanted! I had no need to add in any extra code other than that which defines the VKs as I could now just use the regular keyboard functions and the VKs would "just work".

I also took a moment to add in a VK for shooting and another VK for the special attack, which I was starting to think of as a "critical hit", on the right of the screen. They didn't do anything yet but the code was in place for when they would!

Moving Targets

With the basic player movement done, I needed to start adding in something to actually do in the game, which meant adding in the enemies and giving them some form of AI. I have a number of different AI "frameworks" that I've written over the years - a lot in GM 8 as I had an "AI phase" where all I did was work on different ways to make enemies behave in top down games - so I started by taking a couple of them and adding them into the game for the enemies to use. I knew I wasn't going to use these scripts for the final AI, as I like to program that from scratch, but using these old code snippets made it easier to get things going to start with. I then modified my dungeon generator scripts to spawn a few enemies in each of the room areas and started testing...

The first AI I used was a simple one: if the player is in sight, then move towards them, otherwise, use mp_potential_step to try and get closer. I chose this approach since the game is all about "mobs" of enemies that constantly attack the player, and I figured that I didn't really need too sophisticated an AI for that. I did have in the back of my mind the idea that some enemies would be cleverer than others, but I wasn't at that stage yet in development, as I needed to get the most basic enemy behaviour done first and then build on that.

While this enemy behaviour appeared to work well, it wasn't without it's issues... the main one being that when a lot of enemy instances grouped together, they would end up being "pushed" through walls.


This was due to the way I had added in collisions between the enemies and the rest of the game instances. Basically I was using a simple "push out of collision" code like the following:

var xx = other.x;
var yy = other.y;
var pd = point_direction(xx, yy, x, y);
while (place_meeting(x, y, other)){
x += lengthdir_x(1, pd);
y += lengthdir_y(1, pd);
}

This can work well in some games, but because I'd optimised my wall objects (stretching them to occupy more grid spaces), it meant that the "other" x/y in the collision was often not appropriate for the calculation to work. At this point I started toying with a script which would calculate the nearest x/y position to the enemy that would be the equivalent to the centre point for an 8x8 wall tile, hoping to simulate through code a collision with a single square wall instance. However, after drafting something out and testing, I noticed that the more enemies in collision with a wall (or each other), the lower the average FPS dropped, which worried me a lot.

With a game where the sheer number of enemies coming at you is one of the main gameplay elements, performance was a major concern to me, and it was becoming obvious that using any of the built in GM methods for collisions and movement was going to be painful (you can find out a bit more about why this is the case from Mike, here). I started to revise my collision scripts again, to see what I could do to improve things, and I realised that I already had the solution to my problem...

Everything On The Grid

In Mike Dailly's tech blog about precise collisions, he mentions the use of "tilemap" collisions, based on the lookup of an array. This seemed like a good idea, although I figured that since I already had a ds_grid with all the game information already in it, why not use that? Have the player and the enemy check the square in front of it and then move accordingly? The more I thought about, the more sense it made, especially when I looked back at screenshots of Gauntlet - all the enemies aligned to a grid!


I set out at once to adapt the Player movement to this new system, first by having the player check if they were "snapped" to the grid, and then by adding a script to get the return value of a DS grid lookup 1 square in any direction. So, now when the player presses the keys, a value of -1, 0, or 1 is returned for the horizontal and vertical axis, and these values are used to get the contents of an adjacent grid cell. If the cell is "empty" then the player speed and direction is set and no other input is accepted until they are snapped to the grid again. This "snapping" would be really obvious in most games, and probably wouldn't be at all appropriate normally, but since my game is so low-res, it's almost unnoticeable! Certainly, while testing this I didn't feel that the controls weren't being responsive or that I had to wait a moment between initiating a move and actually being able to move again.

Another benefit to using this "snapping", was that I could clear and set the grid cells in the same step. This is important, as I didn't want the player to clear the wrong cell or leave an empty cell where they should be, as that would cause issues later with the enemies. However, since once the player starts to move no more controls are accepted until it stops again, I could easily clear the current cell, set the goto cell before moving there, then move.

And this worked beautifully! On debugging I could see the player grid cell updating each move, and the actual movement still felt natural and fluid, even though it was snapped to a grid. Next step was to add this type of movement to the enemies, which is what we'll discuss next time I write.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: