Help Generating Enemies from Enemy Class

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
juliosuarez95
Prole
Posts: 2
Joined: Sun Jan 10, 2021 5:39 pm

Help Generating Enemies from Enemy Class

Post by juliosuarez95 »

Hey everyone. This is my first time posting anything on this forum. The long story short, I've taken on CS50 through Harvard and I've made it all the way to the final project. And here is where the problem arises:

I'm creating a sort of Galaga clone for my final project, and I've hit a major road block, I can't figure out to make multiple enemies spawn in my game. At the moment, I just want to be able to spawn a bunch of enemies just for the sake of seeing how it works. Eventually, I want to program it so that a certain amount of enemies spawn depending on what wave the player is on...

I'm not too sure exactly where or even how to implement an enemy table, I just know I have to...

Here's my main.lua file

Code: Select all

push = require 'push'

WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720

VIRTUAL_WIDTH = 1280
VIRTUAL_HEIGHT = 720

function love.load()
    Object = require "Classic"
    require 'player'
    require 'enemy'
    require 'bullet'

    player = Player()
    enemy = Enemy()
    listofBullets = {}


    love.graphics.setDefaultFilter('nearest', 'nearest')

    title = love.graphics.newFont('fonts/Stars Fighters.ttf', 45)
    start = love.graphics.newFont('fonts/robo.ttf', 30)
    menu = love.graphics.newFont('fonts/robo.ttf', 40)

    push:setupScreen(VIRTUAL_WIDTH, VIRTUAL_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT, {
        fullscreen = false,
        vsync = true,
        resize = true
    })

    love.window.setTitle('Titania')

    background = love.graphics.newImage('graphics/space.png')

    gameState = 'start'

    game_paused = false
end

function love.resize(w, h)
    push:resize(w, h)
end

function love.update(dt)
    if game_paused == false then
        player:update(dt)

        for i,v in ipairs(listofBullets) do
            v:update(dt)

            v:checkCollision(enemy)

            if v.dead then
                table.remove(listofBullets, i)
            end
        end
    end

    if gameState == 'play' then
        enemy:update(dt)
    end
end

function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end

    if key == 'enter' or key == 'return' and gameState == 'start' then
        gameState = 'menu'
    end

    if key == 's' and gameState == 'menu' then
        gameState = 'play'
    elseif key == 'h' and gameState == 'menu' then
        gameState = 'help'
    elseif key == 'e' or key == 'escape' and gameState == 'menu' then
        love.event.quit()
    end

    if key == 'backspace' and gameState == 'help' then
        gameState = 'menu'
    end

    if key == 'p' and gameState == 'play' then
        game_paused = true
        gameState = 'pause'
    elseif key == 'p' and gameState == 'pause' then
        game_paused = false
        gameState = 'play'
    end

    player:keypressed(key)
end

function love.draw()
    push:apply('start')

    -- supposed to keep the background scroling??????
    for i = 0, love.graphics.getWidth() / background:getWidth() do
        for j = 0, love.graphics.getHeight() / background:getHeight() do
            love.graphics.draw(background, i * background:getWidth(), j * background:getHeight())
        end
    end

    if gameState == 'start' then
        love.graphics.setFont(title)
        love.graphics.printf('Titania', 0, 30, VIRTUAL_WIDTH, 'center')
        love.graphics.setFont(start)
        love.graphics.printf("Press Enter", 0, 500, VIRTUAL_WIDTH, 'center')
    elseif gameState == 'menu' then
        love.graphics.setFont(title)
        love.graphics.printf('Titania', 0, 30, VIRTUAL_WIDTH, 'center')
        love.graphics.setFont(menu)
        love.graphics.printf("[S]TART    [H]ELP    [E]XIT", 0, 500, VIRTUAL_WIDTH, 'center')
    elseif gameState == 'help' then
        -- draw the player
        player:draw()
        love.graphics.setFont(title)
        love.graphics.printf('Titania', 0, 30, VIRTUAL_WIDTH, 'center')
        love.graphics.setFont(menu)
        love.graphics.printf("Use the ARROW keys or WASD to move and SPACE to shoot", 0, 500, VIRTUAL_WIDTH, 'center')
    elseif gameState == 'play' then
        -- draw the player
        player:draw()
        -- draw the enemy
        enemy:draw()
        -- text
        love.graphics.setFont(title)
        love.graphics.printf('TEST WAVE 1', 0, 30, VIRTUAL_WIDTH, 'center')
    elseif gameState == 'pause' then
        player:draw()
        enemy:draw()
        love.graphics.setFont(title)
        love.graphics.printf('PAUSE', 0, 250, VIRTUAL_WIDTH, 'center')
    end

    for i,v in ipairs(listofBullets) do
        v:draw()
    end

    push:apply('end')
end
and here's my Enemy.lua file:

Code: Select all

Enemy = Object:extend()

function Enemy:new()
    self.image = love.graphics.newImage('graphics/blueship.png')

    -- table function??? 
    enemy = {}

    --[[
        this will likely be changed from "timer" to "wave" when I find the 
        proper way to implement the idea
    ]]

    self.timer = 0
    self.timerLim = 2 --math.random(1, 3)
    self.amount = 4 --math.random(2, 5)
    self.side = math.random(1, 4)


    self.x = 300
    self.y = 20
    self.speed = 300
    self.width = self.image:getWidth()
    self.height = self.image:getHeight()
    self.xvel = 0
    self.yvel = 0
    self.friction = 10
    self.health = 1

end

function Enemy:update(dt)
    --self.x = self.x + self.speed * dt -- you commented this out to test stuff
    local window_width = love.graphics.getWidth()
    local window_height = love.graphics.getHeight()

    --[[
        this should assist with generating multiple enemies on the screen.
        hopefully we'll be able to make them "fly in" from the top of the screen...

        for now, they spawn from all over the place
    ]]
    self.timer = self.timer + dt
    if self.timer > self.timerLim then
        --spawn the enemy
        for i = 1,self.amount do
            if self.side == 1 then -- LEFT
                enemy:spawn(-self.height, window_height / 2 - (self.height / 2))
            end
            if self.side == 2 then -- TOP
                enemy:spawn(window_width / 2 - (self.width / 2), -self.height)
            end
            if self.side == 3 then -- RIGHT
                enemy:spawn(window_width, window_height / 2 - (self.height / 2))
            end
            if self.side == 4 then -- BOTTOM
                enemy:spawn(window_width / 2 - (self.width / 2), window_height)
            end
            self.side = math.random(1, 4)
        end
        self.timerLim = 2 --math.random(1, 3)
        self.timer = 0
        self.amount = 4 --math.random(2, 5)
    end
    --[[
        this whole setup here establishes the enemy tracking the player
        currently the enemy follows the player pretty aggressively
        we'll fix that later... 
    ]]
    for i,v in ipairs(enemy) do
        self.x = self.x + self.xvel * dt
        self.y = self.y + self.yvel * dt
        self.xvel = self.xvel * (1 - math.min(dt * self.friction, 1))
        self.yvel = self.yvel * (1 - math.min(dt * self.friction, 1))
    end

    for i,v in ipairs(enemy) do
        -- TRACK X AXIS
        if player.x + player.width / 2 < self.x + self.width / 2 then
            if self.xvel > -self.speed then
                self.xvel = self.xvel - self.speed * dt
            end
        elseif player.x + player.width / 2 > self.x + self.width / 2 then
            if self.xvel < self.speed then
                self.xvel = self.xvel + self.speed * dt
            end
        end
        -- TRACK THE Y AXIS
        if player.y + player.height / 2 < self.y + self.height / 2 then
            if self.yvel > -self.speed then
                self.yvel = self.yvel - self.speed * dt
            end
        elseif player.y + player.height / 2 > self.y + self.height / 2 then
            if self.yvel < self.speed then
                self.yvel = self.yvel + self.speed * dt
            end
        end
    end
end

function Enemy:spawn(x, y)
    table.insert(enemy, {x = x, y = y, self.xvel, self.yvel, self.health})
end

function Enemy:draw()
    for i,v in ipairs(enemy) do
        love.graphics.draw(self.image, self.x, self.y)
    end
end
Thanks for any help guys. I really appreciate it.
MrFariator
Party member
Posts: 548
Joined: Wed Oct 05, 2016 11:53 am

Re: Help Generating Enemies from Enemy Class

Post by MrFariator »

You'll need to define a table similar to the one that contains bullets.

Code: Select all

function love.load()
  listOfEnemies = {}
  -- ...rest
end  
Any time you want to spawn an enemy (when and where being up to you), you simply call the constructor function, and add the resulting object to the end of that table.

Code: Select all

table.insert(listOfEnemies, Enemy())
Then you loop through that list in similar manner to the bullets within love.update and love.draw.

On the topic of waves, you could add some additional parameters into your Enemy class constructor:

Code: Select all

function Enemy:new(x,y)
  -- ...
  self.x = x or 300 -- the 'or' here will make self.x become 300 if nothing is passed to the constructor, the value of x otherwise
  self.y = y or 20
  -- ...
end

Enemy(10,0) -- spawns an enemy at x:10, y:0
Enemy() -- spawns an enemy at x:300, y:20
This way you could implement your waves not within the Enemy class itself, but somewhere more manageable. Like maybe you have a global timer, and enemy waves are spawned whenever that timer hits a certain value. As a crude example:

Code: Select all

local waveTimer = 0
function love.update()
  waveTimer = waveTimer + 1
  if waveTimer == 300 then
    -- for this wave spawn ten enemies spaced ten pixels apart both horizontall and vertically
    for i = 1, 10 do
      table.insert(listOfEnemies, Enemy(10+i*10, 20-i*10))
    end
  end
end
juliosuarez95
Prole
Posts: 2
Joined: Sun Jan 10, 2021 5:39 pm

Re: Help Generating Enemies from Enemy Class

Post by juliosuarez95 »

So would table.insert(listofEnemies, Enemy()) go in my update function on main? Underneath Enemy:update(dt)?

I'm sorry If this is a dumb question. I'm still trying to wrap my head around coding as a whole.
MrFariator
Party member
Posts: 548
Joined: Wed Oct 05, 2016 11:53 am

Re: Help Generating Enemies from Enemy Class

Post by MrFariator »

You could put the table.insert line to love.load in main.lua to spawn an enemy as the game starts, to your love.update when certain conditions are met (like the timer example I provided), or somewhere else. It's up to you, really.

In general terms, you basically manage a collection of enemies (the listOfEnemies) table, add enemies to it over time (with table.insert, just as an example), update the contained enemy objects (just like how you do with your bullets), and delete them as they are destroyed (similar to how you are using table.remove with your listOfBullets).

I'm not sure what the course teaches you specifically, and how much they ask you to implement yourself, but my best advice would be to study how the bullet management is handled (I assume your player class spawns bullets when a button is pressed). Once you get a grasp on how the lines of code relating to bullets work, you should be able to make insert/update/remove code to handle enemies in similar manner.
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 2 guests