[Done] Recreating a "thrown item" movement

Started by MetalZelda, March 21, 2016, 12:11:45 PM

Previous topic - Next topic
March 21, 2016, 12:11:45 PM Last Edit: March 25, 2016, 08:57:42 AM by MetalZelda
Hello.

Edit:
For other users asking : Diarandor explain how to do it bellow.

This is the final script

Code (lua) Select
  local max_charging_time = 900 -- approximative time of the animation 'casting the fishing rod'
  local charging_time = max_charging_time - self.dowsed_distance -- Value is calculated is self:dowse()
  local distance = self.dowsed_distance -- same
  local max_distance = 230  -- Maximum distance of the movement.
  local duration = distance -- relative speed of the rod when casting it.
  local max_height = math.floor(-self.dowsed_distance / 5)
 
  local function f(t)
    return math.floor(4 * max_height * (t / duration - (t / duration) ^ 2))
  end
 
  local t = 0
  sol.timer.start(self, 10, function()
    fishing_rod:get_sprite():set_xy(0, -f(t))
    t = t + 10
    if t > duration then return false
      else return true
    end
  end)

Yes, it can be done using a parabolic movement, it's not that hard as it seems. I will explain later how I would do it with all detail (I am not with my computer now).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

March 21, 2016, 03:15:33 PM #2 Last Edit: March 21, 2016, 08:29:00 PM by Diarandor
Ok, this is gonna be long to explain. Let's begin! ;D

In first place, I would fix a max height for the movement. And since the gravity acceleration is applied to the vertical component of the movement, the time of the movement sequence should then be the same, independently of the reached distance. Hence, we can fix the values of the variables "duration" (in milliseconds) and "max_height" (in pixels).

I recommend to split the movement in two: a built-in straight movement, and a vertical component of the movement made as a shift on the sprite. The built-in movement cannot traverse obstacles, which is nice since the fishing rod would stop against walls, and the vertical movement (the shifting of the sprite) would not be affected by this.

But first, we need to get the reached distance, depending on the charging time. Define a variable "max_charging_time" with the maximum time of charge. Also, we need a variable "max_distance", you can fix its value (if the charged time is equal or greater than the max_charging_time, the distance will be the max_distance). We can compute the distance to the border of the map (in the direction of the hero) and store it in a variable "distance_to_border" (the reached distance cannot be greater than this). If "charged_time" is the time the player hold the button pressed, then the reached distance can be computed as:
Code (Lua) Select

local charged_time = math.min(charged_time, max_charging_time)
local reached_distance = math.min( (charged_time/max_charging_time)*max_distance,
  distance_to_border)

And the speed of the straight movement (in pixels per millisecond) is given by:
Code (Lua) Select

local speed = reached_distance / duration


The built-in straight movement should have a max distance property given by the variable "reached_distance" (so it will automatically stop).

I continue with the important details now...

EDIT: I removed the third parameter ("max_distance") from the math.min function above used to compute the reached_distance, since it was not really necessary because of the previous conditions.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

March 21, 2016, 03:32:21 PM #3 Last Edit: March 25, 2016, 02:41:15 AM by Diarandor
For the y-component (the shift of the sprite), we can use a degree 2 polynomial to have a parabolic movement.
It will be given by
f(t) = a*t^2 + b*t + c
(it is parametrized in time). The shift can be given using a timer called each millisecond and that should be stopped when we reach the time stored in the "duration" variable. Something like:
Code (Lua) Select

local t = 0
sol.timer.start(self, 1, function()
  sprite:set_xy(0, -f(t))
  t = t + 1
  if t > duration then return false
  else return true
  end
end)

So it remains to compute the polynomial. If we impose that it starts and ends from zero height (the ending will be when "t = duration"), and it reaches the max height in the middle point "t = duration/2". In other words, we have the conditions:
f(0) = 0, f(duration) = 0, f(duration/2) = max_height,
which give rise to a linear equation system with solution:
a = -4*max_height/(duration^2)
b = 4*max_height/duration
c= 0,
hence we get: f(t) = (-4*max_height/(duration^2))*t^2 + (4*max_height/duration)*t
or in other words:
Code (Lua) Select

local function f(t)
  return 4*max_height*(t/duration - (t/duration)^2)
end


EDIT: there was a mistake since the shift must be negative. I have changed the line "sprite:set_xy(0, f(t))" for the new one "sprite:set_xy(0, -f(t))".
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I hope this helps. Ask for any doubt you have, since I may have forgotten to explain some details or there might be some errors (I have not tested the code).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

You can use math.floor before returning the value of f(t), to have integer values, although this is maybe unnecessary if the engine allows non integer values (if it makes the conversion into integer value, but I am not sure of this).

I would also add a semi-transparent shadow sprite to the entity, to make it cooler.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Hmm, I am gonna try it, thanks  ;)

BTW, what is charged_time, max_charged_time ? I did understood everything but not this

Ok, I will explain it now (it probably was not clear at all).
(By the way, I edited a comment above, since one parameter was not necessary to compute the reached_distance.)

I was assuming that, the more time you hold the fishing rod button, the further the fishing rod will fall away (the reached distance is greater).

By charged_time I refer to the time the player holds the button before releasing it. The max_charged_time is a constant in milliseconds (it can be 2000 milliseconds for instance).

If the player holds the button more than the max_charged_time, the effect would be the same than if he holds the button exactly as the "max_charged_time" value (i.e., if charged_time > max_charged_time, the reached distance would be the same as if charged_time = max_charged_time).

In case charged_time > max_charged_time, I replaced the value charged_time with the value of max_charged_time, so that the quotient "charged_time / max_charged_time" is always between 0 and 1, and therefore the value of
(charged_time/max_charging_time)*max_distance
(which is used to compute the reached_distance) is always between 0 and max_distance. The reached distance is proportional to the time the player holds the button (with the obvious restrictions that restrict the max distance reached which are included above in the code).

Do not hesitate in asking more things if anything else is not clear, or if you need more help. In that case, you can post the script of your code here if needed.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

Maybe it is better to call the variables loaded_time and max_loading_time instead of charged_time and max_charging_time. My english is not very good so I maybe was not using the correct word making this unclear.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

I think I understand, now let's try to make the correct values  ;D

Okay, so the movement now works, it does what I wanted, thanks Diarandor  :D

Now, I need to make the wire synchrinised with the entity, any idea of how to do it ? Same algorythm ?


Good to see that it worked. ;D

Do you want to make the white wire to follow the thrown bait in th air? Yeah, a similar algorithm should do it.
I am not sure if using the parabolic movement for the wire is the best choice or not (because that would draw a fixed curve line, which does not move). Another posibility is to draw a straight line between the hero and the bait at each millisecond, but maybe the effect is not so good. Maybe a different trajectory could do it better, but first you need to know which behaviour you want to do.
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."

March 22, 2016, 04:39:55 PM #12 Last Edit: March 22, 2016, 05:44:20 PM by MetalZelda
Quote from: Diarandor on March 22, 2016, 04:00:09 PM
Good to see that it worked. ;D

Do you want to make the white wire to follow the thrown bait in th air? Yeah, a similar algorithm should do it.
I am not sure if using the parabolic movement for the wire is the best choice or not (because that would draw a fixed curve line, which does not move). Another posibility is to draw a straight line between the hero and the bait at each millisecond, but maybe the effect is not so good. Maybe a different trajectory could do it better, but first you need to know which behaviour you want to do.

Edit : I guess that I should scrap this idea, this is too much complex and require a lot of parameters
Plus, there is a lot of sprite to control (and timer to loop on) , and performance wise this is not good

Well I guess that I am going to scrap the wire for performance.

This is the actual state of the Fishing Rod, the camera movement works (relatively well, even if not finished)
https://www.youtube.com/watch?v=PwI8Nnh7lv4&feature=youtu.be

March 22, 2016, 08:35:28 PM #14 Last Edit: March 22, 2016, 08:40:25 PM by Diarandor
It is getting really cool.  :)
Do not forget to add a shadow sprite (with transperency if possible), and destroy it after the bait has fallen; that will give more realism to the sequence.

Edit: there was a crash issue in the development version of Solarus 1.5, when removing a sprite of a custom entity. It did not happen in the previous versions. (Christopho has fixed this a few days ago, but the current snapshots may have this problem, so be careful when you remove the shadow sprite).
"If you make people think they're thinking, they'll love you. But if you really make them think, they'll hate you."