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.
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…
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.
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.