
Nathan Hüsken wrote:
On 12/08/2012 10:32 AM, Heinrich Apfelmus wrote:
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.
Maybe discussing a concrete example could be very helpful. Could you give a minimal example that still contains the key difficulties?
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.
(Sorry for the late reply.) I see, that's a nice example. Indeed, if you try to model this situation with dynamic collections galaxyModel :: Behavior [AsteroidModel] galaxyView :: Behavior [AsteroidView] then you have to keep track of IDs in some way, because a change in the collection of asteroid models needs to be reflected in a corresponding change in the collection of asteroid views. identifier :: AsteroidModel -> ID eCollisions :: Event [ID] eCollisions = collisions <$> galaxyModel <@ eTick where collisions asteroids = [identifier a | a <- asteroids, b <- asteroids, a `collides` b] galaxyModel = accumB initialAsteroidModels $ removeFromList <$> eCollisions galaxyView = accumB initialAsteroidViews $ startExplosions <$> eCollisions That said, do note that any significant use of pointers in an imperative program translates to the use of identifiers in the purely functional variant. This is very much *independent* of FRP! In other words, if you find that giving certain game objects an "identity" is a good way to structure your code, then you need to use identifiers, regardless of whether you use FRP or not. Of course, as you note in another message, there are other ways to structure this code. For instance, the second idea would be to use a data type type Asteroid = (Maybe AsteroidModel, AsteroidView) which represents live asteroids as (Just positionEtc, view) and dead asteroids as (Nothing, explosionView) . Then again, one unsatisfactory point about this approach is that an exploding asteroid is now represented explicitly in the game logic as a Nothing value. A third approach would be to keep an explicit list of explosions. data AsteroidModel = AsteroidModel { view :: AsteroidView, pos :: Position } data AsteroidView = AsteroidView { rotation :: Angle } data Explosion = Explosion { posExp :: Position } galaxyView :: Behavior ([AsteroidView], [Explosion]) galaxyView = (,) <$> (map view <$> galaxyModel) <$> explosions explosions = accumB [] $ startExplosions <$> eCollisions You do need an event to communicate which asteroids have exploded, but an exploding asteroid will not appear in galaxyModel anymore. Instead, it will be added as an "anonymous" explosion to the rendering logic. (In a sense, the asteroid views with the state variables dead = false and dead = true have been split into different types.) I find the third approach to be quite satisfactory. What is your opinion? The more I think about this example, the more I think that the underlying difficulty is not FRP, but the use of pointers / identities. Best regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com