Charas sprite character tutorial
A person sprite walking around is a common component of games, so let's look at how you can get that with a minimum of fuss. (The easiest thing is of course to simply use the files from this demo, and I encourage you to do so, but let's take a closer look)
Let's get the main file out of the way first:
Code: Select all
love.filesystem.require"util.lua"
love.filesystem.require"charas.lua"
function load()
love.graphics.setBackgroundColor(224,224,255)
playa = charas:new{imagename="d00d.png",pos=v2:new{x=200,y=200}, }
First the two files you should definately grab are included. Then we instantiate a charas object. Don't worry about the v2 class, it's just a 2d vector. (If you're interested read the code in util.lua, it's not complicated.)
Code: Select all
keys = {
[ love.key_up ] = playa.sequences.walk_n,
[ love.key_down ] = playa.sequences.walk_s,
[ love.key_left ] = playa.sequences.walk_w,
[ love.key_right ] = playa.sequences.walk_e,
}
end
function update(dt)
playa.seq = playa.sequences.stop_s
for k,v in pairs(keys) do
if love.keyboard.isDown(k) then playa.seq, x = v, true end
end
playa:update(dt)
end
function draw()
playa:draw()
end
After that the global update and draw functions are short-circuited to the character object. The only other processing that happens is that keypresses are mapped to walking sequences.
That out of the way, let's look at the charas class.
Code: Select all
local sequences = obj:new{
walk_n = { 0, 1, 2, 1, move=v2:new{x= 0, y=-2} }, ...
stop_n = { 1, move=v2:new{x=0,y=0} }, ...
}
charas = obj:new{
sequences = sequences,
cellsize = v2:new{ x=24, y= 32, }, cellcenter = v2:new{ x= 0, y=-15, },
pos = v2:new{}, delay = 0.075, time = 0, frame = 1, seq = sequences.stop_s,
}
Charas, it turns out is an "obj". This is because the object orientation model chosen, where "classes" as we know them from most other modern languages don't exist. Instead, an object is kept as the prototype of the class and this has a few implications. If an object does not itself have an attribute, the parent object is asked next. This means that scratching the prototype car scratches
all the cars that have not been individually repainted before the scratch or after. This is because the individual cars don't care what color they are unless they have been given one, and if the prototype is now red-with-scratch, that's the color of derived objects as well. This is a bit bizarre, so just remember you read something about it and read it again if you ever notice the effect.
Ok, back to the sprite tutotial...
The charas object has a number of attributes.
- cellsize and cellcenter describe the positioning of the sprite in the image and if you use the charas generator you can safely ignore it. (they have generators for other sizes, is memory serves, though)
- pos is simply the position of the character in the world. Hopefully, there will be a later tutorial outlining a sane way to handle objects in the world and their relationships, but for now, you can just get/set the position in here. (If you prefer to explicitly place it when you draw it, you can leave it out, but then the whole business of walking becomes your problem..)
- delay and time control the animation and the speed at which the character moves.
- frame and seq control which frame in which sequence is being shown and walked.
If you only have a "normal" charas sprite sheet, instantiating you character with
Code: Select all
joe=charas:new{imagename="joe.png"}
is perfectly reasonable and will create a joe at position 0,0 (top left corner of the screen) standing facing us. If you want the character to walk faster, lower the delay value, this will cause the animation to change frame (and move the character around) faster.
Code: Select all
function charas:reloadAnim()
self.image = lg.newImage(self.imagename)
self.anim = lg.newAnimation(
self.image, self.cellsize.x, self.cellsize.y, 0, 0)
self.anim:setCenter(self.cellcenter.x, self.cellcenter.y)
end
function charas:__init()
self:reloadAnim()
end
Here's a straight-forward piece of code loading the animation and setting the center (handle, really) of the sprite. It's called from the new objects initializer, but can be called separately, if you ever need that.
Code: Select all
function charas:update(dt)
if self.delay > 0 then
self.time = self.time + dt
while self.time > self.delay do
self.frame = (self.frame+1) % #self.seq
self.pos = self.pos + self.seq.move
self.time = self.time - self.delay
end
end
end
The update code checks if the time to the next frame has passed, and if so rolls the animation and moves. If you take a moment to look at the standard sequence definitions, you'll see that the same frame (1) is used both as the middle frame of walking south is also used as the standing still frame. The difference is the move, which is zero in the case of standing.
Code: Select all
function charas:draw(pos)
local pos = pos or v2
pos = pos + self.pos
self.anim:seek(self.seq[self.frame+1] or 1)
lg.draw(self.anim, pos.x, pos.y)
end
To draw the sprite, a position offset can be supplied. This isn't really to place the sprite, but to be able shift the world the charas inhabits. Think vehicles, different floors in a house, ... whatever.
If you want to modify, or add to the animation sequences, you can supply a different sequence table:
Code: Select all
dancing = charas.sequences:new{
moonwalk_e = { 9,10,11,10, move=v2:new{x=3, y= 0} }, ...
}
playa = charas:new{
imagename="d00d.png",pos=v2:new{x=200,y=200},
sequences = dancing,
}
You'd have to make some other modifications as well, to use the new sequences, but I think I can trust you can do that now...
aes.