Build a Scrolling Shooter with LÖVE in 200 Lines of Lua
In this tutorial, we’ll guide you through the process of creating a simple scrolling shooter game using LÖVE, a Lua-based game development framework. The game involves controlling a plane, shooting bullets, spawning enemies, and handling collisions. Take a look at the final source code.
1. Setup the Project and Environment
Section titled “1. Setup the Project and Environment”- Install LÖVE: Download and install LÖVE from the official website.
- Project Structure:
- Create a new folder for your game project.
- Inside this folder, create two files:
conf.luaandmain.lua. - Create an
assetsfolder to store images and sounds.
2. Configure the Game Window (conf.lua)
Section titled “2. Configure the Game Window (conf.lua)”Set up the game window dimensions and title.
function love.conf(t) t.title = "Scrolling Shooter" -- Window title t.version = "11.4" -- LOVE2D version t.window.width = 480 -- Window width t.window.height = 800 -- Window heightend- Explanation: A vertical window is ideal for a scrolling shooter.
3. Load Assets and Initialize Variables (main.lua)
Section titled “3. Load Assets and Initialize Variables (main.lua)”3.1 Initialize Game Variables
Section titled “3.1 Initialize Game Variables”At the top of main.lua, declare global variables for timers, player properties, images, sounds, and game state.
-- TimerscanShoot = truecanShootTimerMax = 0.2canShootTimer = canShootTimerMaxcreateEnemyTimerMax = 0.4createEnemyTimer = createEnemyTimerMax
-- Player Objectplayer = { x = 200, y = 710, speed = 150, img = nil }isAlive = truescore = 0
-- Image StoragebulletImg = nilenemyImg = nil
-- Sound StoragegunSound = nil
-- Entity Storagebullets = {} -- Bullets in playenemies = {} -- Enemies in play3.2 Load Assets
Section titled “3.2 Load Assets”In the love.load function, load images and sounds.
function love.load() player.img = love.graphics.newImage('assets/plane.png') bulletImg = love.graphics.newImage('assets/bullet.png') enemyImg = love.graphics.newImage('assets/enemy.png') gunSound = love.audio.newSource('assets/gun-sound.wav', 'static')end- Explanation: Ensure the assets exist in the
assetsfolder.
4. Handle Player Input and Movement
Section titled “4. Handle Player Input and Movement”4.1 Movement Controls
Section titled “4.1 Movement Controls”In the love.update function, handle player movement based on key presses.
-- Horizontal movementif love.keyboard.isDown('left', 'a') then if player.x > 0 then player.x = player.x - (player.speed * dt) endelseif love.keyboard.isDown('right', 'd') then if player.x < (love.graphics.getWidth() - player.img:getWidth()) then player.x = player.x + (player.speed * dt) endend
-- Vertical movementif love.keyboard.isDown('up', 'w') then if player.y > (love.graphics.getHeight() / 2) then player.y = player.y - (player.speed * dt) endelseif love.keyboard.isDown('down', 's') then if player.y < (love.graphics.getHeight() - 55) then player.y = player.y + (player.speed * dt) endend- Explanation: The player cannot move off-screen.
4.2 Shooting Controls
Section titled “4.2 Shooting Controls”Allow the player to shoot bullets.
if love.keyboard.isDown('space', 'rctrl', 'lctrl') and canShoot then -- Create a new bullet newBullet = { x = player.x + (player.img:getWidth() / 2), y = player.y, img = bulletImg } table.insert(bullets, newBullet) gunSound:play() canShoot = false canShootTimer = canShootTimerMaxend- Explanation: Shooting is limited by a timer to prevent spamming bullets.
5. Implement Shooting Mechanics
Section titled “5. Implement Shooting Mechanics”5.1 Update Shooting Timer
Section titled “5.1 Update Shooting Timer”In love.update, decrement the shooting timer.
-- Update shooting timercanShootTimer = canShootTimer - (1 * dt)if canShootTimer < 0 then canShoot = trueend5.2 Move Bullets
Section titled “5.2 Move Bullets”Update bullet positions and remove them if they go off-screen.
for i, bullet in ipairs(bullets) do bullet.y = bullet.y - (250 * dt) if bullet.y < 0 then table.remove(bullets, i) endend6. Spawn Enemies and Manage Movement
Section titled “6. Spawn Enemies and Manage Movement”6.1 Enemy Spawn Timer
Section titled “6.1 Enemy Spawn Timer”Control the rate at which enemies appear.
-- Update enemy spawn timercreateEnemyTimer = createEnemyTimer - (1 * dt)if createEnemyTimer < 0 then createEnemyTimer = createEnemyTimerMax
-- Spawn a new enemy randomX = math.random(10, love.graphics.getWidth() - 10) newEnemy = { x = randomX, y = -10, img = enemyImg } table.insert(enemies, newEnemy)end6.2 Move Enemies
Section titled “6.2 Move Enemies”Update enemy positions and remove them if they exit the screen.
for i, enemy in ipairs(enemies) do enemy.y = enemy.y + (200 * dt) if enemy.y > 850 then table.remove(enemies, i) endend7. Detect Collisions and Update Game State
Section titled “7. Detect Collisions and Update Game State”7.1 Collision Detection Function
Section titled “7.1 Collision Detection Function”Define a function to check for collisions.
function CheckCollision(x1, y1, w1, h1, x2, y2, w2, h2) return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1end7.2 Handle Collisions
Section titled “7.2 Handle Collisions”Check for collisions between bullets and enemies, and between enemies and the player.
for i, enemy in ipairs(enemies) do for j, bullet in ipairs(bullets) do if CheckCollision( enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), bullet.x, bullet.y, bullet.img:getWidth(), bullet.img:getHeight() ) then table.remove(bullets, j) table.remove(enemies, i) score = score + 1 end end
if CheckCollision( enemy.x, enemy.y, enemy.img:getWidth(), enemy.img:getHeight(), player.x, player.y, player.img:getWidth(), player.img:getHeight() ) and isAlive then table.remove(enemies, i) isAlive = false endend- Explanation: When an enemy is hit by a bullet, both are removed, and the score increases. If an enemy collides with the player, the game is over.
8. Render Graphics on the Screen
Section titled “8. Render Graphics on the Screen”8.1 Draw Bullets and Enemies
Section titled “8.1 Draw Bullets and Enemies”In the love.draw function, render bullets and enemies.
for i, bullet in ipairs(bullets) do love.graphics.draw(bullet.img, bullet.x, bullet.y)end
for i, enemy in ipairs(enemies) do love.graphics.draw(enemy.img, enemy.x, enemy.y)end8.2 Draw Player and HUD
Section titled “8.2 Draw Player and HUD”Render the player and the score.
-- Set color to whitelove.graphics.setColor(255, 255, 255)love.graphics.print("SCORE: " .. tostring(score), 400, 10)
if isAlive then love.graphics.draw(player.img, player.x, player.y)else love.graphics.print( "Press 'R' to restart", love.graphics:getWidth() / 2 - 50, love.graphics:getHeight() / 2 - 10 )end- Explanation: If the player is dead, prompt to restart the game.
9. Restart the Game After Game Over
Section titled “9. Restart the Game After Game Over”Allow the player to restart the game by pressing ‘R’.
if not isAlive and love.keyboard.isDown('r') then -- Reset game state bullets = {} enemies = {} canShootTimer = canShootTimerMax createEnemyTimer = createEnemyTimerMax player.x = 50 player.y = 710 score = 0 isAlive = trueend10. Additional Features and Tips
Section titled “10. Additional Features and Tips”- Debugging Mode: Add a debug mode to display FPS or other stats.
- Boundaries: Ensure the player and enemies stay within the screen bounds.
- Game Balance: Adjust timers and speeds to make the game challenging but fair.
- Asset Quality: Use high-quality images and sounds to enhance the gaming experience.
- Extensions:
- Add power-ups.
- Introduce different enemy types.
- Implement levels or waves.
Conclusion
Section titled “Conclusion”By following this tutorial, you’ve created a functional scrolling shooter game using LOVE2D. This project covers fundamental game development concepts like rendering graphics, handling user input, collision detection, and managing game states. You can build upon this foundation to create more complex and engaging games.
Go and take a look at the source code for the finished example.
Happy Coding!