Mehrere Gegnertypen mittels extends und Funktionspointer

Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Neue Antwort erstellen

Lastmayday

Betreff: Mehrere Gegnertypen mittels extends und Funktionspointer

BeitragFr, Okt 21, 2011 8:40
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo,

hier ein kleines Beispiel wie man mehrere Gegnertypen mittels 'extends' und Funktionspointer realisiert.

Vorteile meiner Methode:
Jeder Gegnertyp besitzt seine eigene Type.
Neue Gegnertypen sind ohne großes Programmieren im Spiel verfügbar.
Kein Doppeltes Programmieren gleicher Funktionen.

Und hier der Ausführbare Code:
BlitzMax: [AUSKLAPPEN]
SuperStrict

'Test Gegner erstellen
Tcreatehook.Create("defaultenemy")
Tcreatehook.Create("enemy2")

'und auf Funktion prüfen
Tworkflowhook.allwork()
Tworkflowhook.allrender()



rem
Ankerpunkt für die create Funktion
endrem

Type Tcreatehook
'diese liste enthält alle createhooks mit Name und funktionspointer
Global list:TList = CreateList()

'Name und funktionspointer
Field _name:String
Field _create:Int()

'erstellt ein neues createhook -> speichert den Namen und den funktionspointer des Gegnertypen -> speichert diesen in der globalen Liste
Function register(myname:String, create_x:Int())
Local re:Tcreatehook = New Tcreatehook

re._name = myname
re._create = create_x

re.list.AddLast(re)
End Function

'wenn man diese Funktion aufruft wird der Name des Gegnertypen in der liste gesucht und bei Fund wird die create funktion für Gegnertypen aktiviert
Function Create(name:String)
For Local re:Tcreatehook = EachIn list
If re._name = name Then re._create()
Next
End Function

End Type


rem
Ankerpunkt für die Routine aufgaben wie das neu berechnen der Bewegung und das rendern der Gegner
endrem

Type Tworkflowhook
'diese liste enthält alle Gegner die aktiv im Spiel sind
Global list:TList = CreateList()

'Halter für den einzelnen Gegner (auch der extendet Gegner)
Field _enemy:Trootenemy

'erstellt ein neues workflowhook -> wandelt alle Gegnertypen in rootenemy um (auch die extendet Gegner) -> speichert diese in die globale Gegner liste
Function register:Tworkflowhook(obj:Object)
Local nm:Tworkflowhook = New Tworkflowhook

nm._enemy = Trootenemy(obj)

list.AddLast(nm)
Return nm
End Function

'Methode um Gegner wieder aus dem Spiel zu entfernen
Method unregister()
list.Remove(Self)
End Method

'alle Gegner durchgehen und die methode work() aufrufen
Function allwork()
For Local mm:Tworkflowhook = EachIn list
mm._enemy.work()
Next
End Function

'alle Gegner durchgehen und die methode render() aufrufen
Function allrender()
For Local mm:Tworkflowhook = EachIn list
mm._enemy.render()
Next
End Function

End Type

rem
Der Standartgegner mit seinen Funktionen und Methoden
endrem

Type Trootenemy
'den Gegner mit Namen und create Funktion bei createhook registrieren, die globale "dummydata" ist nur dazu da die register Funktion auszuführen
Global dummydata:Byte = Tcreatehook.register("defaultenemy", Trootenemy.Create)
'wird ein neuer Gegner erstellt registriert er sich automatisch im workflowhook, die variable myhook besitzt die Methode unregister zum entfernen aus dem workflowhook
'diese Zeile muss bei neuen Gegnertypen nicht kopiert werden
Field myhook:Tworkflowhook = Tworkflowhook.register(Self)

'Beispiel fields für Tests
Field name:String
Field x:Int
Field image:Int

'einen neuen Gegner erstellen und werte zuweisen
Function Create()
Local newenemy:Trootenemy = New Trootenemy

newenemy.image = 1234
newenemy.name = "defaultenemy"

Print "defaultenemy" + ": " + "create" + " is called"
End Function

'abarbeiten der Bewegung und bei zutreffender Bedingung den Gegner löschen
Method work()
x:+1
If x > 10 Then myhook.unregister()
Print name + ": " + "work" + " is called"
End Method

'ausgeben der Bewegung
Method render()
Print name + ": " + "render" + " is called"
Print name + ": " + "moved to " + x
Print name + ": " + "uses picture " + image
End Method
End Type

rem
Ein neuer Gegnertyp
endrem

Type Tenemy2 Extends Trootenemy '<- BEACHTEN!
'ein neuer Gegnertyp braucht mindestens Tcreatehook.register und eine neue create Funktion (damit auch die richtigen Types erstellt werden)
'zu beachten ist das man nicht versuchen sollte Globals zu überschreiben da sonst die Globals aus Trootenemy verwendet werden.

'den Gegner mit Namen und create Funktion bei createhook registrieren, die globale "dummydata" ist nur dazu da die register Funktion auszuführen
Global dummydata:Byte = Tcreatehook.register("enemy2", Tenemy2.Create)

'einen neuen Gegner erstellen und werte zuweisen
Function Create()
Local newenemy:Tenemy2 = New Tenemy2

newenemy.image = 9876
newenemy.name = "enemy2"

Print "enemy2" + ": " + "create" + " is called"
End Function


'dieser Gegner besitzt nun als Beispiel ein weiteres Field
Field boost:Int = 2

'und eine modifizierte work Methode
Method work()
x:+1 + boost
If x > 10 Then myhook.unregister()
Print name + ": " + "work" + " is called"
End Method

'zum rendern wird die Methode aus Trootenemy verwendet.

End Type


Type Tcreatehook:
In dieser Type werden alle Gegnertypen in einer liste gespeichert.
Die Funktion Tcreatehook.register(<name>:string,<functionspointer>:int()) nimmt einen neuen Gegnertyp mit Namen und der create Funktion in die liste auf.
Ein einfacher Aufruf von Tcreatehook.create(<name>:string) erstellt einen Gegner. Man kann auch auf ints zurückgreifen oder eine array verwenden um die Geschwindigkeit zu erhöhen.

Type Tworkflowhook:
In dieser Type werden alle aktive Gegner gespeichert und Routineaufgaben wie die Bewegungsberechnung und Render Methode aufgerufen und abgearbeitet.
Die Funktion Tworkflowhook.register(<object>:object) nimmt ein neuen Gegner in die Arbeitsroutine auf.
Die Methode unregister() entfernt den Gegner wieder.
Die Funktion Tworkflowhook.allwork() arbeitet alle Gegner durch und ruft deren Methode 'work()' auf.
Die Funktion Tworkflowhook.allrender() arbeitet alle Gegner durch und ruft deren Methode 'render()' auf.

Type Trootenemy:
Diese Type ist der Standartgegner und stellt die Standard Funktionen und Methoden bereit.
Näheres steht im Code kommentiert zur Verfügung.

Type Tenemy2 extends Trootenemy:
Mit dieser Type kommen wir zum eigentlichen ziel, einem neuen Gegnertyp. Wichtig ist zu wissen das man hier keine Globals von Trootenemy überschreiben kann.
Näheres steht im Code kommentiert zur Verfügung.
Diese Type muss mindestens aus diesem Code bestehen:

BlitzMax: [AUSKLAPPEN]
Type Tenemy3 Extends Trootenemy
Global dummydata:Byte = Tcreatehook.register("enemy3", Tenemy3.Create)
Function Create()
Local newenemy:Tenemy3 = New Tenemy3

newenemy.image = 5555
newenemy.name = "enemy3"

Print "enemy3" + ": " + "create" + " is called"
End Function
End Type



Fragen und Kritik sind erwünscht!

Mfg Lastmayday

ZEVS

BeitragFr, Okt 21, 2011 17:44
Antworten mit Zitat
Benutzer-Profile anzeigen
Es wäre evtl. interessant, auf eine Erweiterung von Klassen zu verzichten. Das ließe sich bewerkstelligen, indem du eine Art TEnemyKind-Klasse verwendest, die als Feld von TEnemy verwandt wird. Die TEnemyKind-Klasse enthält als Funktionspointer alle Funktionen zu den Inividuen, die als Parameter übergeben werden.
Ich spiele momentan mit einem solchen System rum, weil es einfach über externe Dateien erweiterbar ist (Funktionen mit Private).
Generell ist eine Abtrennung von Gegnerart und Gegnerindividuum interessant. Dann existiert das Bild beispielsweise nur einmal statt #Gegner-mal. Außerdem will ich hoffen, dass image:Int nur zu Testzwecken mit Print gedacht ist und ansonsten durch image:TImage ersetzt wird.

ZEVS

Lastmayday

BeitragSa, Okt 22, 2011 7:21
Antworten mit Zitat
Benutzer-Profile anzeigen
Hi,
meine Idee dahinter war jeden neuen Gegnertyp in einer extra Type abzukapseln und Standard Funktionen aus der root Type zu verwenden. Die Funktionen die man modifizieren muss bleiben dann separat und vermischen sich mit den anderen Typen.

Eine Art Funktionssammlung in einer Kindtype ist auch eine gute Idee, nur geht dort auch die Übersicht verloren. (evt. mal ein Beispiel?)

Bei minimalen Änderungen kann man diese einfach bei der create Funktion als Parameter übergeben.
Es geht primär darum wenn Gegnertypen unterschiedliche Bewegungsabläufe haben (Nahkämpfer / Fernkämpfer).

Wie ich geschrieben habe besteht das Problem das man Globals aus der root Type nicht überschreiben kann und somit ein globales Image flachfällt. Deswegen muss man ein Field image:Timage haben. Das man da natürlich kein loadimage() in der create Funktion verwenden sollte muss doch jedem klar sein.

Und das im Code image:int steht ist Absicht und soll veranschaulichen wo man das Bildchen speichern kann (wegen den Globals).

MfG Lastmayday
 

PhillipK

BeitragSa, Okt 22, 2011 9:53
Antworten mit Zitat
Benutzer-Profile anzeigen
Globale images würde ich allerdings auch bevorzugen.
Ich dimensioniere meine Bilder immer auf 512x512 und halte dann gleich mehrere "grafiken" auf einem Bild.
So würde ich es auch - je nach komplexität der animationen - auch mit einem solchen system machen.

Dazu gibts dann evtl eine globale im Gegner-extend und in der Root-klasse ein Globales bild.
Bescondere bilder können ja nach geschmack auchnoch im extend geladen werden.
Die globale im Gegegner extend gibt zb an, das man Frame 5-10 für diesen Gegner verwenden muss.

Das ist allerdings nicht so einfach zu beschreiben, da n beispiel für einzufuchsen ist mir grade zuviel - vielleicht wurde ich ja auch so verstanden Smile

Ansonsten, schön mal wieder ein paar Codes geteilt zu bekommen - selbst wenn mans nicht verwenden kann, man kann immernoch was lernen und/oder ideen aufgreifen. Danke =)

ZEVS

BeitragSa, Okt 22, 2011 12:45
Antworten mit Zitat
Benutzer-Profile anzeigen
Beispiel soll sofort kommen:
BlitzMax: [AUSKLAPPEN]
Type TCharacterKind
Global list:TList = New TList
Method New()
Self.list.addLast Self
End Method

Field moveSpeed%, lookAngle%, lookRange%
Field image:TImage
'Alles was man braucht als Funktionspointer:
Field Draw(character:TCharacter)
Field Load:TCharacter(stream:TStream)
Field Save(stream:TStream, character:TCharacter)
End Type

Type TCharacter
Field kind:TCharacterKind, data:Object
Field x%, y%, dir%
Field items:TList = New TList
Function Load:TCharacter(stream:TStream)
Local kind:TCharacterKind = TCharacterKind(TCharacterKind.list.valueAtIndex(ReadInt(stream)))
Return kind.Load(stream)
End Function
Method Save(stream:TStream)
Local index%
For Local kind:TCharacterKind = EachIn Self.kind.list
If kind = Self.kind Then Exit
index :+ 1
Next
WriteInt stream, index
Self.kind.Save stream, Self
End Method
End Type


Sodass man ohne extra Type einen neuen Charakter hinzufügen kann (extra Datei):
BlitzMax: [AUSKLAPPEN]
Private
Global character:TCharacterKind = New TCharacterKind
character.image = LoadImage("xyz.ext")
character.moveSpeed = 2
character.lookAngle = 90
character.loogRange = 50
Function Draw(ch:TCharacter)
SetRotation ch.dir
DrawImage character.image, ch.x, ch.y
End Function
Function DoLoad:TCharacter(stream:TStream)
'...
End Function
Function Save(stream:TStream, ch:TCharacter)
'...
End Function
character.Draw = Draw
character.Load = DoLoad
character.Save = Save
Public
Global myCharacter:TCharacterKind = character

Damit existiert nach außen hin nur eine globale Variable, die referenzierten Funktionen extieren nur innerhalb der Datei. Man hat leichten Zugriff auf alle Arten unter TCharacterKind.list. So lässt sich das Problem durch heftige Benutzung von Funktionspointern lösen.
Ich finde es nicht wesentlich unübersichtlicher,
BlitzMax: [AUSKLAPPEN]
character.kind.draw character

anstatt
BlitzMax: [AUSKLAPPEN]
character.draw

zu schreiben, ansonsten kann man ja auch einfach TCharacter um die Methode erweitern, die auf TCharacterKind.draw verweist (wie bei Save und Load).

ZEVS

Lastmayday

BeitragMi, Nov 02, 2011 7:57
Antworten mit Zitat
Benutzer-Profile anzeigen
@PhillipK: Dir ist schon klar das in Bmax Timage mit = verlinkt werden und nicht kopiert? Das Field Image verbraucht also nur ein int.

@ZEVS: Also im Prinzip machen wir exakt das gleiche, nur das meines anders geschrieben ist. =)

Ich hab jetzt mit meinem System gearbeitet und gesehen das es sich auch für andere Sachen gut einsetzten lässt. Leider muss man die hook types verdoppeln und umbenennen was mich nervt. Ich werde in den kommenden tagen versuchen das System umzuarbeiten. Ich denke aber nicht das es funktioniert, da Bmax einfach für so etwas nicht ausgelegt ist. Aber ich hab da schon eine Idee . . .

Ein Punkt fällt mir noch ein: da ich mein Projekt opensurce machen will und die Leute die Möglichkeit haben sollen eigene Gegner zu erstellen und zu teilen, reicht es die extend type zu kopieren und einzufügen. Dieser Gegner sollte dann funktionieren ohne den restlichen Code anzufassen. (und das funktioniert ja bei deinem Code auch Wink )

MfG Lastmayday

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Codearchiv & Module

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group