An analysis of asteroids

October 1, 2019 Bram 0 Comments

To build upon the starscape shader from my last post I took it upon myself to build an asteroids style game on top. Thankfully conventional godot gamedev is much better known to me than shader stuff so I should be able to provide some helpful insight.

The player

To start with I drew up a simple white triangle in photoshop to act as our player sprite. As the player requires custom physics this should be a kinematic body so we can interact with physics objects and control our character. Then we can add a collision shape drawn over the sprite which is a necessary child of the kinematicBody.

the kinematic body with child sprite and collision shape
the triangle image with bounding collision shape

Now a script can be attached to the root node and we can work on the player controls. We require 4 to replicate asteroids: rotate left and right, move forwards and shoot. We can poll a few default keyboard inputs in Godot from the arrow keys and space bar/ enter key, giving them placeholder functions will look like this.

func _process(delta):
	if Input.is_action_pressed("ui_left"):
		turn_left()
	if Input.is_action_pressed("ui_right"):
		turn right()
	if Input.is_action_pressed("ui_up"):
		thrust()
	if Input.is_action_pressed("ui_accept"):
		fire_weapon()

There are a few key points here that require explanation. Firstly _process(delta) is a function that runs every frame on the object with the attached script and delta is the amount of time that has passed since the last frame. Each of those functions within if blocks is a placeholder that just runs the pass command like so.

thrust():
	pass

pass tells the interpreter to do nothing. This is helpful for quick prototyping as we can write code that is well laid out without having to define all our functionality there and then and focus on whatever our most pressing matter is. This is a delightful artifact from python that I’m thankful Godot takes on board.

Input allows us to get an input from any source which we can define in the godot editors preferences. It has a few predefined shown in the image below. The is_action_pressed method lets us poll the inputs defined here and just check if the input is down. then if so run the function, this is helpful for continuous acts like movement.

the godot default inputs

Rotation

This is a relatively simple process. We need to define a rotational acceleration and maximum rotational speed. Having these two variables will allow us to control the rate of change of rotation which would feel jarring if it didn’t change over time. All we need to do is check our current rotation isn’t beyond the limit and if so increase our current spin by the acceleration (in the appropriate direction where positive is counter clockwise and negative is clockwise).

var spin = 0
var r_acc = 0.01
var max_r = 0.15

func rotate_left():
	if abs(spin) < max_r:
		spin -= r_acc
		
func rotate_right():
	if abs(spin) < max_r:
		spin += r_acc

The final part of this process is to apply our rotation in the process function so frame by frame we can update rotation.

$player.rotate(spin)

Thrust

there is a little novel jiggery pokery involved here to turn our players direction into a vector to move them by but other than that its a straightforward few steps.

var v = 0.0
var acc = 0.1
var max_v = 2.0

func thrust():
	if v < max_v:
		v += acc

as you can see the thrust method itself is a little simpler as we are only moving positively between 0.0 and 2.0 however applying this velocity is where things become a little more complicated.

var velocity = Vector2(0, 0)
var speed = 200

velocity = Vector2(v, 0).rotated($player.rotation) * speed
velocity = $player.move_and_slide(velocity)

in the _process function we need to keep a track of the players velocity. The magic happens in Vector2(v, 0).rotated($player.rotation). Here we take a vector2 which has our v value from thrust as a magnitude. It is rotated to point in the direction of our ship and multiplied by a speed value to make our ship move at a controlled rate.

The final part of this process is using the move_and_slide method on the kinematic body, which allows us to move the player sprite whilst paying attention to friction and the physics of other objects it may collide with.

Drag and camera

Now our ship moves but never decelerates, which is undesirable even when simulating a zero G environment. There is one neat trick we can do to apply smooth drag to both rotation and thrust, all we need to do is say if a button isn’t pressed multiply the velocity or rotation by a drag factor between 0.0 and 0.99. As this will apply every frame the value will be eased smoothly to 0.

# handle rotation
if Input.is_action_pressed("ui_left"):
	rotate_left()
elif Input.is_action_pressed("ui_right"):
	rotate_right()
else:
	spin *= drag
	
# handle thrust
if Input.is_action_pressed("ui_up"):
	thrust()
else:
	v *= drag

This last step won’t take a moment, we just need to add a camera and we are done. Thankfully Godot makes this incredibly easy.

The 2d camera with parameters.

We just add a camera2D to our player scene with a couple of parameters. Current is set to true so we use this camera by default, zoom is set to 0.75 as it felt a little far out and finally we set the limits to -500 and 500. Those are the extents of our 1000 pixel star field background, so now the camera cannot leave our lovely backdrop.

Wrapup

That’s it! This was a longer post than I expected but it was a nice opportunity to neatly lay out some game development. I do hope this was of interest or help and stay tuned next time for shooting and some primitive asteroids.

extends Node2D

var spin = 0
var v = 0.0
var acc = 0.1
var velocity = Vector2(0, 0)
var speed = 200
var max_v = 2.0
var r_acc = 0.01
var max_r = 0.15
var drag = 0.9

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	
	# handle rotation
	if Input.is_action_pressed("ui_left"):
		rotate_left()
	elif Input.is_action_pressed("ui_right"):
		rotate_right()
	else:
		spin *= drag
		
	$player.rotate(spin)
	
	# handle thrust
	if Input.is_action_pressed("ui_up"):
		thrust()
	else:
		v *= drag
	velocity = Vector2(v, 0).rotated($player.rotation) * speed
	velocity = $player.move_and_slide(velocity)
	
	# handle shooting
	if Input.is_action_just_pressed("ui_accept"):
		fire_weapon()

func thrust():
	if v < max_v:
		v += acc

func rotate_left():
	if abs(spin) < max_r:
		spin -= r_acc
		
func rotate_right():
	if abs(spin) < max_r:
		spin += r_acc
		
func fire_weapon():
	pass

source

game

https://drive.google.com/open?id=1mL1h443cRxn0EOJ3cMRmvfFJCRjx0Bb7

Leave a Reply:

Your email address will not be published. Required fields are marked *