It took me about 7 years to unlearn the OOP thinking and the major road blocker for me was that I couldn’t think of an alternative. Putting data into a class and then have methods exclusively operating on the data, ignoring any wider context just felt so natural do to.
The first thing that helped me get out of that thinking a little bit is the talk Practical Optimizations by Jason Booth. It introduced me to the idea that I only need one instance of a class to control the behaviour of a whole range of “objects”. This already reduced the complexity of my programs because because it removed the necessity of an external place to loop over all of my objects. It also made it easier to have those objects interact with each other, because the class already knows all of them.
The second step I needed was Caseys talk “The big OOPs”. For me the most significant part of that talk was that he demonstrated 3 examples of an Entity Component System and how it flips the encapsulation. Having those explicit examples instead of just vague explanations have been very important for me to understand the pattern.
My current programming style looks a bit like this:
- I have a “Universe” struct that holds pointers to maps of the components my system uses. Something like this:
EntityID :: distinct i32
Universe :: struct {
entities: [dynamic]EntityID,
next_id: EntityID,
transform: map[EntityID]Transform,
movement: map[EntityID]Movement,
physics: map[EntityID]Physics,
display: map[EntityID]Display,
// ...
}
- All my functions have a pointer to the Universe they work on, e.g.:
update_physics :: proc(u: ^Universe, dt: f32) {
// calculate physics for each entity with Physics
}
- Finally, my main loop just calls the different systems:
u: Universe
dt: f32
for {
get_user_input(&u)
update_actions(&u, dt)
update_npc_movement(&u, dt)
update_physics(&u, dt)
// ...
draw(&u)
}
My OOP brain still thinks of this as a single big object where all functions are methods for that object, but they really are just functions, so I can group them together thematically in separate files and within each function it’s easy to pull out the data I need and work on that data.
So maybe that helps as well giving you another example of how you could structure your program in a non-OOP way that allows you to reuse some part of your OOP thinking.