Tutorial requests and ideas

General discussion about LÖVE, Lua, game development, puns, and unicorns.
rude
Posts: 1051
Joined: Mon Feb 04, 2008 3:58 pm
Location: Oslo, Norway

Re: Tutorial requests and ideas

Not sure what you mean about input stack, but looking forward to it.
how do you cope with having rotation and scaling but not general transformation

By using glRotatef and glScalef. Maybe change it to glMultMatrix if/when we implement general transformations.
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

I've been tinkering, and I've come to a disturbing conclusion: OO in Lua desperately needs a tutorial. Otherwise I'd'a been happy to show other nice things like
• scenegraph, (fancy name for hierarchy of objects in the world)
• input system, with stack (so you can enter/leave modes/menus)
• charas walking animation (or a cheapskate, prefabbed charas one anyway)
• odometry, for wheelchairs, tanks, etc.

Code: Select all

obj = {}
function obj:new(o, ...)
o = o or {}
setmetatable(o, self)
if not rawget(self, '__index') then self.__index = self end
o.__parent = self
if o.__init then o:__init(...) end
return o
end

And I mean to, life just got in the way a bit this week.

edit: There's a reason I really want to demo the basics in a way that isn't really necessary in löve projects: I've a friend i'd like to show these things.
Merkoth
Party member
Posts: 186
Joined: Mon Feb 04, 2008 11:43 pm
Location: Buenos Aires, Argentina

Re: Tutorial requests and ideas

There are a gazillion implementations of OOP in lua. Here's just one example: http://class.luaforge.net/
rude
Posts: 1051
Joined: Mon Feb 04, 2008 3:58 pm
Location: Oslo, Norway

Re: Tutorial requests and ideas

aes wrote:There's a reason I really want to demo the basics in a way that isn't really necessary in löve projects: I've a friend i'd like to show these things.
Hehe, go for it . You existing character-walking-demo could totally be tutorialized.
Merkoth wrote:There are a gazillion implementations of OOP in lua. Here's just one example: m http://class.luaforge.net/
Nice. I've done my "classes" manually so far. Maybe it's a good idea to use something like this instead.
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Merkoth wrote:There are a gazillion implementations of OOP in lua.
This, unfortunately, is part of the problem. Another part is that there's really no known way so far of getting it quite right. To see why, let's dissect my suggested version:

Code: Select all

obj = {}                    -- the ur-instance. needed, obviously
function obj:new(o, ...)    -- note that the default ctor also takes varargs.
o = o or {}               -- so far, so good.
setmetatable(o, self)
if not rawget(self,'__index') then
self.__index = self     -- the reason for this nugget is to permit sub-
end                       -- classes to override __index.
o.__parent = self         -- non-std, to know inheritance in new __index:en
if o.__init then
o:__init(...)           -- a (possible) initializer that does not need to
end                       -- bother with metatable and this stuff.
return o
end

The curious mechanism for __index and __parent is there to permit wierdness such as this:

Code: Select all

function rect:__index(k)
if     k == 'xa' then do return self.x end
-- ...
elseif k == 'y1' then do return self.y + self.h end
elseif k == 'area' then do return self.w * self.h end
end
-- ok, it's not in the table itself (or we wouldn't be called)
-- and it's not any of the named extra properties,
return self.__parent[k]
end

If we didn't have the already-defined test, rect:__index would be clobbered by the default ctor in obj. We could of course make a new ctor, in effect an entirely new class hierarchy not grounded in obj. That is not necessary bad, but it makes for dissociative code and new inventions that would fit nicely higher up in the hierarchy would have to be put in with copy-paste-inheritance. ( newcls.cool = clever.cool at least )

The __init mechanism is completely spurious, but nicely separates the objectological navelnaut E.V.A. from actual payload:

Code: Select all

function charas:__init()
end
Perversely, I can't work out how to construct a generic __tostring. What I'd like is something like "object: 0xDEADBEEF" much like the table representation, there's no way to get that (admittedly mostly useless) pointer. So what might one want to have in a nice superclass? Well, an isinstance predicate would be nice:

Code: Select all

function obj:isinstance(kls)
if kls == obj           then return true end
local k = self
while k ~= obj do
if k == kls then return true end
k = k.__parent
end
return false
end

• I also had a helpful deepcopy method, but it's inexplicably broken right now.
• I haven't migrated to 0.2.1 yet.
rude wrote:Hehe, go for it . You existing character-walking-demo could totally be tutorialized.
Yeah, I'll get there. Eventually.

löve, aes
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

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"

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()
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.
Attachments
charasdemo.love
ivan
Party member
Posts: 1719
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Tutorial requests and ideas

I looked the other demos and the demopack but I couldn't find anything that tests performance.
How about a test suite demo? If somebody writes up a test suite, I'll make a parallel one in AGen.
It could be interesting to compare the two engines, especially in terms of rendering.
Love would probably be faster at rendering dynamic shapes - changing primitives or primitives generated per frame.
Merkoth
Party member
Posts: 186
Joined: Mon Feb 04, 2008 11:43 pm
Location: Buenos Aires, Argentina

Re: Tutorial requests and ideas

ivan wrote:I looked the other demos and the demopack but I couldn't find anything that tests performance.
How about a test suite demo? If somebody writes up a test suite, I'll make a parallel one in AGen.
It could be interesting to compare the two engines, especially in terms of rendering.
Love would probably be faster at rendering dynamic shapes - changing primitives or primitives generated per frame.
And here you go. I don't know if you can consider it a full-blown test suite, but it does give some info about performance. I'd löve to port it to AGen, but I can't be bothered to boot Windows,sorry.
rude
Posts: 1051
Joined: Mon Feb 04, 2008 3:58 pm
Location: Oslo, Norway

Re: Tutorial requests and ideas

ivan wrote:Love would probably be faster at rendering dynamic shapes - changing primitives or primitives generated per frame.
Well ... I don't think LÖVE would be faster at anything. You use a native container for sprites and graphics, so all you do in Lua is scene.add_child(), right? If you want to change a primitive, do you need to remove a child and re-add it? Remember that LÖVE has to iterate manually in Lua: foreach k,v in ipairs(enemies) ... end. 8-)
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Ok, I think I've worked out roughly how I'd like input to work, thought I'd share.

It's like this: Bindings are collected in contexts, (since you'd like it to be modal) that are stacked. (so menus, minigames, &c needn't worry about where they were activated from)

callbacks can have any amount of userdata attached, like so:

Code: Select all

  ic = input.context:new{
keydown = {
[love.key_space] = {
world_master = m
end,
m1,
-- more data here
},
},
}

This is specifically to escape the homeless method problem, wherein a connector function would be needed to remember the object:

Code: Select all

function do_it_but_like_with_the_thing(key) the_thing:do_it() end

The menu system is similar. The difference being that the menu label is the first el, the second the function; user args follow like in input:

Code: Select all

  m1.items = {
{"Don't do anything"},