solarus-games / solarus

This repository was moved to GitLab: https://gitlab.com/solarus-games/solarus
http://www.solarus-games.org
Other
710 stars 133 forks source link

Lua API: Allow to lift/carry/throw custom entities #969

Open Diarandor opened 8 years ago

Diarandor commented 8 years ago

To allow to lift/carry/throw custom entities, or more in general any kind of entity, I propose the following Lua API events/functions.

It would also be nice to allow enemies and NPCs (and maybe any entity?) to lift other entities. The events should be built-in events that can be overriden if they are coded in Lua (in that case, we can decide if the next event of the sequence is called or not).

function hero_or_entity:lift(entity_to_lift) -- Function to make an entity lift other entity.
event entity:on_lifted(carrier) -- Event called when this entity is being lifted by the carrier entity.
event entity:on_carried(carrier) -- Event called when this entity is being carried by the carrier entity.
event entity:on_thrown(carrier) -- Event called when this entity is being thrown by the carrier entity.
event entity:on_fallen(carrier) -- Event called when this entity has fallen to the ground.
function entity:set_thrown_properties(properties) -- Override default thrown properties.
function entity:get_thrown_properties() -- Get properties for the trajectory.

The most realistic option for the throw (and lifting?) is a parabolic trajectory, which can be given parametrized in time. I can help with these trajectories.

Parabolas are determined by three points (or three independent conditions that determine their parameters). The parabola will be applied to the y-coordinate only, to simulate the height, and this could be combined with a straight (linear) trajectory on both coordinates (x, y) to simulate the movement. The initial height (custom), final height (= 0) and the point where the max_height is reached can be used to determine an explicit expression for the parabola. (There are infinite choices for the point where the max_height happens, so I will use one that fits fine.) Maybe we should make the parabolic trajectory of the y-component as a shift of the sprites of the entity to separate it from the other movement?

The straight movement can be determined by two of the following parameters: "speed", "duration" and "max_distance". So tell me which of them you prefer to use so I can give you the parabolic trajectory given as function of them. The properties list for the thrown trajectory could be the following (some elements could be lists of properties inside the properties list):

There are more custom properties to add: -For instance, for the carrying state we can choose how the "bounces" are done with a boolean list that indicates in which frames of the carrier walking animation correspond to higher position and which ones to lower position. -When the entity is thrown, we should allow to choose if we hurt or not enemies with the thrown entity. -By default, after the last bounce, the on_fallen event is called, which by default will destroy the entity (as in destructibles), but it can be overriden to make other behaviors (if it is set to nil then the entity is not removed). -Maybe more things?

Diarandor commented 8 years ago

More functions that will be useful:

hero_or_entity:get_carrying_entity() -- This should be nil if it is not carrying anything.
entity:get_carrier_entity() -- This will be nil if the entity is not being carried.

Also, a "thrown" animation could be started by default on the hero sprite whenever it is defined (I know Link does not have this sprite, but Eldran has one and we could use it).

Diarandor commented 8 years ago

More details: if the thrown entity falls on bad ground it will be destroyed by default with some animation/sound effects. To override/customize this we could allow to define an optional event: entity:on_fallen_on_bad_ground(ground_type) Some entities may not fall, for instance if they can fly or swim, etc. (This event should be called on any of the possible bounces if it happens on bad ground.)

Diarandor commented 8 years ago

I think the throw trajectory should be given by a different function than for the bounces, because for the bounces the max height is reached in the middle point of the parabola, but that is not the case for the first falling trajectory when the hero throws it. For the bounces, the parabola can be given as function of the time by:

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

but this has to be translated into C++. I will compute the first parabolic trajectory and test it in Lua other day.

Diarandor commented 8 years ago

More useful things: The function I use now in my Lua script to compute the heights (as shifts of the sprite) is the following:

-- Function to compute height for each fall (bounce).
local function current_height()
  if current_bounce == 1 then return h * ((t / dur) ^ 2 - 1) end
  return 4 * h * ((t / dur) ^ 2 - t / dur)
end 

The first fall uses a different function because the initial height is from above the hero. The parameters are the max height of the current bounce "h", the current instant of the current bounce in milliseconds "t", the current bounce "current_bounce", the duration in milliseconds of the current bounce "dur".

The initial parameters for customization could be given in a list of properties (some parameters may be lists) similar to the following ones I use:

entity.sprite_shift = 0 -- To customize the shift of the sprite or position (for the height).
entity.num_bounces = 3 -- Number of bounces when falling (it can be 0).
entity.bounce_distances = {80, 16, 4} -- Distances for each bounce.
entity.bounce_heights = {"same", 4, 2} -- Heights for each bounce ("same" on first parameter means the initial height).
entity.bounce_durations = {400, 160, 70} -- Durations for each bounce.

The values above (or other ones chosen by you) could be used as default ones. I have tested the height function above in Lua and they work fine, so you can translate it into C++ to mimic the same.

Diarandor commented 6 years ago

We should have functions to shift the carried entity and center it correctly over the hero (or the corresponding carrier entity):

entity:set_carried_xy(x, y)
entity:get_carried_xy()

this can be used to code the custom "bouncing" effect when an entity is being carried and the carrier is walking, all directly from the scripts.