My first cent - never forget that the purpose of all these software design patterns is to make life easier for the people actually writing and maintaining the code. They aren’t for performance, or for software correctness, etc. If you’re working on a solo project, use the patterns in whatever way makes the most sense to you.
Second cent - I’ve been using a pattern like this lately, which is MVC-derived and pretty useful. I have four types of classes - Game Objects (Model), Renderers (View), Managers (Controller - pt 1), and Controllers (Controller - pt 2).
Game Objects for the most part are just data structures. I usually have one class of these per entity in my world - in my case characters, towns, dungeons, etc. Getters and setters abound
I typically have one renderer for each ‘screen’ - so one for the menu, one for drawing the ‘in town’ display, one for drawing the ‘world map’, etc. Renderers just takes a list of relevant game objects and draws them to the screen. They have logic, but it’s all focused on rendering - culling, etc. Right now I just have renderers running in their own thread, but you could synchronize them with the game loop. Renderers may have data structures that parallel Game Objects (such as vertex data), but I try not to put that data into the Game Objects themselves.
Managers conceptually should contain all the game logic. Lately I’ve been orienting these around game states - which correspond to screens - same as renderers. There is one master manager that keeps track of game state and calls an update method on any active ‘children’ managers.
What I call ‘Controllers’ are assigned to any autonomous entities in the game world, and are responsible for choosing actions for the entity they represent. So the player’s Game Object, and any monsters or NPC’s are each assigned a Controller. Controllers are responsible for querying the world around their assigned entity, formulating a good action, and returning that to whichever Manager class is asking for an action. Controllers have no ability to update the world - they just return instructions to a manager class such as ‘Walk North’ or ‘Open Door’. The manager class is responsible for checking the validity of this instruction and actually moving the game object or opening the door.
There is one special controller for the player that basically maps keyboard and mouse events to instructions in the controller ‘instruction set’, which may be returned to a manager class. If you’re making a turn based game, you have the player controller stall until the user enters an action. If you’re making a real time game, you have the player controller just return NOOP if the player isn’t pressing a button or whatever.
So the basic idea is that you have all these Game Objects that represent the things in your world. Alone these objects don’t do anything. You add on top renderers that show a list of game objects. The list of game objects they display may be limited by a manager class (to eliminate nearby but hidden enemies for example). You add on top of this a layer of manager classes which are responsible for updating the game state (Game Objects). Finally, controllers are responsible for suggesting to the manager classes how to update the game world.
Anyway, that’s the architecture I’ve been using lately. I’ve found this model very flexible and easy to extend. Let’s say I want to add gravity to my world - that belongs in the manager class ONLY. I may choose to enhance a controller class to compensate for gravity, but that’s an independant chunk of work. Let’s say I want to improve pathfinding - I add that to a controller class. If I want to switch to a different rendering engine, I swap out the renderer class for the relevant game state(s). If I want to make the game network enabled, I just create a new proxy controller class which communicates across to some client over the network (which is of course not at all a trivial task).