Hello everyone I'm Shaun Spalding and
this is the first part of a long tutorial series that will show you how
to build your own Zelda-like Action-rpg from scratch in GameMaker Studio 2. We're going to be covering top-down movement and animation talking
to NPCs, branching dialogue and fulfilling objectives buying items from
a store, fighting monsters, using various unique items and a whole bunch more.
There's a demo available in the description so you can go ahead and play
the thing that we're actually going to make. And you can also use that page to
get access to each episode's source code as it releases. This is a beginner to
intermediate level tutorial series and while I will be delivering my usual
style of thorough explanations for each piece of code, in order to actually get
through this series I won't have time to break down everything so if you're a
total beginner to coding anything at all I might recommend my Complete Platformer
tutorial series first maybe not the whole thing, but at least until you get
the hang of things. But as long as you've either coded before in other languages
or you've even a *little* bit of experience coding GameMaker you should
be just fine. We're gonna be opening up just by setting up our game resolution,
bringing in our player sprite and moving it around the room. Humble beginnings. So
let's open GameMaker Studio 2, start a new GML project and let's get started! So the very first thing I'm gonna do and our totally blank project is bring in
the sprite for our hero, our player character: Lulu the cat. So I'm gonna right click anywhere in the workspace, go to resources and hit Create Sprite or we
can also press ALT+S as you can see. Then I'm gonna click import. Now in this
folder you can see I actually have all of the assets that I've handmade for
this entire series, all in one folder you can download this folder from the video
description if you just want to follow along and don't want to spend time
making all your own sprites and that kind of thing. I will be explaining kind of how each sprite is set up, as I bring them in so if
you do want to make your own artwork (and I highly recommend doing so) you'll be
able to follow along there as well but to speed things up for us throughout the
series I've gone ahead and made all the artwork ahead of time. So if you have these
assets available to you, The sprite I'm looking for is called "sPlayer_strip4.png" now if I - before actually bringing this in - if I just
right-click and hit open and get it kind of in a thing here you can see
this image as a transparent PNG with our four frames all spread evenly apart. And
because I've done that and because I've named it with this "_strip4"
at the end, GameMaker knows when I bring it in that it's an image made of four
frames. ...And you can see it's automatically split it up into four
frames, okay. What I'm doing here with the player
sprite, and what I'm doing with a lot of our player sprites going forward is I'm
actually containing all four directions of movement, that we want our player to
be able to face-we'll actually be able to move in eight but animation wise
we're gonna stick to four, because doing eight directions of movement for every type of
animation will take a lot of time and isn't really necessary for what we want
to do-so what I'm doing is out for all four directions I'm keeping all the
animation for each direction in one sprite. Then we're going to use some
code to dynamically play the animation rather than just relying on the sprite
framework to play an animation for us. When you're making a top-down game it's
very handy to do something like this because otherwise you're gonna need a
separate sprite for up, another sprite for left, right, down and so on. So if
you can find a way - as we're going to - to contain all the directions for a given
sprite in one sprite, do so. The code for that won't be until quite a bit later
but just so you understand why on earth we've got like four directions in one
sprite. If you play the animation he just spins around. But obviously that's not
the animation we're actually gonna have in the final game. So now we've brought
the sprite in I'm gonna give it a name I'm gonna call it "sPlayer". And so
again if you want to set up your own version of this sprite you want to make
four directions for your player character standing idle: starting with
facing to the right, then facing up, then facing left and then facing down in that
order okay it's very important that they are in that order if you make it and
it'll be that same order throughout our player sprites for running, jumping and
all that kind of stuff later on. It'll be first facing right, then up, then
left, then down. It's to do with how directions work in GameMaker. It'll become clearer later on, but that's the order in which you
want to do it, okay. The origin for the sprite - this is always
important - it's gonna be in the bottom center. That's gonna be where our
player is considered to *actually be* in the game world at any point in time. Because even though it's top-down it's also sort of not, because we've got kind
of a front-facing view here and a sorta side facing thing here okay we can still
actually *see* the character. It's more of a 3/4 view right? rather than strict
"top-down" top-down. So in order to represent that: the origin (or where we
consider the player to actually be when they're in the game) is going to be in
the *bottom center* of the player sprite. And the other thing we want to set
up is the collision mask. We don't want a full, like rectangle for the whole thing
so I'm gonna go to manual. I want to define this manually and I'm gonna set it to be
around this kind of size, okay? Maybe bring it up one... that looks about
right to me, I think, yeah. So here I've got a 4 left, right 12, 14 top and
bottom 23. I just want kind of a vague square here to represent the area of
space we want to be collidable as our player is walking around. Like our
head probably isn't gonna count. We could probably even make it smaller
because it's kind of like *this* sort of area of the player that we're gonna want
to consider collidable, okay? Adjust as you need to.
This feels about right for my particular player sprite, we might come back to
adjust it later depending on various other things but for now this will do
fine. So now that we have an image to represent our hero and our player, we can
go ahead and close this and create an object; which is going to contain all the
logic and interactivity of our player. We're gonna name it "oPlayer" to start with and then we're going to assign it sprite to match sPlayer.
The next thing we're gonna do and get this out of the way nice and early
is set collision mask to not be "same as sprite" but to specifically label it as sPlayer.
Ok? that's important you might think "what's the difference?" Same as
sprite would mean that it is sPlayer right? but what we're gonna do throughout the game we're obviously gonna animate our player, they're gonna change sprite from sPlayer to sPlayer_walk or sPlayer_roll and various
other different things and what we don't want is for our collision mask to then
change *with* the change in sprite and to have to set up that collision mask to
be the same on every single sprite. What we do is just set the collision mask to
strictly be sPlayer, so that when our sprite changes our collision mask does
not, and it stays as that collision mask that we set up in our sprite. Now before
we get into the events and logic that control the player let's take a look at
our first room. So you should have a room by default called room0 here. You
open that up you'll just get this big blank rectangle, and we're going to go ahead
and change the width and height of the room. I'm gonna change mine to 320 by 180
okay? This is gonna be the resolution of of the game: 320 x 180. we're going to
use this room to kind of define that. Now you can use whatever resolution you like,
you can use a much higher resolution. The reason I'm going for this one is 1.
because it's the the resolution all my sprites were designed for - this is a tiny
16 x 24 sprite - and also 320 x 180 is a useful resolution as it divides very
cleanly into a lot of popular 16:9 resolutions. And since it divides evenly
into them you're not going to get any problems when you scale up your pixel
art to those most common resolutions it's very simple, it'll happen
automatically, allow us to go fullscreen very easily and run in a window of
various different sizes. So we've set it to 320 x 180 here, but um
and if I just click on the instances layer click on a player and just place
them somewhere in the game room. And then if I run the game, you can see them spinning around as well, because the
player sprite is obviously animating at the moment. But you probably can't see
much at all here and that's because I actually use a 4k monitor so this is
going to be no good for you following the series.
on a 1080p monitor it might like be a bit bigger and be about this much, and
you might be able to see it but what you might want to do - you definitely want to
do this if you're using a 4k monitor and if you don't you probably still want to
do it just so you can see a bit more - is we're gonna stretch the viewport up. So
if I go to Viewports and Cameras and click Enable Viewports and then go to
Viewport 0 and click Visible. You can see we get this a white rectangle that
showed up that's way bigger than our game room at the moment and this is the
camera that we're gonna set up. So we're gonna change the the width and
height of the camera - of what it can actually see in the game world - to be what
our room size is, so 320 x 180 which changes that white rectangle to match.
But then in Viewport, which is how big it is in the game window, we're gonna change
width and height to be... for me I'm gonna change it to 1280 x 720. If you're
following along on 1080p resolution you might want to use 640 x 360. And I'll
show you that now. If I run that, you see it's still a bit small for me but for
most people on a 1080p monitor that'll be nice and big, but I'm gonna do
1280 x 720... ...and that's a bit nicer so we can kind of see what's actually
going on. If you are on a 1080p monitor and you use 640 x 360 the size
proportionally should kind of match up to what I've got now. It is important
to get all this stuff sorted out as early as you can in your project,
that's why we're doing it now before we even have any game logic.
We're deciding on our resolution and what it's going to be, so
we can keep things consistent. So I'm gonna close that now, and the other thing we want to do with our resolution very quickly it's go to Game Options, come to
Windows, and then Graphics. If you tick Allow Fullscreen Switching, what this will do is that will enable a shortcut in your game just built into the runner, so if you press Alt and Enter at any point, the game will automatically swap to full screen. It's arguable that you might want to do
a manual setup of this, but this is more than good enough for our game and what we're gonna make. And I'm also gonna make sure here in the scaling, it is on by
default but just make sure "Keeps aspect ratio" is ticked. Because we're using this
resolution, on most 16:9 monitors you're not going to have
much problem with scaling because we're using a very low resolution that scales
nicely to 720 and 1080p and 4k. But if you do put the game on like a 4:3 monitor, it will end up producing black bars when
you scale up, but it'll stop your art from stretching and looking weird.
If you want to know more about working with different resolutions that
aren't 16:9 and how to deal with black bars and resolutions and
things like that, there's a very good - I think it's a two part series - by Pixelated Pope on resolution and aspect ratio management. He goes over everything in a lot of detail, I highly recommend going there checking that out if that's
something that interests you for your specific project or your game. But as I
said, for what we're doing just setting this up, ticking this on and using this
specific resolution and "keep aspect ratio", "allow fullscreen switching" is
more than good enough for what we want. So if I run the game now, you see our black
window here and if I press Alt+Enter, you can see it's given this full screen and
the pixels are nice and clean they're not stretched or distorted in any way.
Because it scaled right up all the way to 4k with no problem and it would to 1080p as
well. So now that was a lot of setup but I promise you it was very important, but
now we can finally go ahead and start writing some actual game code. In fact if
I just close the workspace down just to get rid of everything and click this
plus make a brand-new workspace that's nice and clean - just a quick way of doing
that - and then we'll close the room as well and get rid of that on the side.
Open our player object, nice and blank and click "Add event" and add the Create event. You'll get an empty code window. Now most of the time, if I'm working on a
code window, what I want to do first of all is maximize it.
I personally make the font nice and big but that's just more for the benefit for
you guys watching the video, depending on what device you're watching it on it
might be harder for you to see etc. But you can shrink and increase the font size with
f7 and f8 in case you are interested in how I'm doing that. There might
be some default text here that doesn't match what I've got here but get rid of
anything that's in the code window when you first create it so it is just
totally blank. The first thing I'm gonna type in here is image_speed. And you can see that's come up in the autocomplete as well, when I finish
it turns green and set that to zero. That's gonna stop us spinning around, because as I said we're going to manually handle all of our animations
later on. So we're going to turn image speed to zero so that basically
disables the automatic animation of this particular object. Then I'm gonna
declare a couple more variables. hSpeed equals zero, that's going to represent
our horizontal speed. vSpeed equals zero that's gonna be our vertical speed at any
given time. The only other variable I'm going to define is how fast we are able
to move when we want to walk. I'm gonna set that to two pixels per frames when I
set speedWalk to equal 2.0. It should be stated I have my own naming
conventions for variables and you can use whatever you like but just try and
be consistent, okay? You don't have to use all the variables I use - it'll make it
easier, certainly, to follow along with the tutorial - but I have my personal naming
conventions, you can use whatever you like. Now before we go any further I'll
just run that just to demonstrate that image_speed = 0, you can see there, it stopped us from animating. So now we're going to add the Step event for
the player which controls everything that happens every single "game step",
the vast majority of our player logic is gonna go in there. Now we could
go back to the workspace and click add event again but what I'm gonna do is
just right click in the code editor, of oPlayer: create - just to keep us in
full screen - and go to add/open event and add the step -> step event. Then
that adds the step event. If I go back to that workspace you can see it's
in there now, and we're ready to start adding code to
that. Just a quicker way to do that for you. Now the first thing we want to
do with the player on any given frame is find out what keys the player is
actually pressing. So then we can work out what to do with those inputs
later: whether we're moving, activating something, attacking, opening the menu,
and so on. So we're gonna have a whole bunch of variables that store the
state of various different keys on our keyboard.
so keyLeft that's gonna be about whether or not we're pressing the left
arrow key (or the A key as we'll come to in a bit) but for now just whether or not
we're pressing the left arrow keys. So I'm type keyboard_check(vk_left),
which is obviously going to return True if we're currently pressing
the left arrow key and False if we're not. So that's one if we're going
left or zero if we're we're not pressing the left key. And then very similarly keyRight equals keyboard_check(vk_right); Same thing for the right arrow key.
Now I'll just quickly do keyUp and keyDown... ...That's exactly the same thing.
Remember to end every line of code with a semicolon. GameMaker
actually lets you get away with not using semicolons in a lot of places, but
please do, it's very good practice to declare the end of a function or a line
of code. Oh and let's fix this here, as you can see this is blue because I
spelled this wrong, there's a handy little spellcheck for you, there let's
change that to that. Now I'm gonna establish a couple more button presses
just for things that won't be used in this episode but will be used later just
makes sense to get them all in now so we don't have to keep coming back to this
part of the step event. Okay so I'm going to do keyActivate = keyboard_check_pressed(); Slightly different function. So keyboard_check(); tells us whether or
not the key is currently down or not, pressed tells us whether or not it was
pressed in *this particular step*. So if key_activate - that's going to be when
we talk to NPCs, activate objects, pick things up and so
on - that's gonna be the spacebar now I'm gonna do just a couple more of these
real quick... so I've added keyAttack in here, which is for attacking things,
that's going to be our shift key. We're gonna use the ctrl key on the keyboard
for activating "items", things like the hookshot and the bow,
that are going to come a lot later in the series. Just getting them in there now.
The other thing is - I'm just gonna shrink the font a little because I'll need some space on the end of these lines - now these are all like well known keys on the keyboard that that you can do with vk_left, right and so on. If you want to use individual letters it works
a little bit differently. We have to use what's called the Ord() function. So
say we don't want to use the left arrow key and we want to use "A" like for doing
WSAD right? instead of vk_left I would type the ord() and in those closed brackets we put two quotation marks. And then in between
those two quotation marks we put the letter that we want to use. Don't
worry too much about it, that's just how you do it. That's just how you get the letter in relation to the keyboard. As
compared to vk_right, vk_up It's interchangeable, if
you want a letter on the keyboard that's the function you use. Middle
click it to bring up the manual if you want to learn more about it, but that's
all you need to know about that for now. The other thing is what if we want
to check two keys. Well we can actually do that we can check,
technically, as many keys as we want in one particular variable. What we can do
is, because this will just return True or False, what we can do is combine it with
another thing that will contain True or False and we perform a logical "Or"
statement with it. so if I type "or" there you can see it has bolded and gone
yellow. And I can put a keyboard_check(ord("A")) in and if I just change this one back to vk_left. So what an "Or" does is it returns True if *either* one of the two functions are True. And they both have to be False in
order for it to return False. So if either one of them is True this will
become 1 or True. The other way we can type "Or" as well, there's two
ways to do it you can either write it like that or you can write it with two
vertical pipes ("||") I believe they're called. This is where it is on my keyboard
it might be somewhere else on your keyboard I don't know, but there it is
for me. Once you do that remember to put a semicolon at the end of here, to
declare exactly where the end of this particular line of code is. I'm gonna do
the same thing for right, up and down We're gonna "Or" them with their WSAD
equivalents... So obviously feel free to pause the video,
while you go and actually copy those things down. Okay so now that we've got our inputs we
know which keys the player is pressing, which keys player is not pressing, we can
begin to use that to process what to actually do with our player. Where we
want to move, how we want to behave. Now we're gonna do this slightly differently
from how we do movement in say, a platform game, where we quite simply
move along one axis, left and right every now and again jump we just sort of
handle the the two axes fairly independently. Instead we're gonna combine our up and down axis with our left and right axis to find a vector of movement. So
we're going to work out - by combining our up and down key, and our left and
right key - work out what angle we're trying to move in? and how far to move in
that direction. The reason to do it this way is because
if we handle top-down movement, if you think about it like moving on a grid, if
you've ever played like a grid-based board game you might be familiar with
the fact that moving diagonally across a grid moves you way faster in a
particular direction than than moving just horizontally, or just vertically.
If we were to move two up and two to the right simultaneously that combines to a total distance further than what we want our actual maximum speed to be. Meaning that
you're able to move faster just by moving diagonally. We don't want that, so
we're gonna do some simple vector stuff to make sure that we're always moving
the exact same distance, regardless of whether or not we're moving diagonally.
The first thing I'm going to do is use a little clever trick to work out
the direction of movement based on all four of these keys. So we're going to
combine all four of these keys together to work out a final direction so if we're
pressing up and right we should end up with an angle of 45 degrees. That's
assuming zero degrees are starting from facing to the right, so like 45 degrees
would be facing up right 90 degrees would be facing up and so on. We're
gonna get back an *angle*. So inputDirection is what's going to store our
angle and it's going to equal point_direction(). That's a function
that you can use by plugging two coordinates in and it'll tell you the
angle between those two coordinates in your room. The room part of it actually
doesn't matter for us we're just going to use the fact that this is a quick way
to calculate "a direction" between two different "grid positions" essentially, so my first position is actually going to be 0,0 just
kind of as like a "root position" imagine it as the root position on a graph.
Then I'm gonna type keyRight - keyLeft So if keyRight = one it means
were pressing the right arrow key if keyLeft is one we're pressing the left
arrow key, so if we're pressing them both they cancel each other out (because 1-1=0) if we're pressing neither they cancel each other out
(0-0=0) but say we're just pressing keyRight: 1 - 0 = 1. So think of that again on on a graph between 0,0 and 1,0
so that gives us one pixel to the right. If we're just pressing the left arrow key it will be
0-1 so we end up with -1. so x will finish on minus 1. Hopefully you
can start to see where this is going, if I then put a comma and the second
y-coordinate I want to put in is keyDown minus keyUp, close bracket,
semicolon. Similar thing, so either 1-0=1 going down. or 0-1= -1 therefore going up. And so say
we had -1,-1 we end up with an angle of like 130 degrees, 135 sorry I
can't do maths today. Just a reminder in case or just to let you know / in case you're not aware.
Angles in GameMaker are always considered as 0 degrees facing to
the *right* and then they circle around all the way back to 360 degrees also
facing to the right okay so 90 degrees is up, 180 degrees is left, 270 is down.
That is also why - if we come into our sprite - we can see we've
got a sprite organized in that same way. So our first sprite is 0 degrees,
then 90, then 180, then 270. That's why we always start all
directions from facing to the right and we go around anti-clockwise.
So we have an angle but it might be irrelevant, if say some of these cancel each other out. Like if we're pressing all four arrow keys
or whatever right? That might give us an angle of 0, because it's just it'd be 0,0
to 0,0 there is no angle between that. I'm not actually sure what that returns actually.
An angle from one point to the same point? probably 0 degrees. So that
wouldn't be relevant so we need to make sure that the combination of keys with
press is actually giving us a valid magnitude in a given direction. So I'm going to type inputMagnitude then this is simply going to equal True if we
want to move in this direction okay or False if we don't actually have a direction to move in. Say we're just pressing the left and
right keys simultaneously and nothing else they should cancel each other out.
So to do that I'm gonna basically make this an "or" between two conditions
the first condition is going to be keyRight - keyLeft != 0
So if keyRight - keyLeft is not zero that means either 1-0
therefore 1 or 0-1 therefore -1. So we either want to be moving to the
left or to the right, but if we combine them both together and it equals zero
they should they they cancel each other out. So assuming that's not zero
it'll come back True so we *do* want to move in whatever this direction is.
And the same for up-and-down so I'm gonna do another logical "or" here open
bracket keyDown minus keyUp does not equal zero, close bracket, semicolon. And then finally,
finally, finally, finally we can actually go about moving the player-character.
So I'm gonna write a comment here with two slashes, turns green, "movement"
just to label this section. In fact we can go to the top here as well and put a
comment in, very good practice as always to comment stuff: "get player input". Okay? Get all player inputs, we calculate some stuff based on it and then we can
process that into our players actual movement. so hSpeed - the variable we declared in the create event - is gonna equal... not zero, well it does
equal zero at the moment but it's gonna equal lengthdir_x. Now this
is a function that we can plug a length and direction into - which we happen to have, we have a magnitude and direction right here - and it'll give us back the X
component of that vector. So it'll tell us how far along the x axis we have
to travel to move if we were going to make that particular movement happen.
So the lengths we're going to provide it is inputMagnitude,
which is a 1 or a 0 depending on whether or not we're moving, multiplied by speedWalk.
That's our walking speed which we know is 2 pixels per frame. (And then
that's the thing you could change if you are holding a button to run or something
like that you could do speedRun and so on.) The direction we're
gonna give it is obviously inputDirection. That angle that we worked out.
And I'm gonna do the same again with vertical speed but instead
of lengthdir_x we're gonna use lengthdir_y. So we're gonna get that y component of
our movement. We use inputMagnitude multiplied by speed walk, inputDirection again. Exact same inputs because it's the same length, same
direction. Then very simply at the bottom x+=hSpeed;
y+=vSpeed; So that's everything, you'll notice obviously we
have these little warnings still up here that tell us we've only used these
variables once, very handy as you build up the code later on to be able to see
back if you've declared any variables you haven't actually used throughout
your project. That's because we're only gonna use those later on, so again don't
worry about those. Let's run the game now if we press f5... We see we've got our game
window here he's no longer spinning around as he was before and if I press up, down, left, right you can see we can move around. And our diagonals are not moving
us faster than if we were just holding one particular direction ok. And I can do it with the WSAD keys as well. I know you can't see whether or not I'm using the WSAD keys,
but trust me you can use both sets of keys and you'll discover that
for yourself, as you're coding alongside me.
That's everything we're going to cover for this episode as I said we're
starting quite simple. The next steps are going to be animating the character and
then of course handling walls and collisions. This series is almost wholly
supported by my patrons over at patreon.com/ShaunJS
Those are the names you see passing by now all lovely people that enable me to do
this work and fun it's very existence. If you like the work
but I do or you think it's important and helpful for others please consider
supporting me and keeping work like this free and accessible for everyone, as well
as getting your name in the credits, regular updates and behind the scenes
source code and goodies. A huge extra special shout out to the following
patrons: [CREDITS]