Godot 3.1 Asteroids Tutorial for Beginners [1] Ship Movement

Turning the ship

You will now be greeted by the Script Editor with some code already there. Any line that starts with a # symbol is a comment. This is just a quick template that explains how the scripts function, but you can remove everything after the first line.

extends Area2D

# Declare member variables here. Examples:
# var a = 2
# var b = "text"

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
#	pass

We’ll start by allowing the player to turn left and right. First we will define TURN_SPEED, which will be a constant and not a variable because we will not be changing its speed. Then we will create our _process function. Everything in the _process function will happen every frame. Inside, we will check if the left or right arrow keys are pressed and turn the player depending on the key.

extends Area2D

const TURN_SPEED = 180

func _process(delta):
	if Input.is_action_pressed("ui_left"):
		rotation_degrees -= TURN_SPEED * delta
	if Input.is_action_pressed("ui_right"):
		rotation_degrees += TURN_SPEED * delta

TURN_SPEED is how fast the player will turn. In the _process function, those two if statements check if “ui_left” or “ui_right” is pressed. Those are predefined actions in Godot that map to the left and right arrow keys, respectively. You can create your own actions in Project Settings.

Inside the if statements, we take our Area2D’s rotation_degrees and add or subtract TURN_SPEED. We have to multiply it by delta in order to make sure everything is timed correctly regardless of frame drops. You can read more about delta timing here if you’re new to gamedev.

Testing our game

Before we can test this, let’s create the main scene for our project. The main scene is the scene that opens when the project is run. On the top-left corner of the window, hit Scene>New Scene. Press the 2D button or F1 to access the 2D view. Create a 2D Scene root node in the Scene Tree menu. Rename it “main”. Right click it and select Instance Child Scene and select our player.tscn. Position the player in the middle of the screen.

Save the scene in the scenes folder as “main.tscn”. Hit the play button in the top right (or F5) and select this scene as our main scene when it asks. Now you should see your ship and it can turn left or right when you press the arrow keys!

Accelerating and Decelerating

Back to our player script. Now we are going to create three new constants: MOVE_SPEED, ACC, and DEC. We will also create a variable called “motion” and set it to Vector2(0,0). Inside of _process, we will create a variable called movedir that will be the direction the player will move. We will have an if statement that checks if the player is pressing up, and if so to accelerate. When up is not being pressed, the ship will decelerate. Finally, we’ll move the player to the desired location.

extends Area2D

const TURN_SPEED = 180

const MOVE_SPEED = 150
const ACC = 0.05
const DEC = 0.01

var motion = Vector2(0,0)

func _process(delta):
	if Input.is_action_pressed("ui_left"):
		rotation_degrees -= TURN_SPEED * delta
	if Input.is_action_pressed("ui_right"):
		rotation_degrees += TURN_SPEED * delta
	
	var movedir = Vector2(1,0).rotated(rotation)
	
	if Input.is_action_pressed("ui_up"):
		motion = motion.linear_interpolate(movedir, ACC)
	else:
		motion = motion.linear_interpolate(Vector2(0,0), DEC)
	
	position += motion * MOVE_SPEED * delta

Whew. That was a lot of new stuff. So, MOVE_SPEED is the top speed the player will move. ACC is basically what percent of our MOVE_SPEED the ship’s speed will increase each frame up is held. DEC is what percent it will decrease each frame up is not held. “motion” is a Vector2 that holds which direction the ship is actually going (which is not necessarily the direction the ship is trying to go, because of acceleration and deceleration). As for the direction the player is trying to go, we have “movedir” under our turning code. That basically takes the rotation of the ship and turns it into a Vector2.

Inside the first new if statement, we lerp, or linear_interpolate, our motion value. Think of it like this: motion is the beginning and movedir is the end. Whenever the up arrow is pressed, the ship will move 5% of that distance (because ACC is 0.05). To decelerate, we do the same thing except our end point is Vector2(0,0) and use DEC. Whenever the up arrow is not pressed, the ship will slow down every frame using our DEC value (if you haven’t guessed, ACC stands for acceleration and DEC stands for deceleration). Lerping is an extremely useful tool to smoothly animate things like this.

So, now that we have our motion value set, we multiply it by MOVE_SPEED and delta and add that value to our position. That line is what actually moves the ship, as the rest is just figuring out which direction to move. Hit F5 and try flying around!As you can see, even when you are not pressing up the ship is still influenced by the direction it was moving. All with the power of lerping & acceleration and deceleration!

Screen Wrap

The last thing we have to do for this tutorial is add screen wrapping. This is what allows the ship to go off one end of the screen and pop up the other. We will create two new variables, “screen_size” and “screen_buffer”. The screen_size will be set in the _ready function. We will then use the wrapf() function to wrap the ship’s position to the confines of the screen (+/- a little bit using the buffer, which makes it look smoother).

var screen_size
var screen_buffer = 8

func _ready():
	screen_size = get_viewport_rect().size

(this is right under the other variables and constants)

position.x = wrapf(position.x, -screen_buffer, screen_size.x + screen_buffer)
position.y = wrapf(position.y, -screen_buffer, screen_size.y + screen_buffer)

(this is inside the _process function underneath everything else)

So, “screen_size” starts off undefined because we have to do it after everything is initialized. This is why it’s placed in the _ready function, which is called automatically after the ship’s parents are initialized but before _process starts. “screen_buffer” is how far off the edge of the screen the ship has to be before it wraps. This isn’t true screen wrapping as you won’t see two sides of the player off of both edges he’s overlapping, but it gets the idea across.

wrapf() is a useful function where if a value goes over a certain amount it is set to the lowest amount, and if it goes under the lowest amount it gets set to the highest. Basically we’re taking either position.x or position.y and wrapping it to the confines of the screen, which in the case of position.x is -8 and 328 (because the actual visible area is 0 to 320, and we also add the buffer).

If we run the game now, we should be teleported to the opposite ends of the screen when trying to leave.

Conclusion

Here is the completed player script with commented code:

extends Area2D

const TURN_SPEED = 180 # how fast the ship will turn

const MOVE_SPEED = 150 # ship's speed
const ACC = 0.05 # acceleration %
const DEC = 0.01 # deceleration %

var motion = Vector2(0,0) # ship's actual move direction (not the desired move direction)

var screen_size # set in _ready()
var screen_buffer = 8 # how far off screen before it wraps

func _ready():
	screen_size = get_viewport_rect().size

func _process(delta):
	# TURNING
	if Input.is_action_pressed("ui_left"): # if the left arrow key is pressed...
		rotation_degrees -= TURN_SPEED * delta # turn left by TURN_SPEED (* delta, for uniform timing)
	if Input.is_action_pressed("ui_right"):
		rotation_degrees += TURN_SPEED * delta
	
	# MOVEMENT
	var movedir = Vector2(1,0).rotated(rotation) # desired move direction
	
	if Input.is_action_pressed("ui_up"):
		motion = motion.linear_interpolate(movedir, ACC) # lerp towards desired move direction
	else:
		motion = motion.linear_interpolate(Vector2(0,0), DEC) # lerp towards stillness
	
	position += motion * MOVE_SPEED * delta # move using actual move direction * speed
	
	# SCREEN WRAP
	# wraps position to the other side of the screen when moving off
	position.x = wrapf(position.x, -screen_buffer, screen_size.x + screen_buffer)
	position.y = wrapf(position.y, -screen_buffer, screen_size.y + screen_buffer)

Here is the project so far: Download

That does it for this tutorial. In the next one we will cover asteroids and shooting, along with resetting the game when we win or lose. We will also have a final tutorial where we pretty the game up a little bit after that.

I hope you enjoyed! Again, if you want more, make sure to follow my Twitter and subscribe to my YouTube. If you want to support me further I also have a Patreon. I’ll see you soon!