We use singleton a lot, though that's not really anything special.
One that may not be as obvious: we don't use inheritance for most objects that are part of the gamestate (we do use it for less performance-critical stuff), but rather for things like GameEntity or ForegroundObject (the "character/monster/tree/etc" class from AVWW and the "ship/building/etc" class from AIW, respectively) we have something like:
enum GameEntityType
- has one value for each distinct type of entity in the game, so "Darrell" is one of the npc/player-entity types, "SkelebotSniper" is the Skelebot Sniper, and "Cedar" is one of the cedar tree types.
class GameEntityTypeData
- has exactly one instance per GameEntityType value, and each instance has all the fields about that particular type of entity that never change during the game
- this has a static Initialize() method that is called when the application is first run, that calls the ctor once per GameEntityType value and stores them in a "static GameEntityTypeData[] Lookup", which can be retrieved from like "GameEntityTypeData.Lookup[(int)GameEntityType.SkelebotSniper]".
class GameEntity
- corresponds to an actual in-game entity, so if there are 4 skelebot sniper on your screen there are actually (bopping around in RAM somewhere) 4 instances of GameEntity whose this.TypeData.Type value is GameEntityType.SkelebotSniper
We use the Type/TypeData/Instance pattern all over the place. Sometimes we just do Type/TypeData for, say, PurchaseType/PurchaseTypeData; each PurchaseType enum corresponds to a specific transaction that is possible in the opal guardian store. But there's no "Purchase" Instance class because we had no need to have an object representing the transaction: the TypeData provides all the necessary information to handle it from start to finish, and the resulting change in your inventory is all that needs to happen, no additional intermediate or residual state.