Creating a 2D camera in a video game is a little trickier than I thought. I started out doing it wrong (didn’t look up enough info), and wasted a lot of time trying to figure out what was going on. Hopefully, I can remove some of that trickiness for you and give you a nice simple example of using a 2D camera in a game.
I looked up stuff in the Monkey X forums and came across this article, but it wasn’t plain enough. So this is my attempt at creating the article I wish existed for Monkey X users and 2D following cameras.
This is all code taken from game #3 in my one game a month challenge. Feel free to use it as needed.
The Theory Behind the Code
I started out by watching the tutorial video for camera movement in the Creating a Simple Game Tutorial. The problem was that it just moves the camera down and is not connected to player movement at all.
I wanted to follow my player around in all 4 directions to increase the play area beyond the screen size. This is where my problems began.
Most of the drawing stuff like scaling and rotating rely on matrix math. Moving the “camera” is done by translating the drawing matrix. This is a fancy way of shifting it one way or another.
So What Does Translating Have to Do With the Camera?
The trick is, when we use a camera in 2D, we aren’t actually moving the camera to follow the player. Instead, we treat the drawing space like a little window into the game, and move the game world into the view.
Think of it like a tablet or smartphone, if you want to see whats to the left you swipe to the right. If you want to see whats down below, you swipe up. Its the same with the camera, if you want to follow your player to the right, you move the world and player left and into your view.
Its that simple. Reading this discussion is what made it click for me.
The Trick
You need to determine how far to move you camera. I ended up using the velocity that the player is moving. However, you have to subtract the velocity from the camera’s “position” to get it to behave right.
This is one of the things I messed up before I figured out that left is right and up is down.
Without further ado, here is a simplified version of…
The Code
So before we begin with the camera we need a few things.
1. A character to follow around
2. A way to move the character
3. Something that lets us know when the character is moving
A character to follow and way to move him
Lets create a simple player with a couple of “vectors” that represent his position and movement.
Class Vec2D
' Vec2D class shamelessly borrowed from Jim's Small Time Outlaws
' Youtube channel on creating basic games with Monkey X
Field x:Float
Field y:Float
Method New(x:Float, y:Float)
Set(x, y)
End
Method Set(x:Float, y:Float)
Self.x = x
Self.y = y
End
' My own personal touch to Jim's code
' Calculates Euclidean Distance
Method Distance(point:Vec2D)
Local xdelta:Float = point.x - Self.x
Local ydelta:Float = point.y - Self.y
Return Sqrt(xdelta * xdelta + ydelta * ydelta)
End
End
Class Player
Field position:Vec2D
Field old_position:Vec2D
Field velocity:Vec2D
Field target:Vec2D
Field distance:Float
Field speed:Float
Method New(name:String, x:Float, y:Float, speed:Float)
Self.name = name
Self.position = New Vec2D(x, y)
Self.old_position = New Vec2D(x, y)
Self.speed = speed
Self.velocity = New Vec2D(0, 0)
Self.distance = 0
End
Method Draw()
SetColor(0, 255, 0)
DrawRect(position.x, position.y, 10, 10)
SetColor(255, 255, 255)
End
Method Update()
' update position
Self.old_position.Set(position.x, position.y)
Self.position.Set(position.x + velocity.x, position.y + velocity.y)
' update velocity
If (target <> Null)
If (position.Distance(target)) < distance
distance = position.Distance(target)
Else
' we are not getting closer to the target anymore so stop moving
velocity.Set(0, 0)
End
End
' update size
If exp >= 10
size += 1
exp -= 10
End
' update box
Self.box.Set(position.x, position.y, 10, 10)
End
Method SetTarget(x:Float, y:Float)
Self.target = New Vec2D(x - 5, y - 5)
distance = position.Distance(target)
Local deltax:Float = Abs(target.x - position.x)
Local deltay:Float = Abs(target.y - position.y)
Local sum_delta:Float = deltax + deltay
If (target.x > position.x)
velocity.x = speed * (deltax / sum_delta)
Else If (target.x < position.x)
velocity.x = -speed * (deltax / sum_delta)
End
If (target.y > position.y)
velocity.y = speed * (deltay / sum_delta)
Else If (target.y < position.y)
velocity.y = -speed * (deltay / sum_delta)
End
End
End
This is a slightly complicated player class that moves the player toward some target position that the user touched on the screen. You can read more about the Vec2D Distance method and the math behind the player SetTarget method in my previous article.
Something that lets us know our character is moving correctly
This particular game is about eating things (think Pacman crossed with snake and my own particular twist). So we are going to put some little dots on the screen for our player to eat. I called them plants (the code that lets him eat them is removed from this example, you can find the full game code on github).
Class PlantLife
Field position:Vec2D
Method New(x:Float, y:Float)
Self.position = New Vec2D(x, y)
End
Method Draw()
SetColor(0, 100, 255)
DrawRect(position.x, position.y, 5, 5)
SetColor(255, 255, 255)
End
End
We will be drawing a few of these to the screen as points of reference to see how we are moving.
The Camera
Surprisingly, the Camera class is really simple.
Class Camera
' Camera class alos shamelessly borrowed from Jim's Small Time Outlaws
' Youtube channel on creating basic games with Monkey X
' Great stuff you should seriously check it out
Field original_pos:Vec2D
Field position:Vec2D
Method New(x:Float=0, y:Float=0)
Self.position = New Vec2D(x, y)
Self.original_pos = New Vec2D(x, y)
End
Method Reset()
Self.position.Set(original_pos.x, original_pos.y)
End
' My own take on the update method though
' This is what we use to follow the player around
Method Update(velocity:Vec2D)
Self.position.x -= velocity.x
Self.position.y -= velocity.y
End
End
Now Putting It All Together
The actual game app is where it all comes together.
Import classfile
Class AppName Extends App
Field player:Player
Field cam:Camera
Field plants:List
Field max_plants:Int
Field map_width:Float
Field map_height:Float
Method OnCreate()
SetUpdateRate(60)
map_width = 1000
map_height = 1000
max_plants = 100
plants = New List()
player = New Player("Me", 320, 240, 4.0)
cam = New Camera( )
GeneratePlants()
' Set the random seed for this instance of the game
Seed = Millisecs()
End
Method OnUpdate()
‘ Get our target position to move the player
If TouchDown(0)
player.SetTarget(TouchX(0) - cam.position.x, TouchY(0) - cam.position.y)
End
‘ Update the camera before we update the player
‘ Because we don’t want to move the camera before the player starts moving
cam.Update(player.velocity)
player.Update()
‘ Refill our eaten plants
GeneratePlants()
End
Method OnRender()
Cls(255, 255, 255)
‘ Push the matrix so we can draw a HUD later
PushMatrix()
‘ Move our world to the camera position
Translate(cam.position.x, cam.position.y)
‘ Draw in our player and our plant life
player.Draw()
For Local plant:PlantLife = Eachin plants
plant.Draw()
End
‘ Pop the matrix back out
PopMatrix()
End
Method GeneratePlants()
‘ draw some plants on the screen so we can see when we move
Local plant_count:Int = plants.Count()
If plant_count < max_plants
For Local i:Int = plant_count Until max_plants
Local xpos:Float = Rnd(25.0, map_width - 20)
Local ypos:Float = Rnd(25.0, map_height - 20)
plants.AddLast(New PlantLife(xpos, ypos))
End
End
End
End
Function Main()
New AppName()
End
And that should give you a simple skeleton game with a player that moves around to wherever you touch on the screen and is always in the middle of the camera.
Bonus post coming Wednesday this week on Game Design podcasts.