The big problem with loops running at the same time, about destroying objects and asking about how loops work

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.
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

mygame.love
(14.79 MiB) Downloaded 272 times
Hi everyone, I have another problem in my code. I feel ashamed to constantly bother people with stupid questions like these. But hope that you will sympathize and help me. I really need your help. My problem is as follows:

It relates to the following files: main.lua, player.lua, frog.lua, damageobject.lua. The problem is quite complicated, I will try my best to explain it clearly. First, take a look at my code.

main.lua:

Code: Select all

function love.load()
    Object = require"classic"
    anim8 = require"anim8"
    
    wf = require'windfield'
    world = wf.newWorld(0, 0, true)
    world:setGravity(0, 512)
    
    world:addCollisionClass('Platform') 
    world:addCollisionClass('Frog')  
    world:addCollisionClass('DamageObject')
    world:addCollisionClass('Player')
    
    
    require"player"
    player = Player()
    
    require "wall"
    listOfWall = {}
    buildWall()
    
    require "frog"
    listOfFrog = {}
    table.insert(listOfFrog, Frog(350, 400, 3))
    table.insert(listOfFrog, Frog(450, 400, 3))
    table.insert(listOfFrog, Frog(400, 400, 3))
    table.insert(listOfFrog, Frog(445, 375, 3))
    table.insert(listOfFrog, Frog(425, 390, 3))
    
    table.insert(listOfFrog, Frog(350, 400, 3))
    table.insert(listOfFrog, Frog(450, 400, 3))
    table.insert(listOfFrog, Frog(400, 400, 3))
    table.insert(listOfFrog, Frog(445, 375, 3))
    table.insert(listOfFrog, Frog(425, 390, 3))
    
    require"enemyexplosion"
    listOfExplo = {}
    
    require"damageobject"
    listOfDamageObject = {}
    
    
    Camera = require'Camera'
    camera = Camera()
    camera:setFollowLerp(0.2)
    camera.scale = 2.5
    
end

function buildWall()
    for i=1, 11 do
        table.insert(listOfWall, Wall(200+i*48, 500))
    end
    table.insert(listOfWall, Wall(200-48, 500-48))
    table.insert(listOfWall, Wall(200-48, 500-48-48))
    table.insert(listOfWall, Wall(200-48-48, 500-48-48))
end

function love.update(dt)
    player:update(dt)
    world:update(dt)
    local xx, yy = player.collider:getLinearVelocity()
    -- camera effect made by myself
    if math.abs(yy) > 230 then
        if camera.scale > 1 then
            camera.scale = camera.scale - 0.05
        end
    else
        if camera.scale < 2.5 then
            camera.scale = camera.scale + 0.05
        end
    end
    --
    camera:update(dt)
    camera:follow(player.x, player.y)
    for i, v in ipairs(listOfFrog) do
        v:update(dt)
        
    end
    
    for exnum2, explonow2 in ipairs(listOfExplo) do
        explonow2:update(dt)
    end
    
    for DONum, DONow in ipairs(listOfDamageObject) do
        DONow:update(dt)
        if DONow.lifetime <= 0 then
        table.insert(listOfExplo, EnemyExplosion(DONow.collider:getX(), DONow.collider:getY(), "eneExplo", 35))
        DONow:boom()
        end
    end
    
end



function love.draw()
    camera:attach()
    
    player:draw()
    for i,v in ipairs(listOfWall) do
        v:draw()
    end
    for fronum,frogg in ipairs(listOfFrog) do
        frogg:draw()
        
    end
    
    for exnum, explonow in ipairs(listOfExplo) do
        explonow:draw()
    end
    
    for DONum2, DONow2 in ipairs(listOfDamageObject) do
        DONow2:draw()
    end
    world:draw()
    
    camera:detach()
    camera:draw()
end
player.lua:

Code: Select all

Player = Object:extend()

function Player:new()
    self.width = 32
    self.height = 32
    self.x = 300
    self.y = 300
    self.speedX = 0
    
    self.scaleX = 1
    self.jumpupState = false
    self.jumpdownState = false
    self.idleState = true
    self.runleftState = false
    self.runrightState = false
    self.hurtState = false
    self.hurtReload = 50
    self.ouchX = 25
    self.cherryReload = 20
    self.stompReload = 20
    
    self.sprite = love.graphics.newImage('foxsheet.png')
    self.grid = anim8.newGrid(self.width, self.height, 192, 192)
    self.animations = {}
    self.animations.idle = anim8.newAnimation(self.grid('1-4',5), 0.1)
    self.animations.run = anim8.newAnimation(self.grid('1-6',1), 0.075)
    self.animations.jumpup = anim8.newAnimation(self.grid(1,6), 0.35)
    self.animations.jumpdown = anim8.newAnimation(self.grid(2,6), 0.35)
    self.animations.crouch = anim8.newAnimation(self.grid('1-2',3), 0.1)
    self.animations.hurt = anim8.newAnimation(self.grid('1-2',4), 0.1)
    self.animations.climb = anim8.newAnimation(self.grid('1-3',2), 0.1)
    self.anim = self.animations.idle
    
    
    
    self.collider = world:newRectangleCollider(self.x, self.y, 15, 15)
    --self.collider = world:newCircleCollider(self.x, self.y, 10)
    self.collider:setCollisionClass('Player')
    self.collider:setObject(self)
    
    self.jumping = false
    self.dir = "right"
end

function Player:update(dt)
    self.anim:update(dt)
    
    if self.collider:enter('Platform') or self.collider:enter('Frog') then
        self.jumping = false
    end
    
    if self.collider:enter('Frog') then    
        local collision_data = self.collider:getEnterCollisionData('Frog')
        local enemy = collision_data.collider:getObject()
        if enemy.collider:getY() > self.collider:getY() then
            self.collider:applyLinearImpulse(0, -50)
            --print("ouch")
            enemy.health = enemy.health - 1
            
            
        else
            if self.hurtState == false then
                self.hurtState = true
                if self.collider:getX() < enemy.collider:getX() then
                    self.ouchX = -25
                else
                    self.ouchX = 25
                end
                self.collider:applyLinearImpulse(self.ouchX, -100)
            end
        end
        
    end
    
    if self.hurtState then
        self.hurtReload = self.hurtReload - 1
    end
    
    if self.hurtReload <= 0 then
        self.hurtState = false
        self.hurtReload = 50
    end
    
    if love.keyboard.isDown("up") and not self.jumping then
        self.collider:applyLinearImpulse(0, -100)
        self.jumping = true
    end
    
    x,y = self.collider:getLinearVelocity()
    
    if y>3 then
        self.jumpdownState = true
        self.jumpupState = false
    elseif y<-3 then
        self.jumpupState = true
        self.jumpdownState = false
    else
        self.jumpupState = false
        self.jumpdownState = false
    end
    
    if love.keyboard.isDown("right") then
        self.speedX = 7
        self.runrightState = true
        self.dir = "right"
    else
        self.runrightState = false
    end
    
    if love.keyboard.isDown("left") then
        self.speedX = -7
        self.runleftState = true
        self.dir = "left"
    else
        self.runleftState = false
    end
    
    if not love.keyboard.isDown("left") and not love.keyboard.isDown("right") then
        self.speedX = 0
        self.idleState = true
    else
        self.idleState = false
    end
    
    
    if (math.abs(self.collider:getLinearVelocity()) < 60) then
        self.collider:applyLinearImpulse(self.speedX, 0)
    end
    
    --animations here
    if self.hurtState then
        self.anim = self.animations.hurt
    else
    if self.jumpupState then
        self.anim = self.animations.jumpup
    elseif self.jumpdownState then
        self.anim = self.animations.jumpdown
    else
        if self.runrightState then
            self.anim = self.animations.run
            self.scaleX = 1
        elseif self.runleftState then
            self.anim = self.animations.run
            self.scaleX = -1
        else
            self.anim = self.animations.idle
        end
    end
    
    end
    
    self.x = self.collider:getX()
    self.y = self.collider:getY()
    self.cherryReload = self.cherryReload - 1
    self.stompReload = self.stompReload - 1
    if love.keyboard.isDown("space") and self.cherryReload <=0 then
        if self.dir == "right" then
            table.insert(listOfDamageObject, DamageObject(self.x, self.y, 20, 20, 7, "cherrybomb", 100, 50))
        else
            table.insert(listOfDamageObject, DamageObject(self.x, self.y, 20, 20, 7, "cherrybomb", 100, -50))
        end
        self.cherryReload = 20
    end
      
    if love.keyboard.isDown("down") and self.stompReload <= 0 then
        table.insert(listOfDamageObject, DamageObject(self.x-20, self.y, 40, 2, nil, "stomp", 30, 0))
        self.stompReload = 20
    end
    
end

function Player:draw()
    self.anim:draw(self.sprite, self.x, self.y, nil, self.scaleX, 1, 16, 16)
end
frog.lua:

Code: Select all

Frog = Object:extend()

function Frog:new(x, y, health)
    self.x = x
    self.y = x
    self.width = 40
    self.height = 40
    self.speedX = 50
    self.speedY = -100
    self.scaleX = 1
    self.moveto1 = x + 50
    self.moveto2 = x - 50
    self.phase1 = false
    self.phase2 = false
    self.dirX = -1
    self.dead = false
    
    self.sprite = love.graphics.newImage('frog.png')
    self.grid = anim8.newGrid(35, 32, self.sprite:getWidth(), self.sprite:getHeight())
    self.animations = {}
    self.animations.idle = anim8.newAnimation(self.grid('1-4', 1), 0.2)
    self.animations.jumpup = anim8.newAnimation(self.grid(6, 1), 0.2)
    self.animations.jumpdown = anim8.newAnimation(self.grid(7, 1), 0.2)
    self.anim = self.animations.idle
    
    self.idleState = true
    self.jumpupState = false
    self.jumpdownState = false
    
    self.collider = world:newRectangleCollider(self.x, self.y, 20, 18)
    --self.collider = world:newCircleCollider(self.x, self.y, 10)
    self.collider:setCollisionClass('Frog')
    self.collider:setObject(self)
    
    self.jumpingReload = 200
    self.health = health
end

function Frog:update(dt)
    self.anim:update(dt)
    
    
    
    if self.jumpingReload > 0 then
        self.jumpingReload = self.jumpingReload - 1
    end
    
    xVel,yVel = self.collider:getLinearVelocity()
    
    if yVel > 4 then
        self.jumpdownState = true
        self.jumpupState = false
        self.idle = false
    elseif yVel < -4 then
        self.jumpupState = true
        self.jumpdownState = false
        self.idle = false
    else
        self.jumpupState = false
        self.jumpdownState = false
        self.idle = true
    end
    
    
    --claim
    
    self.x = self.collider:getX()
    self.y = self.collider:getY()
    
    if self.x < self.moveto2 then
        self.phase1 = false
        self.phase2 = true
    elseif self.x > self.moveto1 then
        self.phase1 = true
        self.phase2 = false
    end
    if self.phase1 then
        self.scaleX = 1
        self.dirX = -1
    elseif self.phase2 then
        self.scaleX = -1
        self.dirX = 1
    end
    
    if self.x == moveto2 then
        self.phase1 = false
        self.phase2 = true
    elseif self.x == moveto1 then
        self.phase1 = true
        self.phase2 = false
    end
    
    if self.jumpingReload < 1 then
        self.randX = love.math.random(1, 3)
        self.randY = love.math.random(1, 3)
        if self.randX == 1 then
            self.speedX = 30
        elseif self.randX == 2 then
            self.speedX = 40
        else
            self.speedX = 50
        end
        
        if self.randY == 1 then
            self.speedY = -100
        elseif self.randY == 2 then
            self.speedY = -125
        else
            self.speedY = -150
        end
        self.collider:applyLinearImpulse(self.speedX * self.dirX, self.speedY)
        self.jumpingReload = 200
    end
    
    if self.jumpupState then
        self.anim = self.animations.jumpup
    elseif self.jumpdownState then
        self.anim = self.animations.jumpdown
    else
        self.anim = self.animations.idle
    end
    
    if self.health <= 0 then
            self:boom()
    end
end

function Frog:boom()
    for k, v in ipairs(listOfFrog) do
	      if v == self then 
            table.insert(listOfExplo, EnemyExplosion(self.collider:getX(), self.collider:getY(), "eneExplo", 35))
            self.collider:destroy()
            table.remove(listOfFrog, k) 
            break
        end
    end
    --self.collider:destroy()
    --listOfFrog:pullval(self)
end

function Frog:draw()
    self.anim:draw(self.sprite, self.x, self.y, nil, self.scaleX, 1, 16, 17.5)
end
damageobject.lua:

Code: Select all

DamageObject = Object:extend()

function DamageObject:new(x, y, width, height, radius, name, lifetime, bulletSpeedX)
    self.x = x
    self.y = y
    self.width = width
    self.height = height
    self.radius = radius
    self.name = name
    self.typemove = typemove
    self.lifetime = lifetime
    self.bulletSpeedX = bulletSpeedX
    
    if self.name == "cherrybomb" then
        self.sprite = love.graphics.newImage('cherrybomb.png')
        self.grid = anim8.newGrid(21, 21, self.sprite:getWidth(), self.sprite:getHeight())
        self.animation = anim8.newAnimation(self.grid('1-5', 1), 0.1)
        
        self.collider = world:newCircleCollider(self.x, self.y, self.radius)
        --self.collider = world:newRectangleCollider(self.x, self.y, self.width, self.height)
        self.collider:setCollisionClass('DamageObject')
        self.collider:setRestitution(0.8)
        self.collider:setObject(self)
        self.collider:applyLinearImpulse(self.bulletSpeedX, 70)
    end
    
    if self.name == "stomp" then
        self.sprite = love.graphics.newImage('stomp.png')
        self.grid = anim8.newGrid(40, 40, self.sprite:getWidth(), self.sprite:getHeight())
        self.animation = anim8.newAnimation(self.grid('1-8', 1), 0.1)
        
        --self.collider = world:newCircleCollider(self.x, self.y, self.radius)
        self.collider = world:newRectangleCollider(self.x, self.y, self.width, self.height)
        self.collider:setCollisionClass('DamageObject')
        self.collider:setObject(self)
    end
    
end

function DamageObject:update(dt)
    self.animation:update(dt)
    
    self.x = self.collider:getX()
    self.y = self.collider:getY()
    --self destroy (need to spit in a new function and make a real explosion)-finish
    
    if self.name == "cherrybomb" then
        if self.collider:enter('Frog') then
            local collision_data = self.collider:getEnterCollisionData('Frog')
            local enemy = collision_data.collider:getObject()
            print('yep')
            
            local xaxa = 0
            if enemy.collider:getX() < self.collider:getX() then
                xaxa = -100
            elseif enemy.collider:getX() > self.collider:getX() then
                xaxa = 100
            end
            
            enemy.collider:setLinearVelocity(xaxa, -100)
            enemy.health = enemy.health - 1
            table.insert(listOfExplo, EnemyExplosion(self.collider:getX(), self.collider:getY(), "eneExplo", 35))
            self:boom()
        end
    end
    if self.name == "stomp" then
        if self.collider:enter('Frog') then
            local collision_data = self.collider:getEnterCollisionData('Frog')
            local enemy = collision_data.collider:getObject()
            
            local xaxa = 0
            if enemy.collider:getX() < self.collider:getX() then
                xaxa = -100
            elseif enemy.collider:getX() > self.collider:getX() then
                xaxa = 100
            end
            
            enemy.collider:setLinearVelocity(xaxa, -100)
            table.insert(listOfExplo, EnemyExplosion(self.collider:getX(), self.collider:getY(), "eneExplo", 35))
            enemy.health = enemy.health - 1
        end
    end
    --
    self.lifetime = self.lifetime - 1
end

function DamageObject:boom()
    for dameObNum, ObjNow in ipairs(listOfDamageObject) do
        if ObjNow == self then 
            self.collider:destroy()
            table.remove(listOfDamageObject, dameObNum) 
                    
            break
        end
    end
end

function DamageObject:draw()
      self.animation:draw(self.sprite, self.x, self.y, nil, 1, 1, self.width/2, self.height/2)
end
I tested the game's bug by generating about 10 frogs to try to collide with objects including: player's feet, cherry bullets, stomp, ... When I tried to step on, shoot cherry bullets and stomp at the same time entering some frogs, I got the following error:

Code: Select all

Error: damageobject.lua:54: attempt to index local 'enemy' (a nil value)
stack traceback:
	[string "boot.lua"]:637: in function '__index'
	damageobject.lua:54: in function 'update'
	main.lua:86: in function 'update'
	[string "boot.lua"]:509: in function <[string "boot.lua"]:493>
	[C]: in function 'xpcall'
This error happens very often, and is flexible. It is not only in damageobject.lua 54, it is sometimes damageobject.lua 55 or the error also appears in frog.lua, main.lua. It says that object is nil. I thought it was because the frog was dead and destroyed but at the same time another object continued to collide and interact with it resulting in this error. I tried moving the object cancellation statements to the end of each update () function. But still not work. I did my best. Perhaps my methods of managing collisions and destruction are too poor. I was going to try turning all of the collision tests once into damageobject.lua. Like: if a frog collides with the dameobject (if it's a cherry, .. and if it's a stomp ...) but I think it's a stupid way too and not feasible. I am really desperate and hope everyone helps me. I need a cleaner and smarter code management. Or at least a concrete plan for me to get out of this mess. I would go crazy: ((.I sincerely thank you for taking the time to help me. Thank you very much T_T
This is my project:
Last edited by ilovelove2019 on Sat Oct 05, 2019 2:39 pm, edited 2 times in total.
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

I need some way to manage the collision and remove object correctly and clearly. I am willing to give up everything to start over. I really hope for the help of professional people. Hope everyone be open to help me. Thanks very much
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by pgimeno »

I don't think we can do much without code that we can run ourselves. Please check this thread: https://love2d.org/forums/viewtopic.php?f=4&t=2982 especially the 3rd bullet point.
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

OH. I will send the link soon. Wait me some minute. So sorry :))
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

When I upload the file to google drive, because it has the same name, some things will have "(1)", hope you try to fix some of the links in the code or the names of those files to match the code. Thank you so much! You so awesome!
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

Control by the arrow keys, shoot cherry = space, down button to stomp. First you be careful, the physics I tweak is quite slippery. Just about to fall, try to spam the button up. The error will appear when you try to jump on the frog's head, cherries and stomp at the same time. It may take a while to find this error. Extremely thank you for helping me
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by pgimeno »

The google link asks me for a login. Can't you just attach a .zip or a .love file to the forums?

Also, please look at point 7 here: https://love2d.org/forums/viewtopic.php?f=3&t=64151
ilovelove2019
Citizen
Posts: 78
Joined: Wed Sep 11, 2019 10:38 am

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by ilovelove2019 »

Hope everyone help me as soon as possible
Attachments
mygame.love
This is my game project. Hope you will help me solve the problem
(14.79 MiB) Downloaded 242 times
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: The big problem with loops running at the same time, about destroying objects and asking about how loops work

Post by pgimeno »

Well, it was tricky to get a reproducible failure, but I managed it by using a fixed dt, setting the random seed to a constant value, adding a frame counter, printing keypresses and then activating keys at certain frame counts. The one I got was in damageobject.lua in function DamageObject:update(), under self.name == "stomp", so I'll focus on that one

I don't know much about Windfield, so I don't know if this is a bug in Windfield. Maybe, maybe not.

What I found is the following. In main.lua, you're updating the frogs with this:

Code: Select all

    for i, v in ipairs(listOfFrog) do
        v:update(dt)
    end
which calls Frog:update for every frog. The frog update code has this:

Code: Select all

    if self.health <= 0 then
            self:boom()
    end
and in Frog:boom you call self.collider:destroy(). That happens all in the same call to Frog:update in main.

Now, later on, still in love.update, you do this:

Code: Select all

    for DONum, DONow in ipairs(listOfDamageObject) do
        DONow:update(dt)
    [...]
which calls DamageObject:update, which in turn has this:

Code: Select all

    if self.name == "stomp" then
        if self.collider:enter('Frog') then
            local collision_data = self.collider:getEnterCollisionData('Frog')
            local enemy = collision_data.collider:getObject()
When the collision is happening with the frog that was removed earlier, this sets enemy to nil, and the error is produced.

Why are you getting a collision with a frog whose collider you have destroyed, I don't know for certain. Maybe it's normal, maybe it's a bug in Windfield. In any case, guarding against a nil enemy would solve this.

The other crashes may or may not be manifestations of the same problem. It was hard enough to get this crash :)
Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests