I think it is also highly dependent on how your overall application is structured.
You should ideally separate functionality as much as possible.
Naturally we all want to see a code example, so I'll give it my best shot. Though to be honest, this all means nothing without being related to your own specific examples. Perhaps just do some basic prototypes, and when it gets clunky (and it probably will), re-factor. Iterative design accomplishes so much more than if you try to plan it all at once; in which case you meet constant mental blocks; and may even lose motivation.
class Renderer;
class Mesh {
Vertex Data etc;
}
class WorldObject {
Position, Rotation
}
class Movable {
Velocity, Acceleration;
}
class Terrain {
WorldObject;
Mesh;
draw() { Renderer.draw(Mesh, WorldObject); }
}
class Entity {
WorldObject;
Mesh;
Movable;
draw() { Renderer.draw(Mesh, WorldObject); }
update() {
// update position based on velocity/accelerate
}
}
class World {
Terrain;
Entities;
draw() {
Terrain.draw();
foreach Entities
{
entity.draw();
}
}
update() {
foreach Entities
{
entity.update();
}
}
}
class Animation {
Animation data etc;
}
class AnimatedEntity : Entity {
Animations;
}
class Health;
class Gear;
class Character {
AnimatedEntity;
Health;
Gear;
}
class Situation {
Entities enemies;
Entities obstacles;
Position;
}
class AI {
Brain;
React(Character, Situation) { ... }
}
class NPC {
Character;
AI;
}
This could go on forever; the implementation of functions you described (spawn enemy etc), are just methods you can write that create these objects with particular properties, or modify them.
Obvious points, avoid code duplication, but don't go inheritance crazy, otherwise you will run into pitfalls really quickly because of cross-tree dependencies. I have greatly avoided inheritance here, simply to make a point, but if you find it appropriate it, I'm sure you can implement it differently.