Here's yet another take

It's not unlike Grozzlers, but more C++-ey and less granular.
* Entity has a transform, and you can refer to it with a salted pointer which is robust against entity deletion. World allocates entities from a pool.
* At init time, like Grozzler I provide World with a list of Systems. Each System implements one Facet (component) type. System allocates Facets from a pool. There's a standard system template which is useful for simple components, or you can write your own.
* At runtime, World updates each System in order, and each System updates its facets in pool (linear in memory) order. When Systems hook into third party code like Box2D, System::Tick will update the state of the third party world (or whatever) as necessary.
* Each Facet has a pointer to a block of constant "definition" data, and to create an Entity the World walks through a list of types and definition pointers.
* Facet code can send events to other entities - these are arbitrary structs with type IDs and get buffered up. After all the systems have updated they all get fed this frame's events, and ignore them or dispatch them to Facets as appropriate. This is the weakest link at the moment - it works, but it's high-latency and feels like it's barely enough.
* I also still have two crutches - you can get Facets from entities by type, and Facets can refer to each other within the same entity. This seems a bit messy and I'd be happier if I didn't need this - maybe if I can get a more solid design for events I won't?
I wasn't sure if anyone had really answered Tommo's question about where the game logic goes? In Unity this would be in a script component that you write yourself, which responds to events and does stuff with the built-in components in the entity.
I tend to have some game-specific C++ components for this kind of things, particularly for the player-controllable object where the messy logic goes. They do the high level gameplay glue code which would normally be in e.g. Player::Update(). I'd do the character animation switch there to start with.
Another real example: my simple balloon-ey game from the Versus compo has the following components, all C++ classes:
Built-in engine components:
* None

Add-on package components:
* box2d::Body - has velocity, physics body, collision shape
* box2d::Sensor - has collision shape, tracks things which overlap it
* box2d::Joint
* fx::Trigger - fires effects (sounds, particles in future) in response to cues.
Game-specific components:
* Bullet - does damage on impact, explodes, creates squib entities etc.
* Explosion - does damage, pushes bodies
* Health - takes damage, kills entity
* IPilot - provides yoke data
* PlayerPilot - is-a IPilot, maps controls to yoke
* Sprite - draws a simple sprite
* Weapon - fires out entities, handles ammo + reload timer
* Balloon - glues stuff together, maps yoke and internal state into body forces. Bit hacky.
Some of these are candidates for moving to a "game2d" helper package, like Bullet, Weapon and Health, and as they mature that might just happen.
NB: There's nothing to stop you cross-cutting the architecture and talking to the systems directly when pragmatism suggests it might helpful. E.g. I render the ropes (joints) which tie the balloon to the basket by iterating the JointSystem directly and drawing lines :p
Here's the definition data for the game to hopefully show how things are built up. This gets compiled into a memory image so all the quoted strings which refer to other things in the file, or to external files like assets, are just const pointers to ready-to-eat data on the C++ side.
SILO
{
BalloonShape
{
type= "sil::box2d::CircleShape"
radius = 3.0
density = 0.119
restitution = 0.1
category = 0x0008
mask = 0xffff
}
BasketShape
{
type = "sil::box2d::PolygonShape"
density = 2.0
restitution = 0.5
category = 0x0004
mask = 0xffff
vertex_count = 4
vertices
{
{ x = -0.75 y = -0.75 }
{ x = 0.75 y = -0.75 }
{ x = 0.75 y = 0.75 }
{ x = -0.75 y = 0.75 }
}
}
BombShape
{
type= "sil::box2d::CircleShape"
radius = 0.4
density = 4.0
restitution = 0.75
category = 0x0002
mask = 0xffff
}
CaltropShape
{
type = "sil::box2d::PolygonShape"
density = 4.0
restitution = 0.25
category = 0x0002
mask = 0xfffd
vertex_count = 3
vertices
{
{ x = 0.0 y = 0.3 }
{ x = -0.261 y = -0.15 }
{ x = 0.261 y = -0.15 }
}
}
GroundShape
{
type = "sil::box2d::PolygonShape"
category = 0x0001
mask = 0xffff
vertex_count = 4
vertices
{
{ x = -250.0 y = -12.5 }
{ x = 250.0 y = -12.5 }
{ x = 250.0 y = 12.5 }
{ x = -250.0 y = 12.5 }
}
}
GroundChainShape
{
type = "sil::box2d::ChainShape"
category = 0x0001
mask = 0xffff
chain
{
{ x = -500.0 y = 12.5 }
{ x = 500.0 y = 12.5 }
}
}
BlastShape
{
type = "sil::box2d::CircleShape"
category = 0x0001
mask = 0xffff
radius = 8.0
}
BalloonBody
{
type = "sil::box2d::BodyData"
linear_damping = 0.5
angular_damping = 1.0
shape = "BalloonShape"
}
BasketBody
{
type = "sil::box2d::BodyData"
linear_damping = 0.25
angular_damping = 0.5
shape = "BasketShape"
}
BombBody
{
type = "sil::box2d::BodyData"
linear_damping = 0.2
angular_damping = 0.2
shape = "BombShape"
}
CaltropBody
{
type = "sil::box2d::BodyData"
linear_damping = 0.2
angular_damping = 0.2
die_after_sleep = 2.0
shape = "CaltropShape"
}
GroundBody
{
type = "sil::box2d::BodyData"
shape = "GroundChainShape"
}
BombDropper
{
type = "WeaponData"
bullet = "Bomb"
angular_speed_variation = 3.0
max_ammo = 3.0
recharge_rate = 0.5
refire_delay = 0.5
fire_cue = 3
}
BombBullet
{
type = "BulletData"
explode_on_delay = 3.0
explode_on_impact = 0x0004
squib = "Explosion"
}
CaltropDropper
{
type = "WeaponData"
bullet = "Caltrop"
angular_speed_variation = 1.5
max_ammo = 6.0
recharge_rate = 1.0
refire_delay = 0.2
fire_cue = 4
}
CaltropImpactEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/caltrop_impact.wav"
volume = 0.1
}
CaltropEffects
{
type = "sil::fx::TriggerData"
actions
{
{ cue_id = 1 effect = "CaltropImpactEffect" }
}
}
CaltropBullet
{
type = "BulletData"
damage_on_impact = 0x0008
impact_damage_penetrating = 2.0
impact_cue = 1
}
BombExplosion
{
type = "ExplosionData"
min_impulse = 8.0
max_impulse = 25.0
lifetime = 0.2
}
BombExplosionSprite
{
type = "SpriteData"
sequence = "resources/graphics/blast.frm"
x_scale = 8.0
y_scale = 8.0
}
BombExplosionSensor
{
type = "sil::box2d::SensorData"
shape = "BlastShape"
}
MyBalloon
{
type = "BalloonData"
}
MyPilot
{
type = "PlayerPilotData"
}
MyHealth
{
type = "HealthData"
max_health = 40.0
}
BalloonSprite
{
type = "SpriteData"
sequence = "resources/graphics/balloon-0.frm"
x_scale = 1.05
y_scale = 1.05
}
BasketSprite
{
type = "SpriteData"
sequence = "resources/graphics/basket.frm"
x_scale = 1.05
y_scale = 1.05
}
BombSprite
{
type = "SpriteData"
sequence = "resources/graphics/bomb.frm"
}
CaltropSprite
{
type = "SpriteData"
sequence = "resources/graphics/caltrop.frm"
y_scale = -1.0
}
LeftSpring
{
type = "sil::box2d::JointData"
x1 = -2.5
y1 = -1.3
x2 = -0.75
y2 = 0.75
}
RightSpring
{
type = "sil::box2d::JointData"
x1 = 2.5
y1 = -1.3
x2 = 0.75
y2 = 0.75
}
MiddleSpring
{
type = "sil::box2d::JointData"
x1 = 0.0
y1 = -1.3
x2 = 0.0
y2 = 0.75
}
CaltropFireEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/caltrop_drop.wav"
volume = 0.2
}
BombFireEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/bomb_drop.wav"
}
BalloonBumpEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/balloon_bump.wav"
volume = 0.4
}
BasketBumpEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/basket_bump.wav"
volume = 0.1
}
BurnerEffect
{
type = "sil::fx::EffectData"
clip = "resources/sound/burner.wav"
loop = 1
volume = 0.1
intensity_rate = 1.2
}
BalloonEffects
{
type = "sil::fx::TriggerData"
actions
{
{ cue_id = 1 effect = "BasketBumpEffect" cooldown = 0.5 attached = 0 }
{ cue_id = 2 effect = "BalloonBumpEffect" cooldown = 0.5 attached = 0 }
{ cue_id = 3 effect = "BombFireEffect" cooldown = 0.0 attached = 0 }
{ cue_id = 4 effect = "CaltropFireEffect" cooldown = 0.0 attached = 0 }
{ cue_id = 5 effect = "BurnerEffect" cooldown = 0.0 attached = 1 }
}
}
Dirigible
{
type = "sil::game::Recipe"
ingredients
{
{ data = "BalloonBody" }
{ data = "BasketBody" offset { x = 0.0 y = -5.5 } }
{ data = "LeftSpring" anchors { value = 0 value = 1 } }
{ data = "RightSpring" anchors { value = 0 value = 1 } }
{ data = "MiddleSpring" anchors { value = 0 value = 1 } }
{ data = "MyBalloon" }
{ data = "MyHealth" }
{ data = "MyPilot" }
{ data = "BombDropper" anchors { value = 1 } }
{ data = "CaltropDropper" anchors { value = 1 } }
{ data = "BalloonSprite" anchors { value = 0 } }
{ data = "BasketSprite" anchors { value = 1 } }
{ data = "BalloonEffects" }
}
}
Bomb
{
type = "sil::game::Recipe"
ingredients
{
{ data = "BombBody" }
{ data = "BombBullet" }
{ data = "BombSprite" anchors { value = 0 } }
}
}
Caltrop
{
type = "sil::game::Recipe"
ingredients
{
{ data = "CaltropBody" }
{ data = "CaltropBullet" }
{ data = "CaltropSprite" anchors { value = 0 } }
{ data = "CaltropEffects" }
}
}
Explosion
{
type = "sil::game::Recipe"
ingredients
{
{ data = "BombExplosionSensor" }
{ data = "BombExplosion" }
{ data = "BombExplosionSprite" }
}
}
Ground
{
type = "sil::game::Recipe"
ingredients
{
{ data = "GroundBody" }
}
}
}
I think it's probably all a bit (

) over-engineered, but it's been a good experiment thus far. It feels like good practice to play with this stuff at home since I don't get to touch much gameplay code at work at the moment.
Will