
On 12/08/2012 10:32 AM, Heinrich Apfelmus wrote:
Nathan Hüsken wrote:
Heinrich Apfelmus wrote:
In that light, the separation seems straightforward to me. Given the time-varying values that represent game objects,
bSpaceShipPosition :: Behavior Position bAsteroidPositions :: Behavior [Position] bTime :: Behavior Time
you can transform and combine them into a graphic, for instance like this
bSpaceShipPicture :: Behavior Graphic bSpaceShipPicture = blinkenLights <$> bTime <*> bSpaceShipPosition
bAsteroidPictures = map drawAsteroid <$> bAsteroidPositions
bPicture = overlay <$> ((:) <$> bSpaceShipPicture <*> bAsteroidPictures)
In other words, you just combine old time-varying values into new ones, much like you would combine combine graphical plots. Also note that you can add animation a posteriori; it doesn't have to be part of the values representing a space ship.
Yes, but these examples are extremely simple. The animation has no internal "state". What if every Asteroid also has a animation state (which I would want to add a posteriori) and can be destroyed. Than the connection between the asteroids "game logic" value, and "rendering" value needs some kind of bookkeeping to be maintained.
Fair enough, but I don't see how this can be fitted into a general pattern. If the animation "state" is coupled tightly to the game logic "state", then the question whether the animation is part of the game logic or not does not have a clear answer anymore. Hm.
I see it like this: The "logic state" may not depend on the "rendering state". If for example the animation of an asteroid changes how or when objects collide with it, than the animation is part of the logic. If however the physics of the game treat the asteroid as a object whose shape does not change depending in the animation, than the animation is part of the "rendering state". So the coupling may only be logic->rendering.
You mentioned that in an imperative setting, you would use something similar to the observer pattern. Judging from the wikipedia page http://en.wikipedia.org/wiki/Observer_pattern, it seems to me that this is just the Event type, though, so I don't understand how this helps with the problem at hand.
Yes, you are right. The problem at hand is something one has to deal with when using the observer pattern. Only in C++ I do not find it hard to do.
Maybe discussing a concrete example could be very helpful. Could you give a minimal example that still contains the key difficulties? Maybe a collection of asteroids that float in space, can be added or removed with a button click and who are animated as rotating rocks, all starting in a certain position when they are created? How would you use the observer pattern in this case?
I put a pseudo C++ example below the mail. I use the terms "model" and "view" for the game logic and rendering respectively. The example is a little different. Asteroids explode when they collide. The moment asteroids explode, they are removed from the model (the game logic) while in the view (rendering) they still exist until the explosion animation is over. As you said, this basically is sending messages from the Model (in the observer pattern called Observable) to the view (Observer). The main difficulty I have is how to send the messages from the correct model to the correct view. In C++ this is done by keeping pointers. Simply assigning IDs would work, but than I would have to always pass a map from the model to the view, and I feel like (also I have little experience with this), that this approach is not very scalable. Best Regards, Nathan =========== The C++ example, its not complete, but I believe it captures the idea. class GalaxyModel { GalaxyView* view; list<AsteroidModel> asteroids; void addAsteroid(Position pos) { asteroids.push(AsteroidModel(pos)); view->onNewAsteroid(asteroids.back()); } void update() { for (a in asteroids) { a.update(); } for(a in asteroids) { for (b in asteroids) { if (collide(a,b)) { a.explode(); b.explode(); asteroids.remove(a); asteroids.remove(b); } } } } }; class AsteroidModel { AsteroidView* view; Position pos; update() { updatePosition(); view->onNewPos(pos); } explode() { view->onExplode(); } }; class GalaxyView { list<AstroidView> asteroids; void onNewAsteroid(AsteroidModel* model) { asteroids.push(AsteroidView(model)); } void update() { for (a in asteroids) { a.update(); if (a.dead) asteroids.remove(a); } } } class AsteroidView { float rotation; bool dead = false; void onExplode() { setExplodeAnimation(); } void onNewPos() { //Store position} void update() { rotation += 1.0; if (explodeAnimationOver()) { m_dead = true; } } }