[QUESTION] Enabling a dynamic tile while an entity is being destroyed, not after

Started by ffomega, December 22, 2016, 11:10:44 PM

Previous topic - Next topic
I am using a custom entity for the dash rock, which was pulled from Book of Mudora.  I have everything all set up and it's working perfectly for me.  I have created a dynamic tile using the large dirt patch you would find under large rocks in a Link to the past.  I tried using the event entity:on_removed(}, which the description in the API says this event is called when an entity is about to be destroyed.

However the dynamic tile does not appear until the 'destroy' animation completely finished.  I want this dynamic tile to appear During the destroy animation, as it would in a Link to the Past.  I wasn't able to get it to work.

Here is the Dash Rock custom entity (from Book of Mudora), changed just slightly to use my tileset's entities:

Code ( lua) Select
local entity = ...
local game = entity:get_game()

-- Stone pile: a pile of stones which
-- can only be blown apart by a bomb.

function entity:on_created()
  self:set_size(32, 32)
  self:set_origin(16, 29)
  self:set_traversable_by(false)
  self:add_collision_test("touching", function(self, other)
    ex, ey, el = entity:get_position()
    if other:get_type() == "explosion" then
      self:get_sprite():set_animation("destroy")
      sol.timer.start(self, 500, function() self:remove() end)
    elseif other:get_type() == "hero" and game:get_hero():get_state() == "running" then
      self:get_sprite():set_animation("destroy")
      sol.timer.start(self, 500, function() self:remove() end)
    end
  end)
end


Here is the lua script for the current map this entity is located (for the time being):

Code ( lua) Select
local map = ...
local game = map:get_game()

function dash_rock_1:on_removed()
   dash_rock_dirt_1:set_enabled(true)
end


As explained before, the dynamic tile for the dirt patch appears after the 'destroy' animation finishes, and not when it starts, like it should.

Maybe I am not trying hard enough to figure out the exact method needed here xD
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

That behavior is normal because you are removing the entity (and hence enabling the tile) half second after the "destroy" animation is started (you have a 500ms timer). You should just write the line
Code (Lua) Select
dash_rock_dirt_1:set_enabled(true)
immediately after the line
Code (Lua) Select
self:get_sprite():set_animation("destroy")
and remove the event "dash_rock_1:on_removed()".

More advices:
-To avoid repeating code unnecessarily, you should join both conditions in the same "if" block. Something like this may work:
Code (Lua) Select
if other:get_type() == "explosion"
  or ( other:get_type() == "hero" and game:get_hero():get_state() == "running" ) then
  --blah blah blah, cats cats cats,... put your code here :)
end

-I think it would be more natural to use a sprite attached to the custom entity, if possible, instead of the dynamic tile. But this would make your sprite to follow the dashing rock; I don't know if that is what you need (if not, then just keep your current code).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Well I am still learning how the code works.  This script was a direct copy from Book of Mudora.  The 'on_removed' callback was placed in the map script separate from the dash rock script entirely, and if I place the line of code to enable the dynamic tile within the dash rock script, it does not work that way. In fact altering the dash rock script in any way kind of breaks it.  The only way to make it work with the code written this way was to add scripts to the map
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

I don't know this specific code, but I can confirm that entity:on_removed() is called just before an entity is removed from the map (destroyed). If a sprite happens to have an animation named "destroy", such an animation name is a bit confusing, but there is no special rule. If you can see any animation, it means that the entity still exists on the map.

Maybe you can try to replace the event entity:on_removed() by sprite:on_animation_changed(animation), to detect when the destroy animation starts.

Oh that happen to me long time ago, on_animation_changed solved the thing

One workaround is (not tested but if you are starting let's give you some help)

http://www.solarus-games.org/doc/latest/lua_api_sprite.html#lua_api_sprite_on_animation_changed

Code (lua) Select
function entity:on_created()
  -- Store the sprite in a local variable, we want to remove this entity when a specific animation starts
  local sprite = self:get_sprite()
  self:set_size(32, 32)
  self:set_origin(16, 29)
  self:set_traversable_by(false)

  self:add_collision_test("touching", function(self, other)
    ex, ey, el = entity:get_position() -- Ehm, why ?

    if other:get_type() == "explosion" or (other:get_type() == "hero" and game:get_hero():get_state() == "running") then
      sprite:set_animation("destroy")
      -- The collision has been detected, we no longer need to check.
      self:clear_collision_tests()
    end
  end)

  function sprite:on_animation_changed(animation)
    if animation == "destroy" then
      -- Do your stuffs here
      entity:remove()
      -- Hint: Don't use self:remove() here, self = sprite
    end
  end
end


Yet, when removed, the sprite is removed as well, so the destroy animation will not be played ... The solution is to enable the dynamic tile in on_animation_changed() and remove the entity in on_animation_finished()

Code (lua) Select
function entity:on_created()
  -- Store the sprite in a local variable, we want to remove this entity when a specific animation starts
  local sprite = self:get_sprite()
  self:set_size(32, 32)
  self:set_origin(16, 29)
  self:set_traversable_by(false)

  self:add_collision_test("touching", function(self, other)
    ex, ey, el = entity:get_position() -- Ehm, why ?

    if other:get_type() == "explosion" or (other:get_type() == "hero" and game:get_hero():get_state() == "running") then
      sprite:set_animation("destroy")
      -- The collision has been detected, we no longer need to check.
      self:clear_collision_tests()
    end
  end)

  function sprite:on_animation_changed(animation)
    if animation == "destroy" then
      -- enable the entity here
     
    end
  end

  function sprite:on_animation_finished(animation)
    if animation == "destroy" then
      entity:remove()
    end
  end 
end


Simple yet it does the same thing, your entity is removed after an animation is played, and it enable an entity when the animation starts

And, if you use add_collision_test, use clear_collision_tests if the condition is met. It would avoid strange things.

Okay.  so I swapped out the code you wrote here, and when I did I started running into more problems.  I had the code working before I made any changes to it at all.  For starters, is all of this code to be placed within the custom entity script, or in the map script?  Because, for whatever reason, if I make ANY changes to the custom entity script, then it no longer works as intended.  It only works if it is left completely alone and no changes are made to it :(  The ONLY change I can make to the original code is removing the line containing 'ex, ey, el' and it will still detect collisions, and it will still properly be destroyed upon running into it.

This is the original (and works as intended)

Code ( lua) Select
local entity = ...
local game = entity:get_game()

function entity:on_created()
  self:set_size(32, 32)
  self:set_origin(16, 29)
  self:set_traversable_by(false)
  self:add_collision_test("touching", function(self, other)
    --ex, ey, el = entity:get_position()
    if other:get_type() == "explosion" then
      self:get_sprite():set_animation("destroy")
      sol.timer.start(self, 500, function() self:remove() end)
    elseif other:get_type() == "hero" and game:get_hero():get_state() == "running" then
      self:get_sprite():set_animation("destroy")
      sol.timer.start(self, 500, function() self:remove() end)
    end
  end)
end


This code is the custom entity script itself.  If I make any change to it whatsoever, it either stops working as a non-traversable entity that, upon being dashed into, is removed, or it is removed as intended, but does not remove the collision detection at all.  In either case, the dynamic tile still is not enabled upon "on_animation_changed()'  I even placed 'on_animation_changed(destroy) in the map script and it did not work there either, so either this entire code must be scrapped or I simply have not learned enough to alter it.  I had assumed that all custom entities were plug-and-play with a few minor tweaks here and there.  This has proven me wrong and I must own up to that xD
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/




That would be great :) I won't be home part of tomorrow and all day sunday.  I'll be back on monday though so if you get around to it while I'm gone please feel free to share your findings :)  In the meantime have a merry christmas or happy holiday depending on where in the world you live <3
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

Disclaimer: I make no promises that my code is any good - use at your own risk :)

It's perfectly fine though.  The only thing I had difficulty with was enabling a dynamic tile at the beginning of the 'destroy' animation sequence.  I tried to use the 'on_removed()' condition within the map script and it worked, but only after the 'destroy' animation for the entity was finished, when the entity was destroyed :)

Other than this the entity did exactly what I wanted it to do <3
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/

Nah that's fine wrightmat, I used your ressource as a base for my project, remember  :P

I should redownload the latest version of BoM and test that ...

I can confirm that MetalZelda's code works perfectly. Make sure that when you set up your custom entity on the map, you assign the sprite you'd like it to have there. Thanks MetalZelda!

Code (lua) Select

local entity = ...
local game = entity:get_game()

-- Stone pile: a pile of stones which
-- can only be blown apart by a bomb.

function entity:on_created()
  local sprite = self:get_sprite()
  self:set_size(32, 32)
  self:set_origin(16, 27)
  self:set_traversable_by(false)
 
  self:add_collision_test("touching", function(self, other)
    if other:get_type() == "explosion" or (other:get_type() == "hero" and game:get_hero():get_state() == "running") then
      sprite:set_animation("destroy")
      self:clear_collision_tests()
    end
  end)

  function sprite:on_animation_finished(animation)
    if animation == "destroy" then entity:remove() end
  end
end

Sorry guys, I just wasn't able to get the entity to work the way I intended it to work.  However I did find a workaround.

It's essentially using the same custom entity twice.  I create the entity once using the dash rock, and a second one using a solid ground tile that matches the color of the ground where the dash rock is to be placed.  The solid color entity is used to cover up the dash rock dirt, and when Link dashes into both entities they will both be destroyed at once.

It's not the most efficient way to make the entity work absolutely perfect, but this way does serve my purposes.
My tilesets page can be found here:
http://absolute-hyrule-tutorials.solarus-games.org/