Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

physX

Treue Seele

  • »physX« ist der Autor dieses Themas

Beiträge: 119

Wohnort: Dresden

  • Private Nachricht senden

1

07.07.2009, 12:58

Vererbung von Klassen

Hallo, ich hab mal ein paar grundlegende Fragen. Ich arbeite mit der SFML. Ich habe mir zwei Basisklassen gebastelt
CParticle und CSprite, wobei CSprite von sf::Sprite erbt und eine Erweiterung zur Darstellung animierter Sprites darstellt.
Um später Spielelemente wie Asteroiden, Gegner und Spieler selbst abzuleiten habe ich aus den beiden Basisklassen die Klasse CParticleSprite abgeleitet
CParticleSprite: public CSprite, public CParticle
ok, soweit so gut.

In CParticle gibt es nun eine Memberfunktion

C-/C++-Quelltext

1
2
public:
CParticle::UpdateKinematics(CParticle *pParticleA, CParticle *pParticleB)

Wenn diese Memberfunktion nun aus einem Objekt


C-/C++-Quelltext

1
2
3
4
5
6
//EDIT

CParticleSprite *myElement;
myElement=new CParticleSprite;
CParticleSprite *sec_Element;
sec_Element=new CParticleSprite;
//

aufrufen möchte muss ich natürlich immer im Argument erst in die Basisklasse casten also z.B.

C-/C++-Quelltext

1
myElement.UpdateKinematics(static_cast<CParticle*>myElement,static_cast<CParticle*>second_Element)


jetzt wollte ich mal die profis hier fragen, ob meine herangehensweise überhaupt so sinnvoll ist, oder ob ich den cast prinzipiell möglichst vermeiden sollte. Vielleicht ist das aber auch eine übliche und legitime Anwendung.
Ich könnte natürlich eine eigene Klasse erstellen und dann in der Klasse zwei member

C-/C++-Quelltext

1
2
CSprite *m_pSprite;
CParticle *m_pParticle;

erstellen und dann entsprechen darüber arbeiten. Allerdings stört mich an diesem Ansatz, dass sich hier oft quasi endloslange aufrufe der form

C-/C++-Quelltext

1
m_pPlayer->....->GetElement()->GetSprite()->memberfunktion()

bilden.

Ich wäre dankbar für ein paar Tips oder links insbesondere in die Richtung Klassendesign etc.
Gruss

Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

2

07.07.2009, 13:08

Du brauchst eine Instanze nicht auf ihre Basisklasse casten. Das wird automatisch gemacht.
Ich sehe hier aber den fehler, dass du eine Variable in einen Pointer umcastest, was nen bisl komisch ist und nich das ist was du willst oder ?
Fehlt vtl ein "&" vor myElement und second_Element ?

das design an sich finde ich aber OK ;)

physX

Treue Seele

  • »physX« ist der Autor dieses Themas

Beiträge: 119

Wohnort: Dresden

  • Private Nachricht senden

3

07.07.2009, 13:38

Zitat von »"Databyte"«

Du brauchst eine Instanze nicht auf ihre Basisklasse casten. Das wird automatisch gemacht.
Ich sehe hier aber den fehler, dass du eine Variable in einen Pointer umcastest, was nen bisl komisch ist und nich das ist was du willst oder ?
Fehlt vtl ein "&" vor myElement und second_Element ?

das design an sich finde ich aber OK ;)


oben im Beispiel hab ich myElement falsch angegeben.

myElement und sec_Element sollen eigentlich Pointer auf die jeweilige Instanz sein., dann muesste es wieder richtig sein.

:oops: du hast natuerlich recht. der compiler hatte nicht wegen dem fehlenden cast gemeckert, sondern weil ich nicht die Adresse/pointer übergeben habe, was ich dann beim eintragen des casts intuitiv gleich mitkorrigiert hatte... ok, dafuer bereitet man hier einen ellenlangen text im forum vor... :roll:

danke für die Hilfe

4

07.07.2009, 14:07

Re: Vererbung von Klassen

Ich würde die Vererbung grundsätzlich nochmals überdenken. Im Allgemeinen wird diese nämlich zu oft eingesetzt.

In deinem Fall würde ich meinen, du fährst besser, wenn du die Grafik von der Logik trennst. Das heisst, Enemy erbt nicht von CSprite und CParticle, weil ein Gegner weder ein Sprite noch ein Partikel ist - sondern nur ein Objekt, das sich bewegen und gewisse Aktionen durchführen kann. Sprite wird erst im Bezug auf die Grafik relevant. Von daher würde ich dir eher raten, eine Klasse für bewegliche Objekte zu erstellen, und dann Spieler, Gegner etc. davon abzuleiten. Eventuell sind da noch genauere Abstufungen möglich (wie z.B. kollidierbares Objekt), das hängt von der gegebenen Funktionalität ab.

Diese Trennung erhöht die Abstraktion und hat dementsprechend den Vorteil, dass nicht jedes Spielelement 1:1 an ein Sprite gebunden ist. Zum Beispiel ist es sinnlos, Sprites für Elemente ausserhalb des Bildschirms zu haben. Oder für mehrere Objekte, die gleich aussehen, jeweils einzelne Sprites. Auf der anderen Seite bist du dadurch auch flexibler, weil die Grafik an einem zentralen Ort (am besten in einer Klasse wie Renderer oder GraphicManager) geregelt ist. Wenn du den sf::RenderWindow::Draw()-Aufruf noch kapselst, kannst du zum Beispiel auf Befehl das Zeichnen an- und abschalten.

Ich empfehle dir dann, für die Spielelemente spezifische Funktionen zu machen, z.B.:

C-/C++-Quelltext

1
void GraphicManager::Draw(const Enemy& CurrentEnemy);

Und versuch, nicht so oft mit dem Heap (new) zu arbeiten, wenn möglich (hast du an die deletes gedacht?). Genauso rate ich dir, oft Referenzen statt Zeiger bei der Übergabe zu verwenden und auf Const-Correctness Rücksicht zu nehmen. Wenn eine Funktion am übergebenen Objekt nichts verändert, deklarier es als const. Wenn eine Memberfunktion an der Klasse nichts verändert, mach sie ebenfalls const.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

5

07.07.2009, 14:27

Nexus, du solltest die Eigenheiten von SFML berücksichtigen:

- Draw rendert nichts außerhalb des views.
- Ein Sprite ist bei SFML keine Textur sondern nur ein Objekt mit Größe und Position, dass eine Referenz auf ein Image (eine Textur) haben kann.

Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

6

07.07.2009, 14:27

Ach ja, da du ja Partikel mit SFML machen willst, kann ich dir ein
Partikelengine anbieten, die auf Opengl basiert ( womit ja auch sfml gemacht ist). Das bedeutet, dass das teil echt schnell ist, was ich gerade bei Partikeln wichtig finde.
Außerdem hat das Teil echt alles was man braucht, von Grvitation bis zu Kollision und wenn das nicht braucht, kann man einfach noch seine eigene manipulations-funktion angeben.

Das Problem ist, dass die Docu nicht mehr ganz stimmt, weil Die ursprünglich
als allgemeine 3d partikel-engine gedacht war, ich sie aber komplett auf sfml umgeschrieben habe, weil die sfml-komponente nicht funktioniert hat ;)
Aber das ist nicht so schlimm, weil im entergebniss hab ich nur die Vektoren verändert (von 3d auf sf::vector2f) und die namespaces hab ich auch ein bisl abgeändert ;)

Also wenn du Interesse hast, schreib mir ne nachricht :)
( Kannst natürlich auch die eigentliche engine nehmen :)(Link weiter unten !) )

Hier ein Video, allerdings noch 3d
Screenshoot´s der SPARK Partikelengine (Weiter unten auch die SFML-Komponente

PS: Habe gerade gesehen, dass es jetzt auch ein Tutorial gibt :)

7

07.07.2009, 15:28

Zitat von »"K-Bal"«

- Draw rendert nichts außerhalb des views.
Nicht ganz. Ok, die Entscheidung liegt bei OpenGL, welche Pixel gezeichnet werden. Aber es sind nicht wenige Anweisungen, um ein komplettes Sprite zu zeichnen mit allen Transformationen. Diese werden aber in jedem Falle durchgeführt, selbst sich das Sprite ausserhalb des Views befindet. Eine If-Abfrage, ob dieses überhaupt auf dem Bildschirm ist, kann da einiges einsparen.

Zitat von »"K-Bal"«

- Ein Sprite ist bei SFML keine Textur sondern nur ein Objekt mit Größe und Position, dass eine Referenz auf ein Image (eine Textur) haben kann.
Ja, aber was hat das damit zu tun? Tatsache ist, dass ein Sprite nur für die Grafik benötigt wird. Für Spiellogik kann man damit nicht wirklich viel anfangen. Zudem hat ein sf::Sprite eine Grösse von 196 Bytes, das ist recht viel (zumal man meist nur einen kleinen Teil der Eigenschaften benötigt). Wenn man seine Tilemap oder Partikel auf Sprites basiert, hat man total unnötigen Speicherverbrauch. Genauso unnötig ist er, wenn 30 Objekte 30 Sprites haben, die alle gleich sind.

Unabhängig davon, wie SFML aufgebaut ist und was in den Anfängertutorials steht, kann es Sinn machen, Grafik und Logik zu trennen. Andere Vorteile habe ich ja vorher bereits erwähnt.

K-Bal

Alter Hase

Beiträge: 703

Wohnort: Aachen

Beruf: Student (Elektrotechnik, Technische Informatik)

  • Private Nachricht senden

8

07.07.2009, 18:26

Ich verstehe nicht was an einem Sprite pro Objekt schlimm sein soll. Es ist ja nicht ein Image pro Objekt sondern nur ein Sprite. Willst du jetzt für jedes Objekt, dass du renderst dein Sprite bewegen, rotieren und skalieren?

Vlt versteh ich dich nur falsch.

physX

Treue Seele

  • »physX« ist der Autor dieses Themas

Beiträge: 119

Wohnort: Dresden

  • Private Nachricht senden

9

07.07.2009, 18:38

Hallo Nexus, danke erstmal für deine Tips und Vorschläge. Ich werde deinen Ansatz soweit es geht versuchen umzusetzen.
K-Bal hat aber schon recht, dass es mit der sf::Sprite sich das nicht ganz so problemlos auftrennen laesst wie du vorschlägst.
Bei SFML ist sf::Sprite bereits mit einer Koordinate verknüpft an der dann gerendert wird. d.h. ich kann mehreren Objekten, die prinzipiell die gleiche Textur haben nicht einfach den gleich sf::Sprite zuordnen, da diese sich sonst alle in der Bewegung gleich verhalten würden. Allerdings kann ich verschiedenen sf::Sprites das gleiche sf::Image zuordnen was die ganze Sache wieder etwas relativiert.
Ich werde jetzt erstmal die const-correctness überarbeiten und die Partikelklassenstruktur noch etwas ausarbeiten. (deletes hatte ich alle eingebaut und auch bisher keine mem-leaks gefunden ;) )
Abfragen zu Objekten die ausserhalb des sichtbaren Bereichs liegen hatte ich bereits mit eingebaut.

gruss

Zitat von »"Nexus"«

Zitat von »"K-Bal"«

- Draw rendert nichts außerhalb des views.
Nicht ganz. Ok, die Entscheidung liegt bei OpenGL, welche Pixel gezeichnet werden. Aber es sind nicht wenige Anweisungen, um ein komplettes Sprite zu zeichnen mit allen Transformationen. Diese werden aber in jedem Falle durchgeführt, selbst sich das Sprite ausserhalb des Views befindet. Eine If-Abfrage, ob dieses überhaupt auf dem Bildschirm ist, kann da einiges einsparen.

Zitat von »"K-Bal"«

- Ein Sprite ist bei SFML keine Textur sondern nur ein Objekt mit Größe und Position, dass eine Referenz auf ein Image (eine Textur) haben kann.
Ja, aber was hat das damit zu tun? Tatsache ist, dass ein Sprite nur für die Grafik benötigt wird. Für Spiellogik kann man damit nicht wirklich viel anfangen. Zudem hat ein sf::Sprite eine Grösse von 196 Bytes, das ist recht viel (zumal man meist nur einen kleinen Teil der Eigenschaften benötigt). Wenn man seine Tilemap oder Partikel auf Sprites basiert, hat man total unnötigen Speicherverbrauch. Genauso unnötig ist er, wenn 30 Objekte 30 Sprites haben, die alle gleich sind.

Unabhängig davon, wie SFML aufgebaut ist und was in den Anfängertutorials steht, kann es Sinn machen, Grafik und Logik zu trennen. Andere Vorteile habe ich ja vorher bereits erwähnt.

10

08.07.2009, 01:47

@ physX: Du kannst deine eigene Klasse auch ihre Position speichern lassen. Wenn dir das momentan zu mühsam vorkommt, kannst du es auch vorerst lassen, aber behalt die Möglichkeit doch im Hinterkopf. ;)

@ K-Bal: Wie so oft kann man nicht einfach sagen, 1 Sprite pro Objekt sei grundsätzlich schlecht, aber auch nicht grundsätzlich gut. Ich habe bisher eben die Erfahrung gemacht, dass ich generell flexibler bin, wenn ich die Grafik separat handhabe und klar von der Logik abtrenne.

Man muss nicht unbedingt ein einziges Sprite haben, sondern kann z.B. pro unterschiedliches Objekt eins haben und alle Sprites in einer Map speichern. Die Position und Rotation ändern sich meistens sowieso jedes Frame, und selbst abgesehen davon sind die einzelnen Zuweisungen an Variablen nicht wirklich rechenzeitintensiv. Im Gegenzug kannst du dir Rechenzeit sparen, wenn du Spiellogik-Objekte von Sprites trennst: Da diese eine meist kleinere Grösse haben, werden Kopien schneller durchgeführt, was sich zum Beispiel in Containern positiv auswirken kann. Zudem entfallen auch Konstruktion, wenn ein neues Spielobjekt entsteht, sowie Destruktion, wenn eines verschwindet. Indem du die beiden Dinge unabhängig voneinander machst, hast du viele Vorteile. Performance ist dabei für mich nicht einmal das Entscheidende. Im Extremfall (Tilemap oder Partikel) kann sie allerdings schon entscheidend werden, wobei dann möglicherweise auch der Speicherverbrauch kritisch wird.

SFML hat bereits einen Schritt in diese Richtung getan, indem es Sprites von Images getrennt hat. Ich kann diese Abstraktion noch vergrössern, indem ich Logikobjekte von Sprites trenne. Schau doch nur mal, wie viele Attribute sf::Sprite besitzt:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ResourcePtr<Image>  myImage;      
IntRect             mySubRect;   
bool               myIsFlippedX;
bool               myIsFlippedY;

Vector2f            myPosition;     
Vector2f            myScale;        
Vector2f            myOrigin;       
float              myRotation;     
Color               myColor;        
Blend::Mode         myBlendMode;    
mutable bool       myNeedUpdate;   
mutable bool       myInvNeedUpdate;
mutable Matrix3    myMatrix;
mutable Matrix3    myInvMatrix;
Wie viele davon brauchst du tatsächlich, um einen Gegner zu beschreiben?

Die Variablen myScale oder myBlendMode sind wohl in den meisten Fällen auf den Defaultwert gesetzt. Wenn du wirklich nur einen Gegner mit notwendigen Eigenschaften hast, fallen dir zum Beispiel auch andere Aktionen wie Schreiben/Lesen von Dateien leichter, da du gleich alle relevanten Daten im Überblick hast. Ausserdem kannst du auch zusätzliche grafische Eigenschaften (wie z.B. Animation) direkt in eine Draw-Kapselfunktion einbauen, die ein sf::Sprite selber nicht kennt.

Werbeanzeige