Hello.
Edit:
For other users asking : Diarandor explain how to do it bellow.
This is the final script
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).
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:
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:
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.
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:
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:
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))".
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).
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.
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.
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.
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 ?
(http://i.imgur.com/CgLyBkG.gif)
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.
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
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).
Quote from: Diarandor on March 22, 2016, 08:35:28 PM
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).
Yes, removing a sprite crash the game, adding an animation with a clear animation works as well. I can't revert to solarus 1.4.5 because I am using the new camera in this.
The shadow has been added, as well as some other effects, I am working on a trail effect when the bait moves, to compensate a bit the lack of the wire
(http://i.imgur.com/fCSoQ6S.gif)
Other than that, thanks a lot Diarandor for the help :)
Here is a snapshot of today with the fix! http://www.solarus-games.org/downloads/solarus/win32/solarus-1.5.0-snapshot-20160322-win32.zip
Thanks Christopho for the snapshot !
@Metalzelda: I have been using the code to simulate the jump of a slime enemy and I found two small mistakes in the updated first post of this subject (one of them was mine).
1) I forgot to add a minus sign in the shift, in line: "sprite:set_xy(0, f(t))".
It should be "sprite:set_xy(0, -f(t))" instead.
2) Since you are drawing the shift each 10 milliseconds, the increment of the time variable "t" should be 10 too. So we will have:
local t = 0
sol.timer.start(self, 10, function() -- timer below 10 is useless, 10 ms is approximately 1 frame in-game
fishing_rod:get_sprite():set_xy(0, f(t))
t = t + 10 -- <---- THE PROBLEM WAS HERE.
if t > duration then return false
else return true
end
end)
Just change these two things in the first post, so other people will not get the wrong code. (I know you realized of all of this because your fishing rod works correctly in the videos.)
Okay, it has been modified :)
Good news: Christopho has released a new snapshot, and now getting the sprite of the hero is working correctly. Hence the sprite of the hero can be shifted with this "thrown movement" to program a custom jump (fully controlled, as in Link's Awakening). I have tested it and it's awesome. I will share the script when I finish improving it (I now use a secondary tunic for the jump, which is not good, but I will fix this someday soon).