Skip to content
Sam Seltzer-Johnston edited this page Feb 8, 2015 · 5 revisions

Table of Contents

Basic

In this tutorial, we will go over the basics of the Löve Bone API.

  • Building a skeleton
  • Making an animation for the skeleton
  • Making an actor that uses the skeleton
  • Making a visual appearance for the actor
  • Making the actor play the animation

Require the library:

local lovebone = require("lovebone");

Making the skeleton

Create a Skeleton out of Bones:

-- Create the skeleton.
local mySkeleton = lovebone.newSkeleton();

-- Add bones to the skeleton
local NUM_SEGMENTS = 9;
local boneLength = 50;
local boneName = "bone";
for i = 1, NUM_SEGMENTS do
	local name = boneName .. i;
	local parent = boneName .. (i - 1);
	if (i == 1) then
		parent = nil; -- The first bone is the "root", so it shouldn't have a parent.
	end
	local offset = {boneLength, 0};
	if (i == 1) then
		offset[1] = 0; -- The first bone is the "root", so it doesn't need an offset.
	end
	local rotation = 0;
	local translation = {0, 0};
	local scale = {1, 1};
	local bone = lovebone.newBone(parent, i, offset, rotation, translation, scale);
	mySkeleton:SetBone(name, bone);
end

The skeleton will not be usable until it is validated:

-- Validate the skeleton!
mySkeleton:Validate();

Whenever you modify the bone structure of a skeleton, or bone properties of a bone in a skeleton, you must call Validate. This checks the bone hierarchy for inconsistencies (i.e. missing bones) and then builds the render order for the bones based on their layer.

Making the animation

Create an Animation:

-- Create an animation.
local myAnimation = lovebone.newAnimation(mySkeleton);
for i = 1, NUM_SEGMENTS do
	local name = boneName .. i;
	myAnimation:AddKeyFrame(name, 2, math.rad(5*i), nil, nil);
	myAnimation:AddKeyFrame(name, 2.5, math.rad(0), nil, nil);
	myAnimation:AddKeyFrame(name, 4.5, -math.rad(5*i), nil, nil);
	myAnimation:AddKeyFrame(name, 5, math.rad(0), nil, nil);
end

When we play this animation, everything will be automatically interpolated for us.

Making the actor

Create an Actor:

-- Create an actor.
myActor = lovebone.newActor(mySkeleton);

Now we have an actor, but it's just a set of bones right now. We need to attach a skin to it.

Before we can create the skin, we must first create a Visual object for each possible Bone appearance:

-- Create the visual elements for the actor
local boneVisuals = {};
for i = 1, 3 do
	local imageData = love.image.newImageData(boneLength, 20);
	imageData:mapPixel(function(x, y, r, g, b, a) 
		local hasRed = i == 1;
		local hasGreen = i == 2;
		local hasBlue = i == 3;
		if (hasRed) then r = 255; end
		if (hasGreen) then g = 255; end
		if (hasBlue) then b = 255; end
		return r, g, b, 255;
	end);
	boneVisuals[i] = lovebone.newVisual(imageData);
	local vw, vh = boneVisuals[i]:GetDimensions();
	boneVisuals[i]:SetOrigin(0, vh/2);
end

Attachments follow their assigned bone on an actor. Since bones are invisible, adding an attachment with no modifications to angle, position, or size, will appear wherever the bone is. This is how we make "skins" for our actors.

Using the visuals we just made, make an Attachment for each Bone:

-- Add attachments to the actor using the visual elements.
for i = 1, NUM_SEGMENTS do
	local name = boneName .. i;
	local vis = boneVisuals[((i - 1) % 3) + 1];
	local myAttachment = lovebone.newAttachment(vis);
	myActor:SetAttachment(name, "skin", myAttachment);
end

Our actor will be visible to us as soon as we call its Draw method. However, we can't look at this animation quite yet.

Adding transformations

First we need to register the animation with the Transformer of our actor:

-- Register the animation as a transformation.
myActor:GetTransformer():SetTransform("anim_curl", myAnimation);

We're almost done, but before we finish up, we should reposition this actor so it's easier to see the full animation.

To do that, we use GetRoot, which returns table with orientation data for the actor.

-- Move it toward the center and stand it upright.
myActor:GetTransformer():GetRoot().rotation = math.rad(-90);
myActor:GetTransformer():GetRoot().translation = {love.graphics.getWidth() / 2, love.graphics.getHeight() / 1.25};

The table returned by GetRoot has the following values.

Variable Description
rotation Angle of the actor in radians. Default = 0
translation Position vector of the actor in pixels. Default = {0, 0}
scale Scaling vector of the actor. Default = {1, 1}

Modifying this table will directly affect the actor. The purpose is to provide an easy way to move the actor around.

Playing the animation

Tell the actor to update:

function love.update(dt)
	if (myActor:GetTransformer():GetPower("anim_curl") > 0) then
		local vars = myActor:GetTransformer():GetVariables("anim_curl");
		vars.time = vars.time + dt;
	end
	myActor:Update(dt);
end

Calling the Update method on the actor will not advance time for animations. Multiple animations could be playing at once. Animations could also be playing at different speeds with different start times.

To accommodate this, Animations make use of Transformer variables. Each registered transformation automatically gets its own table to keep track of its state. How that table is utilized is up to the programmer.

Animations automatically come with two state variables.

Variable Description
time The amount of time that has elapsed since the start of the animation in seconds. Default = 0
speed Speed multiplier for the animation. Negative values make the animation play backwards. Default = 1

Tell the actor to draw:

function love.draw()
	myActor:Draw();
end

One last step. We need to tell the animation to start.

-- Tell the animation to start.
function love.keypressed(key, isRepeat)
	if (key == ' ') then
		myActor:GetTransformer():SetPower("anim_curl", 1);
	end
end

The Result

Full Code

button

Intermediate

In this tutorial, we will build upon the basic tutorial with some intermediate concepts.

  • Writing a reskin method
  • Animation events
  • Animation layering/blending

Coming soon.

Advanced

Coming soon.