Welcome to your first lesson, my esteemed Code Ninjas in
training! You
have come here seeking knowledge of the Code in order to create your
own video games. This is a worthy goal. It is my hope that I can teach
you valuable lessons that will allow you to fulfill your dreams more
quickly and capably. Armed with the secrets I shall reveal, you will be
able to make your game engines more professional and - dare I say it -
more fun. I do not mean to illude you - in no way can I make the path
you have chosen easy. Game design is hard work. But
I can make it easier.
Today's tutorial is about handling the player's input. Almost
everyone
playing your game will have a keyboard, but the keyboard is not the
ideal input device for classic games of the type we want to make.
Mario, Sonic, Metroid, Zelda, Final Fantasy, Klonoa - all these games
are designed for joypads. So, ideally, for players who have access to
PC compatible joypads, they should have the choice to use either their
joypads or keyboards.
But it can be somewhat tricky to programme your game to either
query
both, or decide which one to query depending on the player's choice.
Furthermore, Game Maker doesn't natively have very complete joypad
functions.
These things are what I aim to teach you to overcome.
First, create a new object. We'll call it Joypad. It should be
set to
persistent, so that it is always present, even when the player moves
between rooms. It shouldn't be visible, or have a sprite. Then you
should place an instance of Joypad in your initial room, the one that
your game starts in.
Make a script - we'll call it joy_init, and put it in the
create event
of Joypad.
Let's start writing joy_init:
joy_init()
globalvar JoyCurrent, JoyPrevious,
JoyPast, Key, Joy, AnalogCount, AnalogDeadzone;
AnalogCount = joystick_axes(1) div
2;
AnalogDeadzone = 0.25;
What we're doing here is setting up a few global variables
that can be
referenced easily by every object in your game. The variable
AnalogCount is set to the number of analog sticks the player's joypad
has. We determine this by returning the number of axes and dividing by
2. Then we set up the variable AnalogDeadzone. A dead zone is essential
when an analog stick is concerned. Neutral is 0, full on is 1. But due
to the sensitivity of most controllers, the stick is never exactly at
neutral, but fluctuates around .1 or even .2. A lot of driver software
lets people set up dead zones for their joypads automatically, but we
can't always bet on that. It's better to have your game take them into
account. You can supply any value you think is reasonable (I've used
.25 here), but it's even better if you include some option for the
player to adjust the dead zone values manually - perhaps even
independently for each stick.
Next, we add this to joy_init:
joy_init()
Joy[0]=3;
Key[0]=97;
Joy[1]=10;
Key[1]=13;
Joy[2]=13;
Key[2]=38;
Joy[3]=15;
Key[3]=40;
Joy[4]=16;
Key[4]=37;
Joy[5]=14;
Key[5]=39;
These are the joypad buttons numbers (Joy[]), and the keyboard
keycodes
(Key[]) we'll be using later to check for input. I've only done 6
buttons here, 0-5, because that's all a classic Sonic game really
needs, but you can include as many as you'll be needing in your game.
Any more than 16, though, is probably not a good idea, since most
joypads won't have that many buttons. I've also entered the keycodes as
raw numbers, but you can use the vk_... constants, or the ord()
function as well.
Alternatively, you can read values into Joy[] and Key[] from
an ini
file, or even include an interface for the player to change them
manually, which is best. Control configuration interfaces would be a
tutorial in their own right, though.
Now that we know which buttons and keys we'll need to be
checking for,
we need to give them names. This is just a convenience for the
programmer. I suggest using constants, and naming them after buttons on
a console controller, such as A, START, LEFT, etc.
For example:
Code:
A = 0;
START = 1;
UP = 2;
//etc...
If you did the above, then you could type Joy[A] or Joy[START]
instead
of Joy[0] or Joy[1]. This is useful, especially for the direction
buttons, since remembering which number corresponds to each of the four
can be difficult.
But, actually, we're going to be doing something just a little
more
complicated than just making A = 0 and START = 1. We're going to be
using some binary shifting, and you'll see why a little later.
Instead of setting A to 0 (or whichever number you want to
call "A"),
we'll be setting it to 1 left-shifted by 0. START will be set to 1
left-shifted by 1, and so on. This is what the code should look like:
Code:
A = 1<<0;
START = 1<<1;
UP = 1<<2;
//etc...
That means, in binary, A = 1, START = 10, and UP = 100.
Now that we've got our constants named, it's time to make a
new script
- let's call it joy_step - and put it in the begin step event of
Joypad.
joy_step()
JoyPast = JoyPrevious;
JoyPrevious = JoyCurrent;
JoyCurrent = 0;
for (t=0;t<6;t+=1)
{
if joystick_check_button(1,Joy[t])
or keyboard_check(Key[t])
JoyCurrent |= 1<<t;
}
What the for loop does is set up a binary variable,
JoyCurrent, where
each bit corresponds to one of the buttons being active. It checks both
the joypad and the keyboard, so either one the player uses will work.
So, by default, both the joypad and keyboard are detected by
the game
and no setup or choice between the two is necessary. Although, it's
very easy to rewrite the loop to not check for one or the other, if for
instance you wanted to let the player choose which mode they'd rather
use. Some people may not have a joypad at all and there's no reason to
do extra checks.
So, if either the joypad button or the keyboard key (or both)
is
detected for button 0, JoyCurrent becomes a value of 1. If no other
button is detected, it remains a value of 1. But if another button is
detected, the new value is or-ed together. If both buttons 0 and 1 are
detected, for instance, JoyCurrent becomes a (binary) value of 11. In
this way, with only one variable, you can store which buttons are being
detected during this step.
Of course, before the loop runs, we dump the value of
JoyCurrent into a
buffer value called JoyPrevious (and, one step further, dump
JoyPrevious into JoyPast). This is going to be used to detect pressing
and releasing the buttons, akin to the keyboard_check_pressed() and
keyboard_check_released() functions. We could probably get by with only
two values, JoyCurrent and JoyPrevious, but some joypads are subject to
signal noise, and the addition of JoyPast will improve things in those
cases. For instance, the device I use to convert my Nintendo Gamecube
to be compatible with a PC stops detecting some buttons for an instant
while others are rapidly pressed. This makes performing the spindash in
Sonic 2 nearly impossible, because the Down button stops registering
when the A button is tapped, causing Sonic to launch early.
Now we write another script, just called joy.
joy()
return (JoyCurrent&argument0);
Now, as long as the Joypad object is present in the room, any
object
can call the joy script to test for buttons. For example:
Code:
if joy(A)
{
//make the player jump
}
if joy(B)
{
//make the player attack
}
if joy(LEFT)
{
//move the player left
}
else
if joy(RIGHT)
{
//move the player right
}
Now, what about presses and releases? If you used something
like the
code above, the character would continually jump as you held down the A
button. What we need is another script - joy_pressed.
joy_pressed()
return (JoyCurrent&argument0)
and
!(JoyPrevious&argument0) and
!(JoyPast&argument0);
This will only return true when the button is active in this
step, but
not in the previous step, or the one before that.
And now for joy_released.
joy_released()
return !(JoyCurrent&argument0)
and
!(JoyPrevious&argument0) and
(JoyPast&argument0);
This script only returns true if the button is not active
during this
step or the one previous, but was active in the step before that.
And there you have it. A very simple way of having robust
joypad and
keyboard support for your game, that's fully customisable to boot. If
you don't feel like including an interface for mapping keys and buttons
in your game, at least include an ini settings file. Nothing is more
annoying than actually having joypad support in a game, but then
finding that the buttons are all mapped wrong!
And finally, if you want to use an analog stick to emulate the
directional buttons, you can add this code to the joy_step script we
made.
joy_step()
if !AnalogCount exit;
if joystick_xpos(1) < -AnalogDeadzone
JoyCurrent |= LEFT;
if joystick_xpos(1) > AnalogDeadzone
JoyCurrent |= RIGHT;
if joystick_ypos(1) < -AnalogDeadzone
JoyCurrent |= UP;
if joystick_ypos(1) > AnalogDeadzone
JoyCurrent |= DOWN;
You can check as many axes as you want, of course. You can
even use
code like this to make the right-hand analog stick emulate the X, Y,
and Z buttons like in The Legend of Zelda - Ocarina of Time (Nintendo
Gamecube version). The great thing is, since the values are or-ed
together,
either the buttons or tilting the stick both work.
And that's it! You've got a complete joypad system that takes
input
from either the joypad or keyboard, and is incredibly easy to use.
Download the GML scripts
and a GMK example of this lesson here.
Until next time, happy coding, fellow Code Ninjas!