Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
enter_frame and sprites — Gideros Forum

enter_frame and sprites

QkyGamesQkyGames Member
edited September 2016 in General questions
Hi,

what is the best method, have one enter_frame and put all the enemies in a table and make a "for" or add to each enemie an enter_frame event and remove it when the enemie is destroyed?

the same question for bullets, players, bullet players, explosion, etc

sorry for my english.
Maxi.

Comments

  • Hey search for pooled sprites, there's a thread here somewhere covering them. That's the best kind of thing to use for game sprites. I'll have a look more later if you can't find it.

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • Here is a wiki page that gives an overview of pooled objects..
    https://en.wikipedia.org/wiki/Object_pool_pattern

    And here is a thread on the forums about pooled objects..
    http://giderosmobile.com/forum/discussion/4769/simple-item-pool-class/p1

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • SinisterSoftSinisterSoft Maintainer
    edited September 2016
    yes, keep events to a minimum - one enter_frame event for your game, loop through your sprites. Avoid destroying and re-creating objects. Reuse whatever you can. Split image animation, monster brain logic, new sprites appearing, etc over 4 frames - so you spread the work. Use locals as much as possible - including for often called functions.
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • SinisterSoftSinisterSoft Maintainer
    edited September 2016
    Another good trick is this:
    --instead of
    for loop=1,#monsters do
     monster[loop].x=monster[loop].x+1
     monster[loop].y=monster[loop].y+1
    .
    .
    end
     
    -- do this
    for loop=1,#monsters do
     local m=monsters[loop]
     m.x=m.x+1
     m.y=m.y+1
    .
    .
    end
    The m=monsters[loop] doesn't copy the data from monsters[loop] into a new table called m, it actually points m to the position in the monsters table pointed to by loop. That way each time you reference something like .x it won;'t have to search two tables to find that variable, just one table.
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • Better to use a pool. I'll make a small tutorial when I get time.
  • QkyGamesQkyGames Member
    edited September 2016
    Hi, thanks for reply, so I can make this?

    Pool class:
    Pool = Core.class(Sprite)
     
    function Pool:init()
        self.pool = {}
    end
     
    function Pool:createObject()
        local b
        --if there is anything in pool take it
        if #self.pool > 0 then
            b = table.remove(self.pool)
        else
            b = Enemie.new()
        end
        stage:addChild(b)
        return b
    end
     
    function Pool:destroyObject(b)
        b:removeFromParent()
        table.insert(#self.pool, b)
        b:destroy()
    end
    enemie Class:
    Enemie = Core.class(Enemie)
     
    function Enemie:init(self)
        self.health = 10
    end
     
    function Enemie:update()
     
    end
     
    function Enemie:destroy()
     
    end
    and now, how can call the update function for enemie? or how can create another object in createObject()?

    Regards.
    Maxi.
  • I am not sure I understood what you mean by how can call the update function for enemie?
    It all depends on what it does: when do you need to call it? if it's something that happens on every frame you usually call it with onEnterFrame event (as sinisterSoft said, better to have just one enterframe listener, where you call every enterframe function). If it's used to "reset" your enemie stats, you can call it when you destroy the object, or when you get it out from the pool.

    Pool is just a "storage" where you place your enemie(s) instances, instead of garbage collecting them, to reuse them later.
    You will need to reset anything that should be "new" in createObject - for example hitpoints.

    To create different objects you need to "prepare" your pool so it is able to decide which object it needs to create. You must also tell it which kind of object it is when you destroy it.
    Haven't tested it, but it should be something like this:
    Pool = Core.class(Sprite)
     
    function Pool:init()
        self.pool = {
    enemies = {},
    powerups = {}
    }
     
    end
     
    function Pool:createObject(what)
        local b
        --if there is anything in pool take it
            if #self.pool[what] > 0 then
                    b = table.remove(self.pool[what])
    --reset you object properties here, before you return the object or in your class destroy() function (init won't be executed again)
     
            else --you need to create a new instance of "what"
                if what == "enemies" then
                    b = Enemie.new()
                elseif what == "powerups" then
                    b = Powerup.new()
                end
            end
       -- stage:addChild(b)  I would get b out of createObject() and add it in my code instead of adding it directly to stage, so if I need to place the object in another sprite I won't have to change it here..
        return b
    end
     
    function Pool:destroyObject(b, what)
        b:removeFromParent()
        --table.insert(#self.pool[what], b) actually there is a faster way than table.insert to write the same thing in the next line
        self.pool[what][#self.pool[what]+1] = b
        b:destroy()
    end
    in destroyObject() you could avoid passing "what" as a parameter, if you are able to read it from b (ie. if b.type = "enemies").

    hope this helps :)

  • QkyGamesQkyGames Member
    edited September 2016
    Hi,

    thanks for you reply, every enemi needs to move or check if it collides with a bullet, so, I plan put this code in a function in Enemie class: example
    function:update()
       self:setX(self:getX() + 10)
       if self:getX() > 480)
          here need call Pool:destroyObject
       end
    end
    or for power ups for example
    if(time > time_life)
       here need call Pool:destroyObject
    the question is, add to enter_frame for each enemie and for each power up? or call the update funcion manually in a for cycle for example

    You understand me?

    sorry for my english.
    Maxi.
  • piepie Member
    edited September 2016
    You need an enterframe event to listen to in your "level" (if you use scenemanager I would say "scene") and you need a table to keep reference to your enemies and powerups.

    it should be something like this (not tested)
    actors = {} --create a reference table
    for i= 1, 5 do --fill it with enemies
     actors[i] = thepool:createObject("enemies")
     actors[i].id = i --save each object id as an object property if needed
    end
     
    function onEnterFrame() 
         for i,a in pairs(actors) do
                a:update()
         end
    end
    Then you need to remove destroyed objects from actors table (when it happens). if you use pairs to parse it, it should not be a problem if you remove a "middle actor" from the table;
    another option could be to save the id number of each object (powerup and enemie) and replace every removed object in actors table with something that tells your loop to bypass the value. Of course you will have to read each object id to replace the right "dead" object in your actors table.

    ie. object id 3 is destroyed, so actors[3] = "dead": in enterframe check if it's available or not.
    function onEnterFrame() 
         for i,a in pairs(actors) do
              if not a == "dead" then
                a:update()
              end
         end
    end
     
    --replace actors[3], assuming that you saved the object id as a property of your object
    local tmpActor = thepool:createObject("enemies")
    actors[tmpActor.id] = tmpActor
  • so, actors are for example live enemies and the thepool:enemies are "kill" enemies ready to use.

    I need make global thepool class to call function Pool:destroyObject(b, what) from enemie class?
    so in update from enemie class i can make thepool:destroyObject(self, "enemie") and need remove it from actors table?

    I need set nil actor tables and thepool class for example when change the scene or restart the level?
  • piepie Member
    edited September 2016
    Yes, actors is just a reference table to call each specific instance of enemie/powerup/anything else that needs to be accessed.

    you don't need to make thepool global if you create/destroy your "actors" in enterframe: for example you could just set a property inside your enemie class to determine what should enterframe do with each object

    ie:
    function Enemie:destroy()
         self.destroy = "enemies" -- I used "enemies" just to avoid another check to determine "what"
    end
    function onEnterFrame() 
         for i,a in pairs(actors) do
              if not a == "dead" then
                 if a.destroy then --just check if there is a destroy property in your object
                     actors[i] == "dead" --or actors[i] = nil to remove the entry from actors table.
                     thepool:destroyObject(a, a.destroy)
                 else
                    a:update()
                 end
             end
         end
    end
    Then in Pool:createObject() you need to remove destroy property if the object has been extracted from there (it's enough to add b.destroy = nil) .

  • QkyGamesQkyGames Member
    edited September 2016
    Perfect, now I understand
    if I use table.remove (), I do not need "if not a ==" dead "then" because using table.remove the object is deleted and the table is rearranged? what is best method use table.remove for actors table or set "dead" an actor and not call update if dead.

    other question:
    if I use actors = nil and thepool = nill I remove all memory tables?

    sorry for the questions storm and english, I appreciate your help.
  • if you use table.remove the indices are rearranged, so you won't know "who" you are accessing in enterframe: (enemie.id would not be consistent anymore with actors table - but depending on your code this could not be an issue)

    If you just nil the value of actor[i] it's almost the same (as long as you use pairs to parse actors - pairs doesn't bother about consecutive indices) but it should be faster since it doesn't need to rearrange the table.
    In this scenario you don't need to set each "dead" object, but you will need to keep track on how many enemies/powerups you have already spawned to avoid overwriting existing ones (you need to provide unique id for each object, or you risk that some object is not updated by enterframe).

    Be aware that lenght operator # works until it finds a "hole" between indices, so you need a variable to store "lastIdNumber".

    what is best method use table.remove for actors table or set "dead" an actor and not call update if dead.

    This really depends on your game: if you have a limited number of enemies that does not respawn/ respawn until they are n on screen you could go for the first option, the other is good if your enemies number may vary in time, but it's a bit more "complex" to set up because you need to have unique ids (as said before).



    if I use actors = nil and thepool = nill I remove all memory tables?
    Yes, as soon as garbage collector does its job

  • QkyGamesQkyGames Member
    edited September 2016
    I make an example with all your feedback, can you check it please?

    I am using main, pool and enemie class. I can use this method to create dozens of bullets for example if my car shoots? I will create a new table like enemies in main for example for bullets or power ups etc to check collisions for example.
    zip
    zip
    Gideros Race.zip
    231K
  • I code completely games completely differently than normal programs, for games I tend to code a little more 'low level', avoiding class generation and try to keep things simple.

    Here is a link to what I'm working on now btw as a side project to test out new Gidero features... :)
    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
  • you check my example code? I use class becose is more easy to read the code and more organized for my and easy, have all the code in one .lua result a tons line of code.

    wow Bacteria 2 look awesome and the performance is really good.

    Likes: SinisterSoft

    +1 -1 (+1 / -0 )Share on Facebook
  • I looked at it, do you have any more specific question?
    You can use the same method to create dozens of bullets, why not? :)

    however, if I understood how the game would be, I would leave the car in the center of the screen, able to move only on the y axis to avoid obstacles and move the background on the x.
  • QkyGamesQkyGames Member
    edited September 2016
    perfect, so my code is correct?

    I need change the code, becose #enemies was always 0
    function crearenemigo()
    	local enemigo = thepool:createObject("enemies")
    	stage:addChild(enemigo)
    	enemigo:start()
    	if enemigo.id == -1 then
    		nenemies = nenemies + 1
    		enemigo.id = nenemies
    	end
    	enemies[enemigo.id] = enemigo
    end
    however, if I understood how the game would be, I would leave the car in the center of the screen, able to move only on the y axis to avoid obstacles and move the background on the x.
    yes, a simple game to start :)
  • I'm thinking that with a couple of extensions to the API that the particle sprite could be great for bullets, etc. I've spoken to @hgy29 about this and hopefully they may be added.

    Likes: pie

    Coder, video game industry veteran (since the '80s, ❤'s assembler), arrested - never convicted hacker (in the '90s), dad of five, he/him (if that even matters!).
    https://deluxepixel.com
    +1 -1 (+1 / -0 )Share on Facebook
  • it works, so it's correct :D

    Maybe you could insert everything in a scene using scenemanager, so you can easily add a menu and "reset" your level as needed. Right now everything is on stage, which is not "wrong", but since you are using classes I think it would help you later in the development.
  • yes, I will use scenemanager, thanks for all your helps guys, a great community here! :)

    Likes: totebo, antix

    +1 -1 (+2 / -0 )Share on Facebook
Sign In or Register to comment.