OOP Design Dilemma

BlitzMax Forums/BlitzMax Programming/OOP Design Dilemma

I'm having second thoughts about the cleanest way to design all my types/classes in my game engine. For the sake of keeping it short and simple for people to see what I'm up to, I'll pick a basic example.

[code]
Type GameEngine
' DETAILS OF THE 3D ENGINE GO IN HERE
' AMONG MANY OTHER THINGS, OF COURSE...
End Type

Type GameCamera
' THIS NEEDS TO ACCESS THE CAMERA CLASS OF THE 3D ENGINE.
End Type
[code]

Ok, simple enough, I think. Keep everything neat and tidy in it's own class, but the Engine class has the inner workings of the 3d engine within. Perhaps a class for cameras.

Now my game camera class needs to use that class to position it's camera, and perhaps check for collisions with meshes. So hmm.. yeah. it'll need the mesh class instance too.

Would it be cleaner design to give the camera class ( and every other class ) copies of the handles it might need ( as globals / statics obviously, since they will be the same for every instance of the camera class ) or would ie be cleaner to ( as I currently do ) give the camera class ( and all similar classes ) a handle back to the engine, which it can then use to work with the camera and mesh classes.

Of course, if the Engine is a singleton ( well, a fake-singleton really, with all globals and functions, no fields or methods ) then it need not have the instance, it can just call the class itself to access the camera and mesh classes. But either way, it's kinda the same effect. Should the camera and other game object classes have duplicates for everything or should they ( as I currently have it ) go back to the "root" or "hub" Engine class?

I would personally do it something like this:
Global Engine:TEngine

Type TEngine
' when created, sets the Engine variable to this so other entities can easily access it
End Type

Type TEntity
' all the basic entity handling, positioning, etc
End Type

Type TCamera Extends TEntity
' your camera!
End Type


It's all personaly preference and using OOP to the best you can.

I personally prefer the "include" way to create packages which then are imported. That way, inheritance issues can be avoided and the design can be broken down to something "better controlable"

You can do what Dreamora says (I even would say you shuld), but also it's a very very good idea to derive all base objects from a base class, as Regular K says.

It's also a very good practicle to avoid cross referencing, I mean, if your 3D engine points to an instance of the camera object, don't make the camera object point to the base engine. This will keep everything clean to the garbage collector.

*very depending on your classes*, you might be able to use the event system to send around events and create event-listeners in the required classes.

Like, the cameraclass sends out an event when its coords change, it sends event.x, event.y and event.data (for z), event.source to identify and event.id to give the event type. Emit it. Next somewhere in the 3d engine an event listener picks up the event, and you can extract x y z again. This way, the 3d engine and camera class really don't need to know eachother. But it only works for some situations, might not work for yours. It might also require you to create your classes in a somewhat different way, to be prepared for all this sending around. For more info to send around you could try the .extra field.. (never done so, except for sending an own GUI object's helptext to one of my help-displayers)

small example of my point:

you can comment out either the creation of cl or cle .. the functionality is gone, but the program still runs. As you see, the cl and cle really don't know eachother, all communication is done by using events. Ofcourse this is a small example, it's not even perfect.. if you add another cle (cle2) at some different coord, then that one also reacts on changes automatically. I didn't include ID's to limit functionality to a given cle, couldn't be bothered to either.. it's just for demo'ing purposes!

SuperStrict
' "work together"-example

Const EVENT_COLORLIST_CHANGED:Int=$13370590
Const EVENT_COLORLIST_SETHOOVER:Int=$13370591
Const EVENT_COLORLIST_SETCURRENT:Int=$13370592
Const EVENT_COLORLIST_UPDATE:Int=$13370593
Const EVENT_REQUESTCOLORCHANGE:Int=$13370594

' --------------------------------------------------------------------------
' --------------------------------------------------------------------------

Type TColorlist
	Field r:Byte[8]
	Field g:Byte[8]
	Field b:Byte[8]
	
	Field current:Int=0
	Field hoover:Int=0
	
	Field newevent:TEvent=New TEvent	



	Method New()
		Local t:Int
		For t=0 To 7
			r[t]=Rnd(255)
			g[t]=Rnd(255)
			b[t]=Rnd(255)
		Next
		
		AddHook EmitEventHook,eventhook,Self		
	End Method
	
	Function eventhook:Object(id:Int,data:Object,context:Object)
		If TColorlist(context) TColorlist(context).ev TEvent(data);Return data	
	EndFunction

	Method Free()
		RemoveHook EmitEventHook,eventhook
		GCCollect()
	End Method



	Method ev(event:TEvent)
		If event.id=EVENT_REQUESTCOLORCHANGE
			current=event.data
			newevent.id=EVENT_COLORLIST_CHANGED
			EmitEvent newevent
		EndIf
		
		If event.id=EVENT_COLORLIST_SETHOOVER
			hoover=event.data
			newevent.id=EVENT_COLORLIST_CHANGED
			EmitEvent newevent
		EndIf

		If event.id=EVENT_COLORLIST_SETCURRENT
			current=event.data
			newevent.id=EVENT_COLORLIST_CHANGED
			EmitEvent newevent
		EndIf
				
		If event.id=EVENT_COLORLIST_UPDATE

			For Local t:Int=0 To 7
				If t=current
					SetColor 0,0,0
					DrawRect t*16,0,16,16
				EndIf				
				If t=hoover
					SetColor 255,255,255
					DrawRect t*16+1,1,16-2,16-2
				EndIf				
				
				SetColor r[t],g[t],b[t]
				DrawRect t*16+2,2,16-4,16-4
			Next
			SetColor 255,255,255
		EndIf
		
	End Method
	
End Type

' --------------------------------------------------------------------------
' --------------------------------------------------------------------------

Type TColorlisteditor

	Field canvas:TGadget
	
	Field newevent:TEvent=New TEvent
	
	Field ex:Int

	Method New()
		AddHook EmitEventHook,eventhook,Self
	End Method
	
	Function eventhook:Object(id:Int,data:Object,context:Object)
		If TColorlisteditor(context) TColorlisteditor(context).ev TEvent(data);Return data	
	EndFunction

	Method Free()
		RemoveHook EmitEventHook,eventhook
		GCCollect()
	End Method
	
	Method ev(event:TEvent)
		If event.source=canvas
			If event.id=EVENT_GADGETPAINT update
			
			If event.id=EVENT_MOUSEMOVE
				ex=Max(Min(event.x/16,7),0)
				
				newevent.id=EVENT_COLORLIST_SETHOOVER
				newevent.data=ex
				EmitEvent newevent
				update
			EndIf
			
			If event.id=EVENT_MOUSEDOWN
				If event.data=1
					newevent.id=EVENT_COLORLIST_SETCURRENT
					newevent.data=ex
					EmitEvent newevent
					update
				EndIf
			EndIf
			
			If event.id=EVENT_MOUSELEAVE
				newevent.id=EVENT_COLORLIST_SETHOOVER
				newevent.data=-1
				EmitEvent newevent
				update
			EndIf
		EndIf
		
		If event.id=EVENT_COLORLIST_CHANGED
			update
		EndIf
	End Method
	
	Method update()
		SetGraphics CanvasGraphics(canvas)
			SetClsColor 160,160,160;Cls
			newevent.id=EVENT_COLORLIST_UPDATE
			EmitEvent newevent
			Flip			
	End Method
	
End Type

Function CreateColorlisteditor:TColorlisteditor(x:Int,y:Int,parent:TGadget)
	Local a:TColorlisteditor=New TColorlisteditor
	a.canvas=CreateCanvas(x,y,16*8,16,parent)
	Return a
End Function

' --------------------------------------------------------------------------
' --------------------------------------------------------------------------

Local window:TGadget=CreateWindow("oO",0,0,640,480,Null,1)


Local cl:TColorlist=New TColorlist
Local cle:TColorlisteditor=CreateColorlisteditor(2,2,window)

Repeat
	WaitEvent()
	If EventID()=EVENT_WINDOWCLOSE End
Forever


Handles as Global fields