PÄSSION: object-oriented LÖVE

Showcase your libraries, tools and other projects that help your fellow love users.
scirath
Citizen
Posts: 85
Joined: Mon Feb 23, 2009 4:44 am

Re: PÄSSION: object-oriented LÖVE

Post by scirath »

kikito wrote:The latest sourcecode can be downloaded from the new google code page.
*Thumbs through the source.*

Holy... :shock: I think I'm gonna need some more coffee...
(USER MIGHT BE BANNED FOR THIS POST.)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by kikito »

@scirah: please feel free to ask any questions!

@kalle2990: I've had some enlightening interchanges with the love core team and I've decided I'll just eliminate the "fontSize" parameter. The font size will therefore be defined by the fonts themselves. So, one of my next objectives is to make loading fonts as streamlined as possible! So, I'll make a "ResourceManager" as soon as I finish ironing out some rough spots on the interface, and finally implement a way to destroy actors.

This is going to be interesting :)
When I write def I mean function.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by kikito »

I'm back with an update!

This post is long, so I guess it is better that I put the demo at the beginning...
pgdv2.love
PÄSSION GUI demo version 2 - updated to PÄSSION 0.6.2rc1
(30.86 KiB) Downloaded 154 times
Unfortunately today's demo doesn't look very different from the previous one, since most changes have happened "under the hood".
The most visible difference is that the demo no longer segfaults. That happens because I'm using the new PÄSSION resource managing methods - see below.
I've added a visible "pressed" callback. The paddings work better. The button's "focus capture" behaviour is now dependant on a parameter (you can tweak it on MenuButton.rb)

That was the important stuff. Feel free to ignore the babbling that follows :)

1. Resource Managing

[ WARNING: Before anyone gets too excited about this, I've only tested the following with default fonts. Loading of images, sounds, imageFonts and ttfFonts isn't tested yet ]

Most important change is that PÄSSION is now a resource manager! The idea is using getFont, getImage & getSound, like this:

Code: Select all

small = passion:getFont(10) -- loads a default font with size=10
ttf = passion:getFont('fonts/comicsans.ttf', 12) -- loads a ttf font with a size from a file. Please don't use Comic Sans
numbers = passion:getFont('images/numbers.png', '1234567890') -- loads a font from a image
monkey = passion:getImage('images/monkey.png') -- loads an image
honk = passion:getSound('sounds/honk.wav') -- loads a sound
tune = passion:getMusic('musics/juantanamera.wav') -- loads a music
Notice how passion:getFont is able to get the right type of font depending on the parameters passed (it is supposed to be - not everything is tested)

The good thing about these functions is that they 'keep track' of which fonts, sounds & images have already been loaded. If you call passion:getFont(10) again, you will get the previously created font, and a new one will not be created, which is quite nice for what follows.

If you are using passion, you will have actors. So you can load stuff still more easily with the new Actor.load() function. It will use passion.getXXX to load resources passed to it, in a very intuitive way.

Code: Select all

Monkey = passion.Actor:subclass('Monkey', {hasImage=true})
Monkey:load({
  fonts = { small = 10, -- loads a default font
            ttf = {'fonts/comicsans.ttf', 12},
            numbers = {'images/numbers.png', '1234567890'}
  },
  images = { monkey = 'images/monkey.png' },
  sounds = { honk = 'sounds/honk.wav' },
  musics = { tune = 'musics/juantanamera.wav' }
})

-- I now can use the resources by doing Monkey.collection.resource. For example:
love.graphics.setFont(Monkey.fonts.ttf)
love.graphics.draw(Monkey.images.monkey, x,y)
So this way every Actor can 'declare' what resources it needs. You can define them freely in a localized way - no need to worry about loading the same resource twice, no need to create global variables on your love.load function.

2. New main loop

I've finally implemented my own main loop. It is still possible to 'attach' each LÖVE function & callback to its PÄSSION equivalent, but right now it is easier to do just this:

Code: Select all

function love.run()
  return passion:run()
end
All you have to do after this is instantiate actors.
I've also included an additional "step" in the execution. LÖVE's loop does "update-draw-events" while PÄSSION does "update-draw-reset-events". I had to add a special step since I needed to do some actions after the actors were drawn, but before the events were processed. I could have called it "update2".

3. Others

I've started toying with actor deletion, but honestly I haven't tested it enough yet.
I've further tweaked the gui elements, fixing several bugs and adding new functionality here and there. For example, buttons now have an option that activates or deactivates their "focus".

4. Next objectives

My next objective is finishing testing the resource managing. Then I'll make a new demo, with images and sounds.
Then I'll review some tricky parts of MiddleClass (states within states... crazy stuff)
After that, I'm going to spend some time writing some wiki documentation (for PÄSSION and MindState).
Then I'm planning to implement Actor grouping and z-ordering (I suspect these two are actually related)

Comments/requests/questions are welcome!
Last edited by kikito on Wed Mar 24, 2010 10:35 pm, edited 2 times in total.
When I write def I mean function.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by Robin »

kikito wrote:Unfortunately today's demo doesn't look very different from the previous one, since most changes have happened "under the hood".
The most visible difference is that the demo no longer segfaults.
That seems like a rather large difference to me. ;)
Help us help you: attach a .love.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by kikito »

Ok new demo out!

I've ironed out most of the resource loading bugs (I've not tested bitmaps fonts, I'm afraid) and have also built a solid Actor deletion routine, as well as preliminary "actor hierarchies" (with parents & children).

I'm very happy because this is now a completely functional engine - from now on, everything else will be bug fixing and 'nice to have' features, such as drawing order.

Things to note on this demo:
  • A Game object that removes Actors when the states change
  • The keys on the piano are subclasses of passion.gui.button, and the piano is a Pannel. So everything can be moved around nicely.
In the future I might add keyboard support.

You may find the new demo here: http://love-passion.googlecode.com/files/pgdv3.love

Please do try it out!
When I write def I mean function.
osuf oboys
Party member
Posts: 215
Joined: Sun Jan 18, 2009 8:03 pm

Re: PÄSSION: object-oriented LÖVE

Post by osuf oboys »

Really useful, especially MindState. Stacks of states and the ability to "super-call" a lower state would be nice btw.

Not sure I like that there is a single world by default - some games will certainly want to use more. The example code needs to be updated to 6.0.1 (just fix the newSound and newMusic occurrences - perhaps you're dealing with this in order to use multiple copies of sources?). 'updateUnlessFrozen' or something in the Actor class should take and send the dt parameter - or else no actor will get their dt's.

Something that that is not clear to me is how one gets callbacks in a Game class, without having to rely on actors or GUI elements. Note that this is the typical approach for PÄSSIONifying a project and forcing a GUI approach on every project might not be viable. Perhaps just a general register() function which allows callbacks to be sent to an object; be it a Game, non-Actor class, or otherwise? This is a simple solution but I do not like this framework of direct input-model coupling; not even for Actors, as they are now. For instance, it is complicated to let the state of the Game influence Actors (is this the "states in states" deal?). If we bring up the main menu, we do not want callbacks to be sent to the Actors unless Games wants it to be that way. Perhaps something more like ËNVY's observer pattern (http://en.wikipedia.org/wiki/Observer_pattern) where Actors are registered as observers to Game and game an observer of love's callbacks?

Sorry about all the criticism, the framework is really good. Keep it up!
If I haven't written anything else, you may assume that my work is released under the LPC License - the LÖVE Community. See http://love2d.org/wiki/index.php?title=LPC_License.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by kikito »

osuf oboys wrote:Really useful, especially MindState
Thanks! I'm trying hard to make it easy?
osuf oboys wrote:Stacks of states and the ability to "super-call" a lower state would be nice btw.
Not sure what you mean by "stacks of states". You mean states with states inside? I call those "stateful states" :). It is in theory possible. The trick is that addState takes a second parameter that is the superclass of the state you are creating. I haven't got myself to fully test that part, though. Also, the syntax might not be clean for those cases.

When you say "super-call a lower state" you mean calling a function on a state that has a state, right?

When I test it properly, I'll upload examples on the wiki, but it could take some time. Here's a quick peek:

Code: Select all

require('MindState.lua')

Theory = class('Theory', StatefulObject)

function Theory:foo() print('foo') end

local StatefulState = Theory:addState('StatefulState', StatefulObject) -- <-- the magic happens on this line
function StatefulState:foo() print('bar') end

local InternalState = StatefulState:addState('InternalState')
function InternalState:foo() print('baz') end

theory = Theory:new()
theory:foo() -- foo
theory:gotoState('StatefulState')
theory:foo() -- bar
theory.currentState:gotoState('InternalState')
theory:foo() -- baz
Disclaimer: I've written that code from the top of my head, haven't tested it, not sure it will work. But that's the idea.
osuf oboys wrote:Not sure I like that there is a single world by default - some games will certainly want to use more.
I understand your concerns. However, I couldn't see any way of providing a simple interface for multi-words, while the mono-word interface is quite streamlined. If you or anyone else can show me a way to handle them that I like, I'll include them, sure. In the meantime, if someone needs multi-word stuff, they can extend passion (maybe add a passion:getWorld2() or something) and create a hasBody2 on actor.
osuf oboys wrote:The example code needs to be updated to 6.0.1
Indeed. I'm just too busy to update that code at the moment. The PÄSSION version on the svn is already fixed, though.
osuf oboys wrote:'updateUnlessFrozen' or something in the Actor class should take and send the dt parameter - or else no actor will get their dt's.
I'll check that out. However, the "frozen" part is probably going to go. I'll add a Frozen state to actor! (and redefine update() to do nothing there! how cool is that?)
osuf oboys wrote:Something that that is not clear to me is how one gets callbacks in a Game class, without having to rely on actors or GUI elements
Yes, I know what you mean. You might not like my responses very much but - you rely on actors. You can do two things:
  • Make Game a subclass of Actor. Then you can do Game:onkeypress() and handle keys there. Problem with this approach is that you have to be careful not to do funky things with the game object- i.e. passion.Actor:applyToAllActors('destroy') would end the game, when you just wanted to destroy all actors instead.
  • Use an invisible actor for keyboard handling. You don't even need to define a class for it - just add an onkeypress function to the instance:

Code: Select all

keyHandler = passion.Actor:new()
keyHandler.onkeypress = function() ... end
This way your Game is "more inmune" to Actor:applyToAllActors stuff.
osuf oboys wrote:Perhaps just a general register() function which allows callbacks to be sent to an object; be it a Game, non-Actor class, or otherwise?
I've thought about something like this, but for different reasons: efficiency. When the key 'space'(for example) is pressed, onkeypress is invoked on every actor, even if they "do not care" about space, and only "care" about "enter". But that's something I'll address if I run into performance issues, not before.
osuf oboys wrote:This is a simple solution but I do not like this framework of direct input-model coupling; not even for Actors, as they are now.
I'm sorry to hear that. I'll try to make it more attractive :ultraglee: No, seriously. If you don't like a feature, you can just deactivate it. For example, you can define love.onkeypress() so it does other things (register listeners, etc). The rest of PÄSSION will still be available. No hard feelings.
osuf oboys wrote:For instance, it is complicated to let the state of the Game influence Actors (is this the "states in states" deal?).
Maybe we are conceiving two different types of Game object. On my head, the Game object just says "actors, create!", "actors, destroy!" depending on the status he is - only enterState and exitState callbacks are used, most of the time. He's like a lazy dictator; he doesn't even track the input directly, he has a subdit that does it.

keyListener: boss, somebody has pressed esc. We should return to the menu
Game: You are right keyListener. Allright, everyone just DIES now! - except you, keyListener (boum) Now, actors used on the menu, create yourselves. Now, start working! Ok, I'm going back to sleep.

So, the Game just creates and destroys actors, and they do their stuff.

Your Game object seems more similar to the Director of Left 4 Dead - Constantly controlling and plotting with the actors. That is still possible anyway. Let's say that you create a game object (called director on this case) that has a BeVeryMean state. You could do this:

Code: Select all

local BeVeryMean = Director:addState('BeVeryMean')
function BeVeryMean:enterState()
  musicPlayer:transitionToCreepyMusic()
  Bee:applyToAllActors('growHorns', 13) -- invoke methods on all actors, with parameters
  Zombie:applyToAllActors('gotoState', 'nightmarish') -- change the states of actors
  Enemy:applyToAllActors('shout') -- all enemies (zombies and bees) will emit a fearsome cry now!
end
The applyToAllActors is a really powerful method. It should allow you to control your actors easily from your Game object.
osuf oboys wrote:If we bring up the main menu, we do not want callbacks to be sent to the Actors unless Games wants it to be that way.
Well, on my example it will not matter because all the that aren't supposed to receive callbacks will be destroyed. Game is supposed to be a bastard ( ok, they could also be just frozen and invisible, and stored in a pool, to save creation time. Actor pools is another thing I'm considering doing)
osuf oboys wrote:Perhaps something more like ËNVY's observer pattern (http://en.wikipedia.org/wiki/Observer_pattern) where Actors are registered as observers to Game and game an observer of love's callbacks?
Thank you. I'll try to give both a look during this weekend. Unfortunately this week and the next one will be particularly difficult for me (Real Life work...). But if I see something that can improve PÄSSION, I'll add it.
osuf oboys wrote:Sorry about all the criticism, the framework is really good. Keep it up!
By all means, keep your criticism coming! I think criticism helps. I enjoyed your comments very much. My apologies to everyone for the überlong post.
When I write def I mean function.
osuf oboys
Party member
Posts: 215
Joined: Sun Jan 18, 2009 8:03 pm

Re: PÄSSION: object-oriented LÖVE

Post by osuf oboys »

kikito wrote: Thanks! I'm trying hard to make it easy?
It is.
kikito wrote: Not sure what you mean by "stacks of states".
statefulObject.pushState("game")
statefulObject.pushState("menu")
statefulObject.popState()

In other words, the option to go back to whatever was the previous state instead of specifying a new state. The ability to check the previous state also allows some customization, e.g. for animations or game logic.

For instance, one could have the menu render the game in the background while keeping it paused. The way you use states, that old state must be stored and be available for calling. As the primary state of the object, the menuState recieves a draw function and, in its draw function, passes along to the previous state, and then draws over it (think of a semi-transparent menu or a greyed-out game). The menu would however not pass input or update functions along.

Another example would be to add a frozen state to an already existing state. Chances are that you will want to go back from that state later. This could be messy if actors were in different states.

Another trajectory is to allow multiple states - this would essentially correspond to objectifying properties of objects; executing all functions associated with the states. A typical game would then rely on helper functions that are overloaded instead of the main functions, e.g. changing damage calculations without changing the entire battle script. Sort of like script modules. Aspect-oriented programming would get rid of some issues then. Powerful but this can become really messy.
kikito wrote:You mean states with states inside? I call those "stateful states" :). It is in theory possible. The trick is that addState takes a second parameter that is the superclass of the state you are creating.
What would be a practical application of that over, say, using a single layer of states but multiple such?
However, I couldn't see any way of providing a simple interface for multi-words, while the mono-word interface is quite streamlined.
If you want to keep it out of the way and not provide it as an argument, you can let it be a parameter in PÄSSION (or a Game), currentWorld/newActorsWorld. Not a important feature for now though.
I'll check that out. However, the "frozen" part is probably going to go. I'll add a Frozen state to actor! (and redefine update() to do nothing there! how cool is that?)
Nice. Perhaps a bit unrelated but what I find the most intuitive is not for states to have their own set of functions, but rather to overload functions (blanking them to deactivate, e.g.). That way, when the actors are "frozen", they could still be drawn without having to respecify the draw function. Similarly for applying a new state on another (the stacks).
Yes, I know what you mean. You might not like my responses very much but - you rely on actors. You can do two things:
  • Make Game a subclass of Actor. Then you can do Game:onkeypress() and handle keys there. Problem with this approach is that you have to be careful not to do funky things with the game object- i.e. passion.Actor:applyToAllActors('destroy') would end the game, when you just wanted to destroy all actors instead.
  • Use an invisible actor for keyboard handling. You don't even need to define a class for it - just add an onkeypress function to the instance:

Code: Select all

keyHandler = passion.Actor:new()
keyHandler.onkeypress = function() ... end
This way your Game is "more inmune" to Actor:applyToAllActors stuff.
Could be possible but then you should perhaps make it a class for callbacks (Game, GUI, etc) and another under that for more logic (objects in a game). Not sure 'Actor' is the right name. The first part should probably also be a module (included) rather than a class? This still essentially just becomes the observer pattern and the primary issues are resolved with it.

By the way, have you thought about saving&loading, and sending over networks? You want this too to be simple and by storing userdata in the tables, this might not be possible.
I've thought about something like this, but for different reasons: efficiency. When the key 'space'(for example) is pressed, onkeypress is invoked on every actor, even if they "do not care" about space, and only "care" about "enter". But that's something I'll address if I run into performance issues, not before.
That's actually the problem with the Observer pattern, plenty of overhead to send those messages. Your applyToAllActors as it is right now is a special case of this pattern.
Maybe we are conceiving two different types of Game object. On my head, the Game object just says "actors, create!", "actors, destroy!" depending on the status he is - only enterState and exitState callbacks are used, most of the time. He's like a lazy dictator; he doesn't even track the input directly, he has a subdit that does it.

keyListener: boss, somebody has pressed esc. We should return to the menu
Game: You are right keyListener. Allright, everyone just DIES now! - except you, keyListener (boum) Now, actors used on the menu, create yourselves. Now, start working! Ok, I'm going back to sleep.

So, the Game just creates and destroys actors, and they do their stuff.
I can explain how I see it. I agree but only as far as game logic goes. When it comes to input and output, we want to make the interface to come with as little coupling as possible, preferably relying on the Model-View-Controller design. Doing things like checking if a key is pressed in an Actor callback and reacting accordingly is simple in the beginning but that can become pretty obscene, will screw things up with things like AI and networking, and generally preventing a lot of freebies that comes with good design.

One can still take the MVC design and try to fit it into a philosophy of simplicity. Methods like "keypressed" etc can be seen as methods in the "controller" part of the design, conveniently located in the class itself. It should however still be seemless to accept input from other places and, say, for moving right, the keypressed method should not update the location itself; it should call a method like moveRight or orderMoveRight. The keypressed method is however still just a function in the controller part and is also subordinate to it as I am conceiving it now.

I cannot think of an example where this subordination part would be superior to your states+stacks (+some messages for in-game communication) though, putting those efficiency concerns aside.
Well, on my example it will not matter because all the that aren't supposed to receive callbacks will be destroyed. Game is supposed to be a bastard ( ok, they could also be just frozen and invisible, and stored in a pool, to save creation time. Actor pools is another thing I'm considering doing)
Should there have to be this much work to just bring up a menu though when you already have all of the data prepared?
If I haven't written anything else, you may assume that my work is released under the LPC License - the LÖVE Community. See http://love2d.org/wiki/index.php?title=LPC_License.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: PÄSSION: object-oriented LÖVE

Post by kikito »

statefulObject.pushState("game")
statefulObject.pushState("menu")
statefulObject.popState()
Oh! That! I remember I saw something like that when I was "looking for inspiration" on UnrealScript. I made a mental note to include it.. and then I forgot. Thanks for remembering it to me!
What would be a practical application of that over, say, using a single layer of states but multiple such?
I'm not entirely sure, I must admit. The whole idea of states is keeping copied code to the minimum possible. But I wanted to be have those advantages inside states too:

Code: Select all

-- notation: Class { State { functions or substates } }
Enemy {
  Attacking {
    f1()..
    f2()..
    WithWeapon {
      f2() -- do more damage
    }
  }
  Running {
  }
}

Ninja < Enemy {
  Attacking {
    WithWeapon {
      f2() -- instant kill
    }
  }
}
The thing is that I don't like using "switch" because they don't play nicely with inheritance. On the code above, if Attacking wasn't stateful, then f2() would have a if(hasWeapon) then do X else do Y. Then, Ninja.states.Attacking.f2() would look like this: if(hasWeapon) do instantKill else do Y. I don't like the underlined parts. However, I haven't thought this through yet.
Nice. Perhaps a bit unrelated but what I find the most intuitive is not for states to have their own set of functions, but rather to overload functions (blanking them to deactivate, e.g.). That way, when the actors are "frozen", they could still be drawn without having to respecify the draw function. Similarly for applying a new state on another (the stacks).
Hmm so you mean that methods are searched "through the state stack" before searched on the subclass? That's ... intriguing. Powerful. Dangerous. I like it. If I can't come out with some scenario where this is a bad idea, I'll include that feature.
Could be possible but then you should perhaps make it a class for callbacks (Game, GUI, etc) ... (snip) ... MVC design ... I cannot think of an example where this subordination part would be superior to your states+stack.(snip)
I have read the wikipedia link you gave. I still have to study how ËNVY does its thing. I liked what I saw on wikipedia.

*BUT* I want something simple and easy to grasp. The thing I like about my current approach is that it is very easy to understand to someone familiar with LÖVE, or learning it. If I'm going to lose that, it must be to something as easy to use as that.

Code: Select all

Should there have to be this much work to just bring up a menu though when you already have all of the data prepared?
Sorry, which work are you referring to? I didn't get that last part.
When I write def I mean function.
osuf oboys
Party member
Posts: 215
Joined: Sun Jan 18, 2009 8:03 pm

Re: PÄSSION: object-oriented LÖVE

Post by osuf oboys »

kikito wrote:statefulObject.pushState("game")

Code: Select all

-- notation: Class { State { functions or substates } }
Enemy {
  Attacking {
    f1()..
    f2()..
    WithWeapon {
      f2() -- do more damage
    }
  }
  Running {
  }
}
Well, as I see, stacks do that.
*BUT* I want something simple and easy to grasp. The thing I like about my current approach is that it is very easy to understand to someone familiar with LÖVE, or learning it. If I'm going to lose that, it must be to something as easy to use as that.
True, the observer pattern could be taken care of by a different framework. Although, I think for most projects, it would mostly follow a simple pattern that would not at all use the full power - just registering new objects as listeners to some parent object, e.g. a game. Could even be done implicitly. I certainly think for the simplicity principle, you must at least make it possible to register classes in PÄSSION (/include a class) to receive LÖVE callbacks.
Sorry, which work are you referring to? I didn't get that last part.
Having to send to all actors that they should be frozen, invisible, and stored in a pool, just to bring up an options menu, certainly seems like an overly complicated solution.
osuf oboys wrote: If we bring up the main menu, we do not want callbacks to be sent to the Actors unless Games wants it to be that way.
kikito wrote: Well, on my example it will not matter because all the that aren't supposed to receive callbacks will be destroyed. Game is supposed to be a bastard ( ok, they could also be just frozen and invisible, and stored in a pool, to save creation time. Actor pools is another thing I'm considering doing)
[/quote]
If I haven't written anything else, you may assume that my work is released under the LPC License - the LÖVE Community. See http://love2d.org/wiki/index.php?title=LPC_License.
Post Reply

Who is online

Users browsing this forum: No registered users and 69 guests