## HamsterCupid: A toy component based entity system

iamwil
Prole
Posts: 11
Joined: Fri Aug 19, 2011 8:53 pm

### HamsterCupid: A toy component based entity system

Hi all,

It's been a long while since I touched Lua or Love, so this is my first project since then. I wrote a toy component based entity system. It's not done yet, but I figured I should just throw it out in the wild, in an attempt to get early feedback.

Right now, there are just three components to an Entity:
* View - responsible for drawing to the screen and the related transforms
* Movement - responsible for moving the entity, either over time or from player input
* Model - Holds the entity states, such as position, velocity, acceleration, etc.

Currently, it's hardcoded that you use all three, but as I add more component, one would be able to customize it.

It also has a parent-child hierarchy to compose entities (I'm not sure what the formal name is, but I've heard "frames" If anyone knows, lemme know)
The demo shows a magician wearing a helmet, carrying a wand, shield, and the shield has a cow jittering in front of it. The magician has helmet, wand, and shield as children, and the shield has the cow as a child. When you move the character around, with W,A,S,D keys, you can see that the accessories move around with the magician, even though the accessories have a local coordinate system.
Screen Shot 2011-10-04 at 10.35.27 AM.png (18.62 KiB) Viewed 6416 times
The demo displays the vectors for acceleration and velocity. you can also pan and zoom the camera with the arrow keys and the square bracket keys.

I'll be posting updates as I do more.

github: https://github.com/iamwilhelm/hamstercupid
Attachments
hamstercupid.love
Larsii30
Party member
Posts: 267
Joined: Sun Sep 11, 2011 9:36 am
Location: Germany

### Re: HamsterCupid: A toy component based entity system

cool.
Something very useful.
iamwil
Prole
Posts: 11
Joined: Fri Aug 19, 2011 8:53 pm

### Re: HamsterCupid: A toy component based entity system

I've updated hamstercupid to fix some of the motion bugs that I found. The entities can be composed like the following:

Code: Select all

magician = Entity:new("magician", V:new(400, 200))
magician.view:setImage("resources/rpg/magician.front.gif")

shield = Entity:new("shield", V:new(15, 10))

cow = Entity:new("cow", V:new(50, 0))
cow.view:setImage("resources/cow.png")
shield:addChild(cow)
The magician is holding a shield, and the shield has a cow. (There are also other options where you can set the scale and rotation of the child entities.)

And now the motions can also be composed:

Code: Select all

cow.movement:addMovement(Motion.wiggle(50, 1))
cow.movement:addMovement(Motion.waggle(50, 1))
The motion names aren't permanent yet, but wiggle() moves the cow vertically back and forth. waggle() moves the cow horizontally back and forth. As a result, the cow moves back and forth diagonally. The amplitude of moving back and forth is 50, and it has a period of 1 second.

What if we wanted the cow to orbit the shield in a circle?

Code: Select all

cow.movement:addMovement(Motion.wiggle(50, 1))
cow.movement:addMovement(Motion.waggle(50, 1, math.rad(90))
We simply make the waggle motion delayed by a phase of 90 deg.

And when you move the magician around, the cow still circles the shield correctly. Whee.
Attachments
hamstercupid.love
iamwil
Prole
Posts: 11
Joined: Fri Aug 19, 2011 8:53 pm

### Re: HamsterCupid: A toy component based entity system

I've added animations to HamsterCupid! Now, you can specify which animation the entity should be playing based on its state. This is done with a crude DSL. An entity has a film with a spritesheet, with different animations named by the state, and each animation has many frames.

So as an example, we have a person walking left that's 40x32 pixels with frames on rows 0 and cols 0 to 3.

Code: Select all

  local person = Entity:new("person", V:new(400, 200))
person.view:film("resources/personSpriteSheet.gif", function(view)
view:animation("walk.down", 40, 32, {}, function(animation)
animation:frame(0, 0)
animation:frame(0, 1)
animation:frame(0, 2)
animation:frame(0, 3)
end)
end)

However, declaring the frames one by one is pretty repetitive, so instead of using for loops, there's a short hand, where you specify how many consecutive columns after the first frame in the spritesheet to set:

Code: Select all

  local person = Entity:new("person", V:new(400, 200))
person.view:film("resources/personSpriteSheet.gif", function(view)
view:animation("walk.left", 40, 32, {}, function(animation)
animation:frame(0, 0, { cols = 4 })
end)
end)

If there are frames on two different rows belonging to the same animation, you can just declare that also:

Code: Select all

  local person = Entity:new("person", V:new(400, 200))
person.view:film("resources/personSpriteSheet.gif", function(view)
view:animation("walk.left", 40, 32, {}, function(animation)
animation:frame(0, 3, { cols = 2 })
animation:frame(1, 0, { cols = 2 })
end)
end)

Sometimes, spritesheets only have animations of the character walking to the left, but you want to also use it for walking to the right. If that's the case, we can specify a reflection on the frame.

Code: Select all

  local person = Entity:new("person", V:new(400, 200))
person.view:film("resources/personSpriteSheet.gif", function(view)
view:animation("walk.left", 40, 32, {}, function(animation)
animation:frame(0, 0, { cols = 4 })
end)
view:animation("walk.left", 40, 32, {}, function(animation)
animation:frame(1, 0, { cols = 4, scale = Vector:new(-1, 1) })
end)
end)

Right now, animations and movements can be declared as a DSL, but I'll want to make it declarable in XML or JSON.

The code is at: https://github.com/iamwilhelm/hamstercupid
iamwil
Prole
Posts: 11
Joined: Fri Aug 19, 2011 8:53 pm

### Re: HamsterCupid: A toy component based entity system

Here's a demo video showing hamstercupid with an entity that has both a body and a head, animated while in different states.

The current DSL to declare this entity from a spritesheet is:

Code: Select all

  local person = Entity:new("person", V:new(x, y))
person.view:film("resources/dodgeball/wildlynx.gif", function(view)
view:animation("stand.down.left", 40, 32, {}, function(animation)
animation:frame(0, 0)
end)

view:animation("walk.down.left", 40, 32, { offset = V:new(0, 0), period = 1 }, function(animation)
animation:frame(0, 3, { cols = 3 })
animation:frame(1, 0, { cols = 3 })
end)

view:animation("stand.down.right", 40, 32, {}, function(animation)
animation:frame(0, 0, { scale = V:new(-1, 1) })
end)

view:animation("walk.down.right", 40, 32, { offset = V:new(0, 0), period = 1 }, function(animation)
animation:frame(0, 3, { cols = 3, scale = V:new(-1, 1) })
animation:frame(1, 0, { cols = 3, scale = V:new(-1, 1) })
end)

view:animation("stand.left", 40, 32, {}, function(animation)
animation:frame(0, 1)
end)

view:animation("walk.left", 40, 32, {}, function(animation)
animation:frame(1, 3, { cols = 3 })
animation:frame(2, 0, { cols = 3 })
end)

view:animation("stand.right", 40, 32, {}, function(animation)
animation:frame(0, 1, { scale = V:new(-1, 1) })
end)

view:animation("walk.right", 40, 32, { scale = V:new(-1, 1) }, function(animation)
animation:frame(1, 3, { cols = 3, scale = V:new(-1, 1) })
animation:frame(2, 0, { cols = 3, scale = V:new(-1, 1) })
end)

view:animation("stand.up.left", 40, 32, {}, function(animation)
animation:frame(0, 2, {})
end)

view:animation("walk.up.left", 40, 32, {}, function(animation)
animation:frame(2, 3, { cols = 3 })
animation:frame(3, 0, { cols = 3 })
end)

view:animation("stand.up.right", 40, 32, {}, function(animation)
animation:frame(0, 2, { scale = V:new(-1, 1) })
end)

view:animation("walk.up.right", 40, 32, {}, function(animation)
animation:frame(2, 3, { cols = 3, scale = V:new(-1, 1) })
animation:frame(3, 0, { cols = 3, scale = V:new(-1, 1) })
end)
end)

view:animation("look.down.left", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 0)
end)
view:animation("look.left", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 1)
end)
view:animation("look.up.left", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 2)
end)

view:animation("look.down.right", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 0, { scale = V:new(-1, 1) })
end)
view:animation("look.right", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 1, { scale = V:new(-1, 1) })
end)
view:animation("look.up.right", 32, 32, { offset = V:new(768, 0) }, function(animation)
animation:frame(0, 2, { scale = V:new(-1, 1) })
end)
end)

-- FIXME forgetting to initialize the state is causing bugs when writing the DSL
person.model.state = "walk.down.left"

I currently don't yet have code for a state machine to help manage the different entity states and their transitions, but I'll be taking a look at kikito's Stateful lib to see if it fits my needs.

If any one has any feedback on the DSL and how useful or easier it might make your life, or the demo in general, I'd love to hear it, good or bad!
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Contact:

### Re: HamsterCupid: A toy component based entity system

I think the dsl is just too verbose.

• A film is made of 1 or more animations
• An animation is made of 0 or more frames
• In some cases the Animation's options parameter isn't needed. It would be nice if it could be removed
• Those "40,32" parameters are likely to be repeated in all cases, so I'd rather specify them once, and leave an option to change them (on the options params of each animation) if that was the case
If that's the case, you shouldn't need all those function() to give you context. A table-based dsl, such as this one, should be enough:

Code: Select all

local person = Entity:new("person", V:new(x, y))
person.view:film(
"resources/dodgeball/wildlynx.gif",
40, 32, -- defaults
{
{ id="stand.down.left", frames={ {0, 0} } },
{ id="walk.down.left",
frames={
{0, 3, { cols = 3 } },
{1, 0, { cols = 3 } }
},
options={ offset=V:new(0, 0), period = 1 }
},
{ id="stand.down.right", frames={0, 0, { scale=V:new(-1, 1) } },
...
)

It'll probably be a good idea to have animations in separate global variables. That way they can be shared between films:

Code: Select all

local stand_dl = { id="stand.down.left", frames={ {0, 0} } }
local walk_dl = {
id="walk.down.left",
frames={
{0, 3, { cols = 3 } },
{1, 0, { cols = 3 } }
},
options={ offset=V:new(0, 0), period = 1 }
}
local stand_dr = { id="stand.down.right", frames={ 0, 0, { scale=V:new(-1, 1) } }
person.view:film(
"resources/dodgeball/wildlynx.gif",
40, 32, -- defaults
{ stand_dl, walk_dl, stand_dr ... }
)
Regards, and good luck with your project!
When I write def I mean function.
iamwil
Prole
Posts: 11
Joined: Fri Aug 19, 2011 8:53 pm

### Re: HamsterCupid: A toy component based entity system

Hey there, thanks for the feedback. I'll defn take your suggestions into account as I'm refining it. Thanks.

Wil

### Who is online

Users browsing this forum: No registered users and 5 guests