A function can accept objects of different type by passing the Int Handle: any type can be cast to Int in this way. But each Int can only be cast back to an object of the original type. You can create a single "polymorphic interface" function, that accepts a handle - but it has to delegate to implementation code written for each of the types it can accept. In other words, no, there is no easy way to have the same code work on values of different types (without using unsafe DLL hacks), because types force the separation of code.
What you can do, however, is make any data that you want to handle in one specific way only ever exist in one type - and you can then have "addon" types that extend the data with case-specific extra stuff. That way, objects of different absolute type both "extend" the same base class, and can share the code that operates on this base. To give an example for your situation:
Type Actor ;An actor is any (N)PC with movement
Field x#, y#, z#
Field this ;Handle of the "rest" of the object
End Type
Type EnemyActor
Field base.Actor ;Reference to the shared base type
Field enemyStuff
End Type
Type PlayerActor
Field base.Actor
Field playerStuff
End Type
Function NewEnemy.EnemyActor(x#, y#, z#, enemyStuff)
Local e.EnemyActor = New EnemyActor
e\base = private_NewActor_(x, y, z, Handle e)
e\enemyStuff = enemyStuff
Return e
End Function
Function NewPlayer.PlayerActor(x#, y#, z#, playerStuff)
Local p.PlayerActor = New PlayerActor
p\base = private_NewActor_(x, y, z, Handle p)
p\playerStuff = enemyStuff
Return p
End Function
;Private constructor because we don't want people actually creating these separately
Function private_NewActor_.Actor(x#, y#, z#, this)
Local a.Actor = New Actor
a\x = x : a\y = y : a\z = z
a\this = this
Return a
End Function
; Assume the existence of FreeEnemy, FreePlayer, private_FreeActor_ here (obvious implementations)
Function MoveActor(a.Actor, x#, y#, z#)
a\x = a\x + x : a\y = a\y + y : a\z = a\z + z
End Function
;Instances that are strongly typed, separate
Local e.EnemyActor = NewEnemy(1.2, 3.4, 5.6)
Local p.PlayerActor = NewPlayer(7.8, 9.1, 0.1)
;Generic collection
Local actors.Actor[1]
actors[0] = e\base ;To cast to the shared supertype, simply get the \base member
actors[1] = p\base
Local i : For i = 0 to 1
MoveActor actors[i], 10, 0, 0 ;Move players and enemies alike
Next
Local pInvalid.PlayerActor = Object.PlayerActor actors[0]\this ;Null, because this Actor is not a Player
Local pValid.PlayerActor = Object.PlayerActor actors[1]\this ;Not Null - valid Player
You can see an example of this in real-world code in my extended version of the bOGL OpenGL 3D engine project, where it's used to share the Entity class between Camera, Mesh, Light and Pivot (it uses a slightly faster system than Handle, but I'm sure you'll see the relevant differences).
...if you find yourself relying on this sort of thing heavily though, what you really want to do is move up to a language that properly supports object-orientation, like BlitzMax. What we've got above is basically a hand-implemented version of "inheritance", one of the Four Pillars of OOP. Language-level support is handy for this.
You could also think of this the opposite way around: instead of Enemy and Player being subtypes of Actor, you could consider them independent types that both "have" a Movable component object. So Movement wouldn't affect the whole object, but only one property of the object (this is much more dynamic, in that it separates the application of effects from any notion of a "class hierarchy", but it also requires you to explicitly design objects as "owning" components, and to remember to examine those components separately).
Last edited 2012
|