Fixing direction and walking/stopped animations

Started by Diarandor, April 07, 2016, 02:56:49 AM

Previous topic - Next topic
April 07, 2016, 02:56:49 AM Last Edit: April 08, 2016, 04:38:25 PM by Diarandor
Hi, here is part of a script I made to (re)define some functions of the metatable of the hero. In this piece of code a few functions which allow fixing direction and walking and stopped animations are defined. The trick I am using was an idea that MetalZelda posted in an older topic (http://forum.solarus-games.org/index.php/topic,448.0.html). It was not possible until now that Christopho has improved solarus in the development version of 1.5.

My custom functions:
-The direction fix allows to walk sideways, which is useful for some weapons. The following functions are defined:
Code (Lua) Select

hero:set_fixed_direction(direction)
hero:get_fixed_direction()

-The fixing animation function allows to simplify some code in case you are changing the stopped/walking animations of the tunic of the hero for some purposes. In my case, I was changing to a jumping tunic (for a custom jump) and a carrying tunic (for a custom carry that allows carrying custom entities). This now can be done without changing the tunic sprite, using the functions:
Code (Lua) Select

hero:set_fixed_animations(stopped_animation, walking_animation)
hero:get_fixed_animations()


The code can be put in some script apart. Be careful since it may override some function or event of the hero metatable in case you have defined it elsewhere. The code is here:
Code (Lua) Select

local hero_meta = sol.main.get_metatable("hero")

-- Function to set a fixed direction for the hero (or nil to disable it).
function hero_meta:set_fixed_direction(direction)
  self.fixed_direction = direction
  if direction then
    self:get_sprite("tunic"):set_direction(direction)
  end
end
-- Function to get a fixed direction for the hero.
function hero_meta:get_fixed_direction()
  return self.fixed_direction
end
-- Function to set fixed stopped/walking animations for the hero (or nil to disable it).
function hero_meta:set_fixed_animations(stopped_animation, walking_animation)
  self.fixed_stopped_animation = stopped_animation
  self.fixed_walking_animation = walking_animation
end
-- Function to get fixed stopped/walking animations for the hero.
function hero_meta:get_fixed_animations()
  return self.fixed_stopped_animation, self.fixed_walking_animation
end

-- Initialize events to fix direction and animation for the tunic sprite of the hero.
-- To do it, we redefine the on_created and set_tunic_sprite_id events using the hero metatable.
do
  local function initialize_fixing_functions(hero)
    -- Define events for the tunic sprite.
    local sprite = hero:get_sprite("tunic")
    function sprite:on_animation_changed(animation)
      local fixed_stopped_animation = hero.fixed_stopped_animation
      local fixed_walking_animation = hero.fixed_walking_animation
      local tunic_animation = sprite:get_animation()
      if tunic_animation == "stopped" and fixed_stopped_animation ~= nil then
        if fixed_stopped_animation ~= tunic_animation then
          sprite:set_animation(fixed_stopped_animation)
        end
      elseif tunic_animation == "walking" and fixed_walking_animation ~= nil then
        if fixed_walking_animation ~= tunic_animation then
          sprite:set_animation(fixed_walking_animation)
        end
      end
    end
    function sprite:on_direction_changed(animation, direction)
      local fixed_direction = hero.fixed_direction
      local tunic_direction = sprite:get_direction()
      if fixed_direction ~= nil and fixed_direction ~= tunic_direction then
        sprite:set_direction(fixed_direction)
      end
    end
  end
  -- Initialize fixing functions when the hero is created.
  function hero_meta:on_created()
    initialize_fixing_functions(self)
  end
  -- Initialize fixing functions for the new sprite when the sprite is replaced for a new one.
  local old_set_tunic = hero_meta.set_tunic_sprite_id -- Redefine this function.
  function hero_meta:set_tunic_sprite_id(sprite_id)
    old_set_tunic(self, sprite_id)
    initialize_fixing_functions(self)
  end
end

"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 07, 2016, 01:58:58 PM #1 Last Edit: April 07, 2016, 02:18:32 PM by MetalZelda
This is cool, gonna try this out.
Thanks Diarandor

But I need some informations.

does set_fixed_animations(stopped_animation, walking_animation) replace the default stopped animation while the direction fix is active ?

April 07, 2016, 02:54:07 PM #2 Last Edit: April 07, 2016, 02:56:00 PM by Diarandor
Hi! They always replace the animations, unless you set them to nil value with the setter functions.

Be careful because I redefined hero.on_created and hero.set_tunic_sprite_id (to define the events on the tunic sprite when the hero is created, and also when the tunic sprite is replaced for a new one).

Edit: yes. The direction fix and the animation fix are independent one from the other.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

This is cool, it works perfectly on items.
The only issue I am having is the hero:unfreeze() issue that restore the hero to it's default "stopped" animation if I call this item and don't move

Mmmm... maybe the engine is not calling the event "tunic_sprite:on_animation_changed()" when the hero unfreezes, or something like that. Maybe you can change the animation again after calling "hero:unfreeze()", but I am not sure that I understand your problem...
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 07, 2016, 04:17:33 PM #5 Last Edit: April 07, 2016, 10:45:48 PM by MetalZelda
Quote from: Diarandor on April 07, 2016, 04:01:34 PM
Mmmm... maybe the engine is not calling the event "tunic_sprite:on_animation_changed()" when the hero unfreezes, or something like that. Maybe you can change the animation again after calling "hero:unfreeze()", but I am not sure that I understand your problem...

Edit : Oh I didn't see that I put the unfreeze() above the set_animation()

Okay, so I did some major changes on my item scripts, all of the animations are merged with the tunic sprite instead of using it's own sprite id. Turns out it allowed me to clean some stuffs in these script and apply the direction fix.
It works relatively well
https://www.youtube.com/watch?v=CVyGcq-jNy8&feature=youtu.be

April 08, 2016, 01:12:08 AM #6 Last Edit: April 08, 2016, 03:41:12 AM by Diarandor
It is great to see that this works so nice. I have improved my custom jump and custom carry items using the animation fix.

But I am still  having an issue with the animation fix in my custom jump script (which could be considered a bug). I guess that MetalZelda may be having the same issue with his item scripts.

During the custom jump the hero is unfrozen by my script, and when the jump is finished the animation fix is reseted (the variables of my script are set to nil), so the engine can use again the built-in animations: "stopped" and "walking". The "stopped" animation is then set automatically by the engine. But if the hero is already moving and has not stopped, the "walking" animation is not started, and the hero continues moving with the "stopped" animation. (When the movement is stopped and the hero starts moving again, the "walking" animation is then used by the engine and the problem disappears.) So this is a problem when the hero is moving and the engine sets the "stopped" animation instead of the "walking" one. I do not know if this has something to do with the function "item:set_finished()".

@Christopho: Should I open an issue on github for this problem?
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 08, 2016, 01:36:19 AM #7 Last Edit: April 08, 2016, 01:54:51 AM by MetalZelda
It is related to hero:unfreeze(), I've tested with the bow script since it is a menu (deleted the item:set_finished() from the script so it is never called for this test) and it is something related to the unfreeze function (for example, if I call hero:unfreeze() while falling, the hero fall a second time. but that might be easily fix-able)

Didn't completly read before edit, but I've never faced this issue to be honnest (the stopped anim while walking), I did a similar thing with the Roc Cape (controllable jump) in my project and never faced it, strange, but I don't think that set_finished() has something to do with it.
I just tested it in game with game:simulate_command_effect() and game:get_item():set_finished() and the animation was still played, even with unfreeze() (after the hero got freezed).

@MetalZelda: you are right, this has nothing to do with "item:set_finished()".

I made a test (and I think I found a way to reproduce something related to my problem): I made a sensor and changed the animation of the hero in the event "sensor:on_activated()". Then, what happens is that if the hero walks over the sensor, the new animation (which in my case has a loop, although this does not matter) is started, but it does not end until the hero stops walking (when he stops, the "stopped" animation is set again).

I think that the problem is that if we change the animation while walking, the built-in behavior does not start until the hero stops walking.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 08, 2016, 03:34:46 AM #9 Last Edit: April 08, 2016, 08:42:46 AM by Diarandor
Ok, I finally solved my problem. (And it would be better to keep the current behavior of the engine, so I will not open an issue.)

If I start a new animation while walking, that animation will not stop until the hero stops walking. Hence, after the jumping animation, what I did is just as easy (and silly) as setting the animation of the hero to the "walking" one. If the hero continues walking there is no problem since the animation is already the walking one; and if the hero suddenly stops, then the built-in behavior (with the stopped animation) is started again; so in any case there is no problem.

My personal recomendation: if an item does not freeze the hero (i.e., we unfreeze the hero immediately when starting to use the item) and changes the walking animation at the same time, then we must set the animation to the walking one when (or before) we finish using the item. (This was the case of my feather script, which does not freeze the hero and changes the walking animation to a different one.)

(If we do not set any animation and the hero keeps walking, the hero would keep the previous animation until he stops. And if we set the stopped animation when we finish using the item, in case the hero has not stopped walking, the "stopped" animation would be kept while walking until the hero stops. In both cases this is not good. So the only solution is to change the animation to the walking one when we have finished using the item.)

EDIT: I was wrong, there is still a problem. Setting the animation to the walking one only works if the hero was walking during the change of animation. But if the hero was not moving, then he would start the walking animation while he is stopped, and the built-in behavior will only start when the hero is moved...

@Christopho: How can I make the hero start the correct animation stopped/walking after the change of animation? Is there some way to know if the hero is walking or stopped without using the sprite animation?

EDIT2: I realized that there is an easy solution, using 2 equal animations jumping_stopped and jumping_walking, instead of only one, so I can know if I need the stopped or the walking animation. I will test it later. Sorry for the long posts. :(
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 08, 2016, 01:33:52 PM #10 Last Edit: April 08, 2016, 04:11:04 PM by Diarandor
There will be a cleaner solution for my problem when this issue is closed: https://github.com/christopho/solarus/issues/873
(it is a request of the function hero:is_walking()).

Edit: Christopho has already solved the issue (and hence my problem).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

April 08, 2016, 03:09:34 PM #11 Last Edit: April 08, 2016, 03:11:29 PM by MetalZelda
Isn't this related to hero:unfreeze() since unfreeze restore the hero to it's stopped animation (free state) ?

https://www.youtube.com/watch?v=PjJNSn3USVY&feature=youtu.be

hero:unfreeze() actually means "set the state to 'free' " so i am not sure if what you show in this video should be considered a bug. But maybe the function is poorly named, it should be named hero:start_free().

I point out that this direction fix only works correctly if the fixed animation has the same frame number than the original animation. Otherwise the frame position is reseted to the first frame each instant and the new animation would not be played since it would stay stuck at the first frame. But this can be solved just by saving in some variable the current frame position, and the remaining time of the current frame using a timer.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

June 08, 2016, 11:21:56 AM #14 Last Edit: June 08, 2016, 11:33:30 AM by MetalZelda
The only problem I faced with this script is that if I change the tunic id (let's say that I choose Goron Tunic and then switch back to the Green Tunic), the direction fix doesn't work anymore, the game needs to be reset.

Edit : It seems that hero_meta:set_tunic_sprite_id() is never called in my case since it just change variant of the tunic item, might investigate this


Fixed, just added a set_tunic_sprite_id in the equipment submenu when selecting a tunic, alongside with game:set_ability()