Basics of Game Development

pirate ship in storm, pixel art
Game Development 101: (incomplete)
a. Basics of Game Development
b. Designing your Video Game
c. Math in Video Games
d. AI Decision-making & Pathfinding
e. Asset Creation & Game Art
f. Physics & Collision Detection
g. Shaders & VFX in Games
h. Multiplayer & Networking in Games

Intro

In this series, you’ll learn gamedev from scratch. The prerequisite to this text is, that you already have basic programming skills & logic-building. Game development process involves coming up with an idea, thinking hard to see if its feasible, designing your game, writing story, choosing development technologies, making art, coding it, adding sounds & music, adding UI interface & publishing it. All of these elements are usually done by separate teams in AAA studios, but here the target are indie developers who do everything alone or in a small do-it-all team. My approach in this text is to  gradually learn about the techniques & to practically implement them.

In a Nutshell

We use some images, 3D models, audio, and text to synthesize a game world which we are able to view on the screen using our game camera; one frame at a time. For the complete animation, we have many frames drawn per second to give the illusion of moving animation.

Now this “synthesis of game world” is what makes the game development so complex. We will slowly find out how.

Understanding the Bigger Picture

Whatever appears on the screen is an image generated as a result of computation. If something appears moving, that means the picture is changing & the new one is replacing it. It happens so fast that we usually do not notice it. Each of this image is called a frame & usually we have 60 frames per second (fps) in most video games.

In each frame, the computation that occurs is:

  1. Inputs are processed; that is, we detect input events such as mouse motion or keyboard key press and so on, so we can later move our characters & everything according to this input.
  2. Game state is changed; that is, the game characters & objects are actually moved & now positions and other variables are updated.
  3. The updated game state is then drawn to the screen (rendering).

This process happens in a loop, for each frame, until the game is quit. We call this a game loop.

In this article, to make it less boring, we’ll make a simple game right now to understand some key concepts. We will develop a 2D space game in which our player spaceship has to dodge the asteroids & prevent the collisions. You will learn about cameras, basics of coordinates, game structure & it will be fun. Right now, just ignore game design & collision detection & focus on the overall idea of gamedev.

The Development Technology

I choose Godot right now, since it is what we will be using later in this series. You dont need to know how it works right now, just download & open it and create a new project named dodge-asteroids. The alternatives to Godot are Unity, Unreal, or other high-level game engines. If you want the taste of low level, I’d recommend LibGDX for Java.

No matter what technology you use, this series doesn’t relies too much on any technology apart from just for demonstration. This text is designed to not be dependent on technology used.

Making an Asteroids Game

The complete project & assets for this game can be downloaded from here. Unzip/open the folder in Godot Engine & open the file “game.gd” (bottom-left file view panel in Godot). Don’t look at details & just play with the code to understand it. At this moment, our target is just to warm up with some basic stuff.

Creating our Player

Our player is just a png image of a spaceship (we name it “sprite”), which we will be putting in our game world & move/animate it using some code. First we will create a class for our player & then put some attributes such as health & speed as well as a method for movement.

class Player extends Sprite2D:
	var health = 8
	var speed = 8
	
	# We load a png image as a texture for this sprite
	func _init():
		self.texture = load("res://assets/PNG/playerShip2_red.png")
	
	# We update position based on user input
	func move(delta):
		if Input.is_action_pressed("ui_up"):
			self.position.y -= speed
		if Input.is_action_pressed("ui_down"):
			self.position.y += speed
		if Input.is_action_pressed("ui_right"):
			self.position.x += speed
		if Input.is_action_pressed("ui_left"):
			self.position.x -= speed

You see that we are already processing inputs from keyboard at this stage & updating our game state (which is the position of player here). To make it functional, create a player instance in _ready() method & call player’s move() method in _process():

var player
var camera

func _ready():
	
	player = Player.new()
	add_child(player)

func _process(delta):
	player.move(delta)

Attach Camera to Player

What is a Camera?

In order to see the game world in our screen, we need a camera. It is like a window through which we see the game world. Usually camera is attached with the player in many games to keep their position in sync so camera moves as player moves. In our game, camera is fixed since player cannot go beyond screen bounds.

In the above image, the green and red lines represent the x and y axes of the coordinate system. It is the world space (any point with respect to these axes are in world space). However, you can see a purple box which is a game camera. When this game is actually run, all we see is whatever is inside the purple box; and we need to move our camera to see other parts of the world.

It is important to note that our screen, too has a coordinate system with origin at top-left. So if you reference any position with respect to screen coordinates, they are said to be in screen space. This property is important as you will later see this thing happening much frequently in shaders.

There is much more detail to cameras that we will later see; the 3D camera projections and the mathematics.

Adding a Camera to our Game

Godot readily provides a camera class that we can use:

var camera
func _ready():
	...
	
	camera = Camera2D.new()
	add_child(camera)

Spawning Asteroids

Our next target is to create an asteroid object, spawn it above outside the screen bounds, make it move downward, and give it a random velocity. Similar to how we created player, we will make its own class.

class Asteroid extends Sprite2D:
	var speed = 140 + randf() * 64
	# We load a png image as a texture for this sprite
	func _init():
		self.texture = load("res://assets/PNG/Meteors/meteorBrown_big" + str(randi_range(1,4)) + ".png")
	
	# We update position based on user input
	func move(delta):
		var direction = Vector2(0, 1)
		self.position += direction * speed * delta

And in game loop function, we instance/spawn it:

func get_camera_rect() -> Rect2:
	var half_size = get_viewport_rect().size * 0.5
	return Rect2(camera.position - half_size, camera.position + half_size)

var elapsed_time = 0.0

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	...
	
	while asteroids.size() < 16 and elapsed_time > 1:
		elapsed_time = 0.0
		var asteroid = Asteroid.new()
		var camera_rect = get_camera_rect()
		asteroid.position = Vector2(randf_range(camera_rect.position.x, camera_rect.size.x), camera_rect.position.y)
		asteroids[asteroid.to_string()] = asteroid
		add_child(asteroid)
	
	for key in asteroids.keys():
		asteroids[key].move(delta)
		if asteroids[key].position.y > get_camera_rect().size.y:
			asteroids[key].queue_free()
			asteroids.erase(key)
                        continue
	
	elapsed_time += delta


The get_camera_rect() function is used to get the extents of camera, so we can spawn our asteroids outside of it. But you should ignore it for now & just copy paste it.

Collision Detection

Godot provides a very elegant way to handle collision for both 2D & 3D. But since our goal is to understand the basics, we will stick to a simple math-based approach that should work for this simple case. We will assume all of our objects are a rectangle & will calculate if 2 rectangle are intersecting based on simple math. If they are intersecting, we will consider it to be a collision.

func is_colliding(sprite1 : Sprite2D, sprite2: Sprite2D) -> bool:
	var rect1 = Rect2(sprite1.global_position, sprite1.get_rect().size)
	var rect2 = Rect2(sprite2.global_position, sprite2.get_rect().size)
	return rect1.intersects(rect2)

We can use the above method in our game loop to check collision for every asteroid with player & if it occurs, we will remove the asteroid from game world & decrease player.health by 1.

func _process(delta):
    ...
	for key in asteroids.keys():
		...
		if is_colliding(asteroids[key], player):
			player.health -= 1
			asteroids[key].queue_free()
			asteroids.erase(key)
			continue
		...

Adding More Things

So far we have made a very primitive game. We can display health on screen using a simple Label that displays text on screen:

var health_label

func _ready():
        ...
	health_label = Label.new()
	health_label.position = get_camera_rect().position
	add_child(health_label)

func _process(delta):
	health_label.text = "Health: " + str(player.health)
        ...

After that, we can add sounds, change of sprite to explosion when health reaches 0 to show end of game. We can also add shoot mechanic to our spaceship & bullet impact to asteroids and so on. But the scope of this article is limited to just warming up with very basics so I must stop here.

Final Words

You have seen a general overview of how a game is made. In complex games, there is much more detail that we will slowly dig as we move forward. The next part of this series discusses the game design.

Game Development 101: (incomplete)
a. Basics of Game Development
b. Designing your Video Game
c. Math in Video Games
d. AI Decision-making & Pathfinding
e. Asset Creation & Game Art
f. Physics & Collision Detection
g. Shaders & VFX in Games
h. Multiplayer & Networking in Games
Table of Contents