Turn Spine animation into flip book animation

Hey! I’m currently trying to modify the endless runner tutorial. I’d like to Change the frog :frog: Spline Animation into my own Animation wich is a flip-book Animation.

I’m not that much of an skripting pro and am happy if I actaully understand the code… :confused: How can I turn all the logic of the vector based Spline aniamtion into the flip-book Animation?
Is there an easy way to do that? Thanks in Advance :sweat_smile:

There’s very little work needed. You basically set up new flip book animations and play them instead of the spine ones. If you use the same animation names as the spine ones, you only need to change the play_animation(self, anim) function so it plays a flipbook instead.

1 Like

Thanks a lot! It’s working halfway. Only changing the
play_animation(self,anim) to msg.post("#sprite", “play_animation”, {id = hash(“run_right”)})
had my character running but didn’t use the jumping Animation.

I had to change the local function update_animation(self) too only Problem is: now the character isn’t running anymore… but the jumping works :sweat_smile:

Did you set the play back mode of your run animation in your atlas/tilesource to loop forward.?

There’s a sprite.play_flipbook() function recently introduced to the API to make it a bit easier to play sprite flipbook animations.

In my case it’s set to loop backwards but yes it is.

Thanks! I tried it with the function
sprite.play_flipbook("#sprite", {id = hash(“run_right”)})

now the character is running with something like a afterimage and jumping out of the frame never coming back :sweat_smile:
I get the ERROR notice
hero/hero.script:41: bad argument #2 to ‘play_flipbook’ (hash or string expected, got table)

Edit: okay changed the code line to
sprite.play_flipbook("#sprite", “fall_right”)

now it’s working like before, no running animation but the jumping animation works :face_with_raised_eyebrow:

Well, hard to tell why it isn’t working. It should be ok to simply replace all calls to spine.play_anim() with matching calls to sprite.play_flibook(). Double check all animation names and check the console for errors.

Also make sure that the animations have the right playback. Is the run animation playing once or not at all?

I think at the beginnig it’s playing one frame from the run animation before going sliding and using the fall aimation unless I jump.

the playback is set to loop backwards for run, the jump and fall aniamtion have only one frame and are set to none

I looked if I have missed any call but found only the play_animation and the update_animation that had the spine.play_anim() :expressionless:

When I changed the update_animation the character runs endless, as soon as I change the update_aniamtion function it stops running completly… I don’t get it

Perhaps you can share some code? It’s difficult to guess the exact problem

Okay, here u go the whole hero.script :sweat_smile: I hope it helps

-- 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()
	msg.post("#", "reset")

	-- 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

-- hero.script
local function play_animation(self, anim)
	-- only play animations which are not already playing
	if self.anim ~= anim then
		-- tell the spine model to play the animation
		sprite.play_flipbook("#sprite", "run_right")
		-- 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
		sprite.play_flipbook("#sprite", "run_right")
	else
		if self.velocity.y > 0 then
			sprite.play_flipbook("#sprite", "jump_right")
		else
			sprite.play_flipbook("#sprite", "fall_right")
		end
		--sprite.play_flipbook(url, id)
	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("reset") then
		self.velocity = vmath.vector3(0, 0, 0)
		self.correction = vmath.vector3()
		self.ground_contact = false
		self.anim = nil
		go.set(".", "euler.z", 0)
		go.set_position(self.position)
		msg.post("#collisionobject", "enable")

	elseif message_id == hash("contact_point_response") then
		-- check if we received a contact point message
		if message.group == hash("end") then
			-- Die and restart
			play_animation(self, hash("jump_right"))

			go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7)
			go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
			function()
				msg.post("#", "end")
			end)
		elseif 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

EDIT: I changed so it uses the code formatting /Mathias

1 Like
local function update_animation(self)
	-- make sure the right animation is playing
	if self.ground_contact then
		sprite.play_flipbook("#sprite", "run_right")
	else
		if self.velocity.y > 0 then
			sprite.play_flipbook("#sprite", "jump_right")
		else
			sprite.play_flipbook("#sprite", "fall_right")
		end
		--sprite.play_flipbook(url, id)
	end
end

This function will start the animation over all the time. You should only call it when the animation should change.

1 Like

How do I change that? I didn’t write the Code myself.
That’s the code from the tutorial, wich works fine with the frog :frog: (except the changes in the play_animation function and the update_aimation function) I didn’t change anything in the code

Well, your difference from the tutorial is that you omitted the play_animation()call.
That call makes sure you only play the animation if it’s a different one.

Here’s the original tutorial code:

local function play_animation(self, anim)
    -- only play animations which are not already playing
    if self.anim ~= anim then
        -- tell the spine model to play the animation
        spine.play("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, 0.15)
        -- 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
        play_animation(self, hash("run_right"))
    else
        if self.velocity.y > 0 then
            play_animation(self, hash("jump_right"))
        else
            play_animation(self, hash("fall_right"))
        end
    end
end
1 Like

I feel stupid… don’t I have it right here? before the update function? I only exchanged the code for the animation :sob:

local function play_animation(self, anim)
	-- only play animations which are not already playing
	if self.anim ~= anim then
		-- tell the spine model to play the animation
		sprite.play_flipbook("#sprite", "run_right")
		-- 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
		sprite.play_flipbook("#sprite", "run_right")
	else
		if self.velocity.y > 0 then
			sprite.play_flipbook("#sprite", "jump_right")
		else
			sprite.play_flipbook("#sprite", "fall_right")
		end
		--sprite.play_flipbook(url, id)
	end
end
1 Like

#1 sign of learning stuff, tbh – you go, man! :raised_hands:

1 Like

You never call the function play_animation, you only call sprite.play_flipbook directly every frame, which causes the animation to start over.

Try something like this (untested):

local function play_animation(self, anim)
	-- only play animations which are not already playing
	if self.anim ~= anim then
		-- tell the spine model to play the animation
		sprite.play_flipbook("#sprite", anim)
		-- 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
		play_animation(self, "run_right")
	else
		if self.velocity.y > 0 then
			play_animation(self, "jump_right")
		else
			play_animation(self, "fall_right")
		end
	end
end

Very true!

2 Likes

Ahhhh! okay now I get it! :open_mouth: That actually makes sense
And oh my gosh! It works! Thank you!! :sob:

2 Likes