
In part 1 we drew the player object to the screen. You may have noticed something a tad bit counterintuitive -- the origin point (0,0) isn't in the bottom left like we were taught in algebra. Instead it is in the top left. Each individual image also has an origin point in the top left. So when we're picking a position for a image what we're really measuring is the distance from the top-left of the screen to the top-left of the image in pixels.
4. Creating A Player Object
In the last tutorial we placed the player at 100,100 but that value isn't going to be static. Instead,
the player will be moving. We need to create variables to store our current player location. While we
could just add an x
and y
variable to our file, I prefer to store related
variables together. Let's change our player
variable to be a "table" or "object."
Let's remove the playerImg
variable. This will cause an error in the draw command, but
we'll fix that in a second. Then lets add the following declaration where playerImg
used to
be.
player = { x = 200, y = 710, speed = 150, img = nil }
Now we have a player table that contains an x
and y
coordinate, a
speed
we'll be using later, and an img
. Note that I've left the img value blank.
That's because we need to fill it in at runtime. Where you currently have playerImg = love.graphics.newImage('assets/plane.png')
change playerImg
to player.img
. Do the same to the playerImg
used
in the love.draw
function.
If you run the application now -- and you should -- you'll see exactly the same thing as you did before.
Let's make use of our new player variables. Change our draw command to read:
love.graphics.draw(player.img, player.x, player.y)
5. Adding User Input
You should now see our player image centered in the bottom of the screen, right where it should be. If
we change player.x
or player.y
the player will move accordingly. Let's add some
user input. Replace your current empty love.update
with
-- Updating
function love.update(dt)
-- I always start with an easy way to exit the game
if love.keyboard.isDown('escape') then
love.event.push('quit')
end
if love.keyboard.isDown('left','a') then
player.x = player.x - (player.speed*dt)
elseif love.keyboard.isDown('right','d') then
player.x = player.x + (player.speed*dt)
end
end
When you run our game again you'll be able to move the player back and forth and exit out just by hitting the
escape key. If you feel your player should be faster or slower you can change player.speed to a higher or
lower number. We multiple by dt
standing for delta-time or the change in time since the last
update call to account for framerate differences between machines.
We do have a few issues though. The main one is concerning bounding. Our player can just fly right off screen. We'll have to check his current position before we move him. Change our movement key block in the update function to the following:
if love.keyboard.isDown('left','a') then
if player.x > 0 then -- binds us to the map
player.x = player.x - (player.speed*dt)
end
elseif love.keyboard.isDown('right','d') then
if player.x < (love.graphics.getWidth() - player.img:getWidth()) then
player.x = player.x + (player.speed*dt)
end
end
All we're doing is making sure our player.x
variable is within our game world. For our right
bound we get the width of the screen (love.graphics.getWidth()
) and subtract the width of the
player image (player.img:getWidth()
). This gives us the correct value for the top-left
corner position of our player.
6. Creating Bullets
Are you ready for the tricky part? We need to allow our player to fire. This means creating and tracking an entire table of bullet objects, drawing them, updating them, etc., and using timers to track when our player is allowed to fire. Let's start by declaring some variables at the top of the file.
-- Timers
-- We declare these here so we don't have to edit them multiple places
canShoot = true
canShootTimerMax = 0.2
canShootTimer = canShootTimerMax
-- Image Storage
bulletImg = nil
-- Entity Storage
bullets = {} -- array of current bullets being drawn and updated
While we're here let's load our bullet image (stored in bulletImg) in our load function. Our load function needs to contain the following line:
bulletImg = love.graphics.newImage('assets/bullet.png')
You're probably wondering why we're not using an object for bullet like we did with player. Bullet will be an object but we're going to declare that object with each bullet created. To say ourselves some loading time we're going to load the image ahead of time, store it by itself, and just reference it later when we create the bullet itself.
Those timers aren't going to take care of themselves. We'll have to deal with them in our update loop. We'll be manually subtracting values each time the frame updates. Here is the code:
-- Time out how far apart our shots can be.
canShootTimer = canShootTimer - (1 * dt)
if canShootTimer < 0 then
canShoot = true
end
You'll note that we're not just subtracting 1 on each update. Instead we're subtracting 1 × dt. Like in our other calculations we want to account for varying framerates between computers.
After we do our subtraction we check our timer value and if it is under 0 we set our canShoot variable to true. Later in our update function we'll check this value and our keypresses. That code reads as follows:
if love.keyboard.isDown('space', 'rctrl', 'lctrl') and canShoot then
-- Create some bullets
newBullet = { x = player.x + (player.img:getWidth()/2), y = player.y, img = bulletImg }
table.insert(bullets, newBullet)
canShoot = false
canShootTimer = canShootTimerMax
end
We're giving the player a lot of options here. They can use space or either control button as their fire
key. However, we can't just create a new bullet if one of those keys is down. If we do that the player
will be able to create a constant stream of bullets, literally hosing down enemies. You can see this
by removing and canShoot
from our code above.
The next line creates a new object called newBullet
. Like the player object, we give
bullets an x and y position as well as an image. Our y position is just the player's y position. This
will put the bullet at the top of our player image. For our x position we need to do a little math. We
take the player's x position and move over by half the width of the player's image. This gives us the
center x position of the player, and means the bullet will be "fired" from the top center of our player
image.
Finally we get to our table.insert
call. This is one of lua's built in functions for
dealing with tables. Earlier we declared an empty table called bullets
. Now we'll use
table.insert
to add our entire newBullet object to the bullets table. Later we'll loop
through this table and update each bullet object.
Actually, we're going to loop through those twice. First, in our draw function:
for i, bullet in ipairs(bullets) do
love.graphics.draw(bullet.img, bullet.x, bullet.y)
end
Then in our update function:
-- update the positions of bullets
for i, bullet in ipairs(bullets) do
bullet.y = bullet.y - (250 * dt)
if bullet.y < 0 then -- remove bullets when they pass off the screen
table.remove(bullets, i)
end
end
Both of these little code snippets are loops. Here we take a table full of bullet objects and perform
the same operations on each one. The loop is just saying for each bullet in bullets do x
.
However, we're going to use a curious lua function called ipairs
. In our loop we have both
a bullet object, bullet
, and the index (location) of that object, i
. These values
are returned by ipairs
. We use that index in our table.remove
function to ensure
we're not wasting time drawing or updating bullets that have left the screen.

That concludes part 2. In part 3 we'll create enemies, handle collisions, and make a real game out of this.