-
Notifications
You must be signed in to change notification settings - Fork 7
Tutorials
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");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);
endThe 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.
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);
endWhen we play this animation, everything will be automatically interpolated for us.
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);
endAttachments 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);
endOur actor will be visible to us as soon as we call its Draw method. However, we can't look at this animation quite yet.
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.
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);
endCalling 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();
endOne 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
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.
Coming soon.