Activity 2.2 ended on the fact that makes movement possible: the screen is wiped and redrawn every frame. So you never move anything. You store a position, change the number, and draw at wherever it now is.
Which gives a clean division of labor, and it is worth holding to strictly:
love.update changes numbers. It draws nothing.love.draw reads numbers. It changes nothing.Break that and you get bugs that only appear at certain frame rates, which are among the least pleasant kinds to find.
dt is delta time — how many seconds passed since the last frame. At 60 frames a second it is about 0.0167.
-- wrong: speed depends on the computership.x = ship.x + 5
-- right: 300 pixels per second, on any computership.x = ship.x + 300 * dtAdding a fixed amount ties your game's speed to the computer it runs on. On a 144 Hz monitor the first line moves the ship almost two and a half times faster than on a 60 Hz one. The player did nothing differently; the game is just a different game.
Multiplying by dt fixes it, and the arithmetic is worth doing once by hand. At 60 frames a second you add 300 * 0.0167 = about 5 pixels, sixty times — 300 pixels. At 30 frames a second you add about 10 pixels, thirty times — 300 pixels again.
So the number becomes a unit you can reason about. speed = 300 means 300 pixels per second, which crosses an 800-pixel window in under three seconds. That is a design decision you can make on paper.
The rule: any number you add every frame gets multiplied by dt. Movement, rotation, fuel drain, cooldowns. Anything you set once does not.
if love.keyboard.isDown("left", "a") then ship.x = ship.x - ship.speed * dtendlove.keyboard.isDown asks whether a key is held right now, and takes several keys at once — true if any of them is down. That is how you support arrows and WASD in one line.
Four separate if statements, not elseif. With a chain, holding up and right would only move you up. Separate tests let both run, and diagonal movement falls out for free.
This is polling — asking every frame. It suits things that happen continuously while a key is held. For things that happen once per press, like firing, you want love.keypressed, which Activity 2.5 uses.
local half = ship.size / 2ship.x = math.max(half, math.min(love.graphics.getWidth() - half, ship.x))That is Activity 1.4's math.max and math.min, nested. Read it inside out: math.min stops it going too far right, math.max stops it going too far left. Together they trap the value in a range, with no if.
Clamp after all the movement, not inside each branch. One check at the end of update handles every way the ship could have left, including diagonals.
The half is there because this ship is stored by its center — which is why the draw call subtracts half the size. Storing the center is worth doing: it makes rotation and collision easier later, and it is what Activity 2.4's sprites want too.
Same skynest folder. You are putting a ship on screen and taking control of it.
-- main.lua
local ship = { x = 400, y = 300, size = 40, speed = 300 }
function love.load() love.graphics.setBackgroundColor(0.05, 0.05, 0.1)end
function love.draw() love.graphics.setColor(0.2, 0.8, 1) love.graphics.rectangle("fill", ship.x - ship.size / 2, ship.y - ship.size / 2, ship.size, ship.size)endlove.update(dt) with four separate if statements.* dt from one direction and compare how it feels — it will be far too fast, because you are adding 300 pixels sixty times a second.-- main.lua
local ship = { x = 400, y = 300, size = 40, speed = 300 }
function love.load() love.graphics.setBackgroundColor(0.05, 0.05, 0.1)end
function love.update(dt) if love.keyboard.isDown("left", "a") then ship.x = ship.x - ship.speed * dt end if love.keyboard.isDown("right", "d") then ship.x = ship.x + ship.speed * dt end if love.keyboard.isDown("up", "w") then ship.y = ship.y - ship.speed * dt end if love.keyboard.isDown("down", "s") then ship.y = ship.y + ship.speed * dt endend
function love.draw() love.graphics.setColor(0.2, 0.8, 1) love.graphics.rectangle("fill", ship.x - ship.size / 2, ship.y - ship.size / 2, ship.size, ship.size)end-- main.lua
local ship = { x = 400, y = 300, size = 40, speed = 300 }
function love.load() love.graphics.setBackgroundColor(0.05, 0.05, 0.1)end
function love.update(dt) if love.keyboard.isDown("left", "a") then ship.x = ship.x - ship.speed * dt end if love.keyboard.isDown("right", "d") then ship.x = ship.x + ship.speed * dt end if love.keyboard.isDown("up", "w") then ship.y = ship.y - ship.speed * dt end if love.keyboard.isDown("down", "s") then ship.y = ship.y + ship.speed * dt end
local half = ship.size / 2 ship.x = math.max(half, math.min(love.graphics.getWidth() - half, ship.x)) ship.y = math.max(half, math.min(love.graphics.getHeight() - half, ship.y))end
function love.draw() love.graphics.setColor(0.2, 0.8, 1) love.graphics.rectangle("fill", ship.x - ship.size / 2, ship.y - ship.size / 2, ship.size, ship.size)endvx and vy, and one line at the end applies them. Then multiply the velocity by 0.9 each frame and you have drift. This is the model Activity 2.5's bullets use.
Right and down held until nothing more happened. The square is still on screen because of the clamp, not because the keys ran out.
If it moves in jumps rather than smoothly, your movement is in love.draw. If it is far too fast, a * dt is missing. If it will not go diagonally, you used elseif.