The player's node hierarchy
I usually start games by making the player, and we'll do that now. The player will be a scene with a root node of a KinematicBody2D with child nodes including an AnimatedSprite for the player's sprite and a CollisionShape2D for his hitbox. You can name the root node Player (and leave the other nodes with their default names), make a directory called res://player, and save the scene there as player.tscn. To add the sprites, download them from https://o-lobster.itch.io/simple-dungeon-crawler-16x16-pixel-pack and since we'll have a lot of sprites for the player make a subdirectory within your Godot project of res://player/sprites to put the relevant sprites into: in this case just the knight's run and idle sprites (not the spritesheets). Click the AnimatedSprite node and in its Frames property make a new SpriteFrames, then click it and edit the SpriteFrames to include one animation called idle and another animation called run. Add the relevant sprites to the idle and run animations and give both animations a speed of 15 FPS (which I used) or whatever feels right to you – click on the AnimatedSprite node and set its Playing property to On to see it in action. For the CollisionShape2D, in its Shape property give it a new CapsuleShape2D and edit the new CapsuleShape2D to get the size about right – you'll notice it's pointing up/down when you want it left/right (if you think that's surprising then just bear with me for now, I'll explain in more detail later), so click on the CollisionShape2D node again and in its properties under Node2D and Transform there will be a Rotation Degrees property that you can set to 90 so the capsule is aligned to point horizontally. But don't worry too much about getting it just the right size for now because I'll go into more detail about it soon.
The player's code - Movement
After that's set up, it's time to start coding. Click on the root node and attach a script to it (go ahead and use the default name which should be res://player/player.gd if you've already saved the scene as res://player/player.tscn) and give it the following code so the guy can move around.
extends KinematicBody2D var speed:float = 400.0 var move_vector:Vector2 onready var sprite_node = get_node("AnimatedSprite") func _ready(): pass # Replace with function body. func _physics_process(delta): # Movement move_vector = speed * Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down") if move_vector.length() > 0.1: sprite_node.animation = "run" else: sprite_node.animation = "idle" if move_vector.x > 0.01: sprite_node.flip_h = false if move_vector.x < -0.01: sprite_node.flip_h = true move_and_slide(move_vector)
That's a pretty common way to handle movement for a top-down game like this with keyboard controls and similar to how it's done in the Your First Game tutorial. If you'd like to check out other ways of moving a character around, the Godot docs talk about it at https://docs.godotengine.org/en/stable/tutorials/2d/2d_movement.html
Introduction to the API reference
However, the tutorial didn't use the Input.get_vector() method. if you're not already familiar with Input.get_vector() then you can find out how it works in the Godot docs' API reference. If you haven't looked there before then now's the time to get started because you'll need to go there a lot as a Godot dev, and it might be confusing at first but it's important so I'll talk about it in detail now in case it's new to you. From the Godot docs at https://docs.godotengine.org/en/stable/ scroll down to the bottom of the index on the left and under CLASS REFERENCE expand the Godot API tree to see a ton of Classes. Briefly look through that first one, @GDScript, to see built-in functions like rand_range() which we'll use later. If you click on it, you'll see the entry
float rand_range ( float from, float to )
and you should interpret that as meaning that if you use the rand_range() function then it will take two parameters (which it calls "from" and "to") which should be floating point values and it's output will be a floating point value (that's what the "float" at the very beginning means). So if you want to use it in your own code, you could write a command like
my_floating_point_var = rand_range(my_min_value, my_max_value)
When you start writing your own code and including functions that you haven't seen used in a tutorial or other code before, the API reference is the place to look to see a description of what it does and how it should be structured to work. Now if you wanted to see how that Input.get_vector() method works, in the API listing scroll down to Input and click on it (don't expand its tree, just click on it) and look through its Methods and find get_vector and click it. There you can see that it returns a Vector2 (so Godot will be able to handle move_vector = Input.get_vector(), phew!) and the parameters when you call get_vector() should each be a String with the name of the axis for negative and positive x and y values, which are set up in the Input Map that we'll come back to in just a minute. But first, a couple more examples of looking things up in the API reference. Suppose you didn't know how move_and_slide works. In this example it's not like Input.get_vector() where it's easy to tell that we're looking in the Input class for the method get_vector(). What's going on here is the player.gd script is of a type KinematicBody2D (that's what the very first line of code starting with extends tells you) so the move_and_slide method is in the KinematicBody2D class. Scroll down and find KinematicBody2D in the API reference and click on it to see how move_and_slide works and what parameters can be used with it, and also notice that it returns a value of a Vector2 which you might not have known about but will be helpful if you make a game where you need to preserve vertical velocity when the player jumps (and thankfully we don't have to worry about it for this game but be aware that it's there and should be thought about if you do make a game that involves jumping). As you look through the API reference listing for KinematicBody2D you might be thinking "Wait a minute, there's not a lot here. Aren't there a lot more things that you can do with a KinematicBody2D, like look_at()?" This is the last thing I'll point out for now that might trip you up when you're using the API reference at first – the look_at() method can be used by KinematicBody2D, but it's actually a method that belongs to Node2D. A KinematicBody2D "inherits" from a Node2D, so a KinematicBody2D will have all of the properties and methods that a Node2D has plus some more that are specifically for KinematicBody2D and not for all Node2Ds. In this case, if you scroll up to the top of the page for KinematicBody2D you'll see that it inherits from things including Node2D, and if you click on Node2D to see its API reference page you'll find the look_at() method. And that's enough of the API reference for now.
A note about static variable typing
Also, you might not have seen people declare variables like this
var speed:float var move_vector:Vector2
where the variable's name is followed by a colon followed by the type of variable that it should be. I strongly recommend that you do that, which is called static typing. It will make Godot throw errors if you try to assign a value other than that type to a variable. For example if you had the following erroneous line in your code
move_vector = speed * delta
where speed is a floating point value of 400.0 and delta is the frame rate, then if you didn't use static typing Godot would blithely calculate 400.0 times 0.0167 = 6.68 and put that floating point value into move_vector, which is clearly not what you wanted because move_vector should be a vector and not a float. And it wouldn't show up as an error until later in the code when you try to use move_vector in the move_and_slide() function where Godot is expecting to get a vector saying where the player should move but instead just got the floating point value of 6.68. Then you would have to rummage through your code looking for where things went wrong, whereas if you declared the variable with
var move_vector:Vector2
then Godot would tell you right away exactly where the problem is since you tried to assign a float to a variable that should be a Vector2. This is a simple example, but the more complex your code is and the more abstract the variables become, the harder it gets to track down those bugs. So I recommend using typed variables unless you have a good reason not to. For more details about static typing, see https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/static_typing.html and hopefully their description is more understandable after you've seen this explanation.
Test and tweak
Now let's test out the player and see how it looks: make sure the tab for the player scene is selected at the top of the center viewport, then click the button in the top right to play the selected scene (the movie director thingy with the play button in the middle). The first thing you'll notice is that the player in the top left can move with arrows but not WASD, so you should set it up to move with either WASD or arrows. Stop playing and go to Project – Project settings – Input map and set ui_left, ui_right, ui_up, and ui_down to be controllable with WASD too. Now try playing it again, and it seems ok but I'd like the player to be bigger. So stop playing and click on the AnimatedSprite node and look under Node2D, Transform, Scale and set it to 2 for both x and y. (Remember how I was talking about inheritance earlier? The inspector is helping to teach you that the Transform, which has Scale, is a property of Node2D.) But now the hitbox in the CollisionShape2D node also needs to be made larger. Do you have any vague memories of seeing somewhere in the Godot docs that you should never adjust the Scale of a hitbox? Well, here's your reminder: do NOT just adjust the scale of the CollisionShape2D under Node2D-Transform-Scale the same way you did with the AnimatedSprite. Instead you want to select the CollisionShape2D node and edit the Shape at the very top and adjust its dimensions to something like Radius 6 and Height 16.
Hitboxes in 45° view games
The last point for now is getting the AnimatedSprite and the CollisionShape2D aligned properly. When you're making games like this where the "camera" is sort of looking at the scene from above at a 45-or-so degree angle, think about how things work in the real world. If you're standing in a room looking at a tall tree and a short person, then who will look like they're "in front" from your perspective? See the picture below. Whoever's "feet" are lowest in your field of view will be the one standing closest to you. Even if the tree's center is higher in your field of view than the guy's center, if the position in your field of view where the tree contacts the earth is lower than where the guy's feet touch the earth, then the tree is closer to you. Godot can automatically "sort" things from back to front like this so anything lower on the screen is drawn in front of anything higher on the screen, but remember that the position of the objects should be the position of their "feet" to make this work right. The other concept to be aware of is how large the hitbox should be. Suppose the player in the figure below were to walk straight left – would he collide with the tree? No, he would walk "behind" it from your field of view. But if the guy were a little bit lower in your field of view so his feet lined up with the tree's "feet" and he walked left then he would smack into the tree, and if he were to walk even lower than that then he would be walking "in front of" the tree from your perspective. So the tree's hitbox shouldn't cover the entire tree's sprite, it should only cover a small area around its "feet" where collisions should happen, and same for the guy. In the picture below, you would want the hitboxes to be something like the yellow areas. And if you remember to do all those things – make the "position" of the tree and the guy be their feet instead of their centers, and make the hitboxes cover a reasonable area around their feet – then the guy will be able to walk around the tree and be properly drawn in front of or behind it. Remember that this is only applicable for games where the camera is sort of looking down at an angle and is not applicable for platformers like Mario or Sonic where the camera is looking directly from the side and the hitbox should probably cover most of the sprite.
Putting that into practice with our player, the root node of the scene (the KinematicBody2D) should still have its position property (under Node2D-Transform-Position) be (0, 0) because we haven't messed with it, and this is the "official" position of the player so that (0, 0) is where the feet should end up centered. The AnimatedSprite should be positioned so that the center of its feet are at about (0, 0), and in this case after I set the scale to 2 I set the Node2D-Transform-Position to be (0, -11) so go ahead and set that now. The hitbox CollisionShape2D should have its Node2D-Transform-Position be at (0, 0) and its Shape set to be a CapsuleShape2D with Radius about 6 and Height about 16 while the CollisionShape2D's Rotation Degrees (under Node2D-Transform) is 90.
In the next post, we'll go over making the Level scene and the sword swipes.