type alias Body =
{ id : Int
, mass : Float
, position : Vector2D
, velocity : Vector2D
, radius : Float
, bodyType : BodyType
}
type BodyType
= Conceptual
| Planet { gravity : Float }
| Ship
{ rotation : Float
, rotationSpeed : Float
, propulsion : PropulsionType
}
| Projectile
{ damage : Int
, lifetime : Float
}
type PropulsionType
= Newtonian { thrust : Float }
| LittleGrayMenTech { movementIncrement : Float }
One of the obstacles with game dev in FP languages have reportedly been that it’s hard to model game loops without OO since so many actions rely on altering various similar aspects of the various game entities.
In making Space Pew Pew, I hit a bit of a wall early on wrt making records that have similar fields (everything you see in Body), because whenever I needed to update the game state, I had different lists of entities (like ships, planets, projectiles, etc). I had to extract the similarities, then update the separate lists again. This immediately looked like it was going to be a tonne of code.
So what Claude and I came up with is what you see above. If there’s a name for this pattern (or anti-pattern), please let me know.
Basically I start with the common fields, then add a field which links to the specialization information that might be available. So everything is a body with position, velocity, etc. Then some of the things are planets or ships or projectiles. Then the ships have varying means of propulsion, like rocket based Newtonian means or LGM tech which has them move without gaining velocity.
This allows me to do stuff like this:
ship_propel : Body -> Body
ship_propel body =
case body.bodyType of
Ship ship ->
case ship.propulsion of
Newtonian { thrust } ->
body |> applyForce (angleToV thrust ship.rotation)
LittleGrayMenTech { movementIncrement } ->
{ body
| position = addV body.position (scaleV movementIncrement (angleToV 1 ship.rotation))
, velocity = { x = 0, y = 0 }
}
_ ->
body
So I can call ship_propel on any body and it will either apply the propulsion logic and give me a new record, or just leave the record as is.
Would love to hear the community’s thoughts on this, especially if I might it a wall with this approach. The game structure is going to get about 10 times more involved than it is right now, so your feedback is valuable.