> but something about OO programming always seemed unnecessary for me.
I actually learned to program making mods for Unreal Tournament (1999). I'd love to hear more about your feelings about how OO seems unnecessary.
For me, UT was a prime example of OOP (both the general bundling data and functions together as well as inheritance) being used to simplify things. Key is that any instance of 'actor' is a single physical object rendered in the world. A subclass of 'actor' is "inventory" - items that can be picked up and held by players. Below that is the "weapon" class; which a player can switch between, fire, and are rendered in 1st and 3rd person views.
Obviously you don't need to be OO to pull off a game; many FPS games are not. But it makes things so easy to handle. If you build a new weapon, all of the functions to handle picking it up, rendering it, etc. are written in super-classes. But if you want, you can over-ride those. Furthermore, the weapon interface is well-defined to external entities; e.g. the 'playerpawn' knows his weapon has a 'fire' method.
I'm not sure how you could build an equally powerful and customizable engine without using OOP techniques at some level. Sure a weapon could be a struct, but how would you know what associated fire() method you would call? Unless you want a bunch of non-extensible case statements, you'd need a function pointer in the struct itself.. and once you couple the data and methods, you are getting close to OOP.
One thing I loved about UT compared to Quake-engine games was its extensibility. OOP allowed reflection; just 'spawn weaponpackage.weapon' and you can use a custom weapon. This also allowed gameplay "mutators" to blend together. Quake games in the day required a custom -game argument at startup; and consequently custom content could not easily be merged together.
Of course, this is how I learned to program so my brain may be overly-wired that way.