Changing animations when key is pressed and on collision with the ground and platform

Good day, Defold forum so i have a game character with four flipbook animations for now “Dave Run”, “frontflip”, “Jump” and “fallanim” and I have been trying to make the character transition from the default running animation to the frontflip animation when space is pressed then to the jump animation if space is pressed afterwards since the game requires continuous tapping then if the character is still in the air I wanted the character to transition to the fall animation then on collision with the ground or any other platform the character should immediately transition back to the run animation but it isn’t working I ended up with the character changing through these four animations when space is pressed how do I do this? here is my code:

       `-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20

-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900

function init(self)
	-- this tells the engine to send input to on_input() in this script
	msg.post(".", "acquire_input_focus")

	-- save the starting position
	self.position = go.get_position()

	-- keep track of movement vector and if there is ground contact
	self.velocity = vmath.vector3(0, 0, 0)
	self.ground_contact = false
end

function final(self)
	-- Return input focus when the object is deleted
	msg.post(".", "release_input_focus")
end
local function play_animation(self, anim)
	-- only play animations which are not already playing
	if self.anim ~= anim then
		-- remember which animation is playing
		self.anim = anim
	end
end

local function update_animation(self)
	-- make sure the right animation is playing
	if self.ground_contact then
		msg.post("#sprite", "play_animation", {id = hash("Dave Run") })
	else
		if self.velocity.y > 0 then
			msg.post("#sprite", "play_animation", {id = hash("frontflip") })
		else
			msg.post("#sprite", "play_animation", {id = hash("fallanim") })
		end
	end
end
function update(self, dt)
	local gravity = vmath.vector3(0, gravity, 0)

	if not self.ground_contact then
		-- Apply gravity if there's no ground contact
		self.velocity = self.velocity + gravity
	end

	-- apply velocity to the player character
	go.set_position(go.get_position() + self.velocity * dt)

	-- reset volatile state
	self.correction = vmath.vector3()
	self.ground_contact = false
end

local function handle_geometry_contact(self, normal, distance)
	-- project the correction vector onto the contact normal
	-- (the correction vector is the 0-vector for the first contact point)
	local proj = vmath.dot(self.correction, normal)
	-- calculate the compensation we need to make for this contact point
	local comp = (distance - proj) * normal
	-- add it to the correction vector
	self.correction = self.correction + comp
	-- apply the compensation to the player character
	go.set_position(go.get_position() + comp)
	-- check if the normal points enough up to consider the player standing on the ground
	-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
	if normal.y > 0.7 then
		self.ground_contact = true
	end
	-- project the velocity onto the normal
	proj = vmath.dot(self.velocity, normal)
	-- if the projection is negative, it means that some of the velocity points towards the contact point
	if proj < 0 then
		-- remove that component in that case
		self.velocity = self.velocity - proj * normal
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		-- check if we received a contact point message. One message for each contact point
		if message.group == hash("geometry") then
			handle_geometry_contact(self, message.normal, message.distance)
		end
	end
end

local function jump(self)
	-- only allow jump from ground
	if self.ground_contact then
		-- set take-off speed
		self.velocity.y = jump_takeoff_speed
	end
end

local function abort_jump(self)
	-- cut the jump short if we are still going up
	if self.velocity.y > 0 then
		-- scale down the upwards speed
		self.velocity.y = self.velocity.y * 0.5
	end
end

function on_input(self, action_id, action)
	if action_id == hash("jump") or action_id == hash("touch") then
		if action.pressed then
			update_animation(self)
			jump(self)
		elseif action.released then
			update_animation(self)
			if self.ground_contact then
				msg.post("#sprite", "play_animation", {id = hash("Dave Run") })
			msg.post("#sprite", "play_animation", {id = hash("fallanim") })
			abort_jump(self)
			end
		end
	end
end`

any ideas

You need to keep track of which animation you are currently playing, like you do in play_animation(), although you don’t seem to call that function from your code. You should probably use that function from update_animations().

1 Like

i thought i called play animation() with this local function play_animation(self, anim) -- only play animations which are not already playing if self.anim ~= anim then -- remember which animation is playing self.anim = anim end end

In the code you posted above I don’t see that function or self.anim being used anywhere. As britzl suggested it may be useful.

As for double jumping , I would track the state for “jump” presses with maybe a variable. First press frontflip , second press jump , then on ground collision reset the variable.
On “jump” input change animation state etc… according to this jump state variable.

1 Like

its still not working here is what my code looks like now
`-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20

-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900

function init(self)
	-- this tells the engine to send input to on_input() in this script
	msg.post(".", "acquire_input_focus")

	-- save the starting position
	self.position = go.get_position()

	-- keep track of movement vector and if there is ground contact
	self.velocity = vmath.vector3(0, 0, 0)
	self.ground_contact = false
	self.anim = self.anim
end

function final(self)
	-- Return input focus when the object is deleted
	msg.post(".", "release_input_focus")
end

local function update_animation(self)
	-- only play animations which are not already playing
	if self.anim ~= anim then
		-- remember which animation is playing
		self.anim = anim
	-- make sure the right animation is playing
	if self.ground_contact then
		msg.post("#sprite", "play_animation", {id = hash("Dave Run") })
	else
		if self.velocity.y > 0 and self.anim == self.anim then
			msg.post("#sprite", "play_animation", {id = hash("fallanim") }) 
		else
			msg.post("#sprite", "play_animation", {id = hash("Dave Run") })
			end
		end
	end
end
function update(self, dt)
	local gravity = vmath.vector3(0, gravity, 0)

	if not self.ground_contact then
		-- Apply gravity if there's no ground contact
		self.velocity = self.velocity + gravity
	end

	-- apply velocity to the player character
	go.set_position(go.get_position() + self.velocity * dt)

	-- reset volatile state
	self.correction = vmath.vector3()
	self.ground_contact = false
end

local function handle_geometry_contact(self, normal, distance)
	-- project the correction vector onto the contact normal
	-- (the correction vector is the 0-vector for the first contact point)
	local proj = vmath.dot(self.correction, normal)
	-- calculate the compensation we need to make for this contact point
	local comp = (distance - proj) * normal
	-- add it to the correction vector
	self.correction = self.correction + comp
	-- apply the compensation to the player character
	go.set_position(go.get_position() + comp)
	-- check if the normal points enough up to consider the player standing on the ground
	-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
	if normal.y > 0.7 then
		self.ground_contact = true
	end
	-- project the velocity onto the normal
	proj = vmath.dot(self.velocity, normal)
	-- if the projection is negative, it means that some of the velocity points towards the contact point
	if proj < 0 then
		-- remove that component in that case
		self.velocity = self.velocity - proj * normal
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		-- check if we received a contact point message. One message for each contact point
		self.ground_contact = true
		if message.group == hash("geometry") then
			handle_geometry_contact(self, message.normal, message.distance)
		end
	end
end

local function jump(self)
	-- only allow jump from ground
	if self.ground_contact then
		-- set take-off speed
		self.velocity.y = jump_takeoff_speed
	end
end

local function abort_jump(self)
	-- cut the jump short if we are still going up
	if self.velocity.y > 0 then
		-- scale down the upwards speed
		self.velocity.y = self.velocity.y * 0.5
	end
end

function on_input(self, action_id, action)
	if action_id == hash("jump") or action_id == hash("touch") then
		if action.pressed then
			update_animation(self)
			jump(self)
		elseif action.released then
			update_animation(self)
			abort_jump(self)
			end
		end
	end`

You are right i declared the update animation function then called it in the function update(self, dt) but the new problem is the animations are changing as they should but they are all frozen it switches from the run animation to the jump animation then to the fall animation but the animations themselves arent playing just one frozen frame like switching still images here is my code:
– gravity pulling the player down in pixel units/sˆ2
local gravity = -20

-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900

function init(self)
	-- this tells the engine to send input to on_input() in this script
	msg.post(".", "acquire_input_focus")

	-- save the starting position
	self.position = go.get_position()

	-- keep track of movement vector and if there is ground contact
	self.velocity = vmath.vector3(0, 0, 0)
	self.ground_contact = false
end

function final(self)
	-- Return input focus when the object is deleted
	msg.post(".", "release_input_focus")
end

local function play_animation (self, anim)
	--only play animations which are not already playing
	if self.anim ~= anim then
		--tell the character to play the running animation
		msg.post("#sprite", "play_animation",  {id = hash("Dave Run")})
		--remember which animation is playing
		self.anim = self.anim
	end
end
local function update_animation (self)
	--make sure the right animation is playing
	if self.ground_contact then
		msg.post("#sprite", "play_animation",  {id = hash("Dave Run")})
	else
		if self.velocity.y > 0 then
			msg.post("#sprite", "play_animation", {id = hash("frontflip")})
		else
			msg.post("#sprite", "play_animation",  {id = hash("fall anim") })
		end
	end
end
function update(self, dt)
	local gravity = vmath.vector3(0, gravity, 0)

	if not self.ground_contact then
		-- Apply gravity if there's no ground contact
		self.velocity = self.velocity + gravity
	end

	-- apply velocity to the player character
	go.set_position(go.get_position() + self.velocity * dt)
	
	update_animation(self)

	-- reset volatile state
	self.correction = vmath.vector3()
	self.ground_contact = false
end

local function handle_geometry_contact(self, normal, distance)
	-- project the correction vector onto the contact normal
	-- (the correction vector is the 0-vector for the first contact point)
	local proj = vmath.dot(self.correction, normal)
	-- calculate the compensation we need to make for this contact point
	local comp = (distance - proj) * normal
	-- add it to the correction vector
	self.correction = self.correction + comp
	-- apply the compensation to the player character
	go.set_position(go.get_position() + comp)
	-- check if the normal points enough up to consider the player standing on the ground
	-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
	if normal.y > 0.7 then
		self.ground_contact = true
	end
	-- project the velocity onto the normal
	proj = vmath.dot(self.velocity, normal)
	-- if the projection is negative, it means that some of the velocity points towards the contact point
	if proj < 0 then
		-- remove that component in that case
		self.velocity = self.velocity - proj * normal
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		-- check if we received a contact point message. One message for each contact point
		if message.group == hash("geometry") then
			handle_geometry_contact(self, message.normal, message.distance)
		end
	end
end

local function jump(self)
	-- only allow jump from ground
	if self.ground_contact then
		-- set take-off speed
		self.velocity.y = jump_takeoff_speed
	end
end

local function abort_jump(self)
	-- cut the jump short if we are still going up
	if self.velocity.y > 0 then
		-- scale down the upwards speed
		self.velocity.y = self.velocity.y * 0.5
	end
end

function on_input(self, action_id, action)
	if action_id == hash("jump") or action_id == hash("touch") then
		if action.pressed then
			jump(self)
		elseif action.released then
			abort_jump(self)
		end
	end
end

I think you are confusing these two. They are not the same thing. The first is a message that is sent to a sprite component to play an animation, the second is a function (which you never call).

I would recommend that you check the platformer template project, available from the welcome screen of the editor? It handles different animation states etc. Here’s the player script from that template:

There’s even a suggestion on how to implement double jump:

4 Likes

oh yes I see what you are talking about now I declared play_animation but never called it in the update animations my trouble is still fully understanding how local functions work and this script was really helpful after some hours of me going crazy with this i finally did it and it works here is the code now
– gravity pulling the player down in pixel units/sˆ2
local gravity = -20

-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900
-- prehashing improves performance
local anim_run = hash("run")
local anim_fall = hash("fall")
local anim_frontflip = hash("frontflip")

function init(self)
	-- this tells the engine to send input to on_input() in this script
	msg.post(".", "acquire_input_focus")

	-- save the starting position
	self.position = go.get_position()

	-- keep track of movement vector and if there is ground contact
	self.velocity = vmath.vector3(0, 0, 0)
	self.ground_contact = false
	self.anim = nil
	
end

function final(self)
	-- Return input focus when the object is deleted
	msg.post(".", "release_input_focus")
end
local function play_animation (self, anim)
	--only play animations which are not already playing
	if self.anim ~= anim then
		sprite.play_flipbook("#sprite", anim)
		--remember which animation is playing
		self.anim = anim
	end
end
local function update_animations(self)
	if self.ground_contact then
		play_animation(self, anim_run)
else
	if self.velocity.y > 0 then
		play_animation(self, anim_frontflip)
	else
		play_animation(self, anim_fall)
		end
	end
end
function update(self, dt)
	local gravity = vmath.vector3(0, gravity, 0)

	if not self.ground_contact then
		-- Apply gravity if there's no ground contact
		self.velocity = self.velocity + gravity
	end

	-- apply velocity to the player character
	go.set_position(go.get_position() + self.velocity * dt)

	-- called update_animation(self) to change player animations with state
	update_animations(self)

	-- reset volatile state
	self.correction = vmath.vector3()
	self.ground_contact = false
end

local function handle_geometry_contact(self, normal, distance)
	-- project the correction vector onto the contact normal
	-- (the correction vector is the 0-vector for the first contact point)
	local proj = vmath.dot(self.correction, normal)
	-- calculate the compensation we need to make for this contact point
	local comp = (distance - proj) * normal
	-- add it to the correction vector
	self.correction = self.correction + comp
	-- apply the compensation to the player character
	go.set_position(go.get_position() + comp)
	-- check if the normal points enough up to consider the player standing on the ground
	-- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
	if normal.y > 0.7 then
		self.ground_contact = true
	end
	-- project the velocity onto the normal
	proj = vmath.dot(self.velocity, normal)
	-- if the projection is negative, it means that some of the velocity points towards the contact point
	if proj < 0 then
		-- remove that component in that case
		self.velocity = self.velocity - proj * normal
	end
end

function on_message(self, message_id, message, sender)
	if message_id == hash("contact_point_response") then
		-- check if we received a contact point message. One message for each contact point
		if message.group == hash("geometry") then
			handle_geometry_contact(self, message.normal, message.distance)
		end
	end
end

local function jump(self)
	-- only allow jump from ground
	if self.ground_contact then
		-- set take-off speed
		self.velocity.y = jump_takeoff_speed
		play_animation(self, anim_frontflip)
	end
end

local function abort_jump(self)
	-- cut the jump short if we are still going up
	if self.velocity.y > 0 then
		-- scale down the upwards speed
		self.velocity.y = self.velocity.y * 0.5
	end
end

function on_input(self, action_id, action)
	if action_id == hash("jump") or action_id == hash("touch") then
		if action.pressed then
			jump(self)
		elseif action.released then
			abort_jump(self)
		end
	end
end

So back to the double jump actually what I’m trying to do is that the player taps the screen to make the character go jump higher with each tap but the game should abort the jumps as soon as the character reaches the top of the screen, and i want the character to do the frontflip animation on the first tap then continue with the jump animation or switch between these two animations at random and how do i go about the jump counter talked about in the above script