Tutorial requests and ideas

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

Re: Tutorial requests and ideas

Post by rude »

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.
User avatar
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Post by aes »

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.
But, I'll probably need to write something sensible about this snippet instead:

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.
User avatar
Merkoth
Party member
Posts: 186
Joined: Mon Feb 04, 2008 11:43 pm
Location: Buenos Aires, Argentina

Re: Tutorial requests and ideas

Post by Merkoth »

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

Re: Tutorial requests and ideas

Post by rude »

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.
User avatar
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Post by aes »

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,
  -- so let's ask super.
  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()
  self:reloadAnim()
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
User avatar
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Post by aes »

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.
Attachments
charasdemo.love
(4.69 KiB) Downloaded 492 times
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Tutorial requests and ideas

Post by ivan »

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.
User avatar
Merkoth
Party member
Posts: 186
Joined: Mon Feb 04, 2008 11:43 pm
Location: Buenos Aires, Argentina

Re: Tutorial requests and ideas

Post by Merkoth »

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.
User avatar
rude
Administrator
Posts: 1052
Joined: Mon Feb 04, 2008 3:58 pm
Location: Oslo, Norway

Re: Tutorial requests and ideas

Post by rude »

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-)
User avatar
aes
Prole
Posts: 13
Joined: Sat Mar 29, 2008 2:24 am

Re: Tutorial requests and ideas

Post by aes »

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] = {
        function(menu)
          world_master = m
          menu:enter()
        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 = {
    {'Submenu',       m2.enter, m2},
    {'Other submenu', m3.enter, m3},
    {"Don't do anything"},
    {'Exit menu',     m1.leave, m1},
  }
The "menuworld" subclass of baseworld exists to solve the problem of showing the gameworld but not updating it unless it's a menu. (that last bit is for making animated menus.)

aes
Sharing the löve! :D
Attachments
menudemo.love
(12.45 KiB) Downloaded 485 times
Post Reply

Who is online

Users browsing this forum: No registered users and 60 guests