Archief - [PROG][C++] Voor de liefhebber: polymorfisme

Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.

forloRn_

Legacy Member
Goedenavond,

Ik ben nog altijd druk bezig aan mijn platformspel, en collision detection staat nu op het programma.

Entity is de moederklasse van alle voorwerpen die je in een level tegenkomt: spelers, vijanden, projectielen, power-ups, blokjes, ...
Avatar (in feite de speler) en Enemy zijn subklassen van Entity.

De huidige level houdt een vector bij van pointers naar Entities:
Code:
vector<Entity *> entities;

De bedoeling is nu dat de entities in de level met elkaar interageren: muren hinderen spelers, spelers verliezen energie als ze met een vijand in aanraking komen en winnen energie als ze een power-up oppakken, muren hinderen vijanden maar vijanden winnen GEEN energie als ze over een power-up lopen.

De manier waarop een entiteit met een andere interageert, hangt dus af van BEIDE klasses van de entiteiten.

De collision detection zal alle entiteiten in de level overlopen en nagaan of ze met elkaar in botsing zijn gekomen:
Code:
for(unsigned int p = 0; p < entities.size(); p++) {
	for(unsigned int s = p + 1; s < entities.size(); s++) {

		// collision detection-algoritme ...

		// als ze botsen:
		entities[p]->collidedWith(entities[s]);
		entities[s]->collidedWith(entities[p]);
	}
}

Dit was mijn docent zijn voorstel:

Als het gedrag echt een kwestie van combinaties is zal je polymorfisme
moeten combineren met overloading. Als je 2 mogelijke types hebt die
afgeleid worden van Entity, bvb X en Y
virtual void Entity::collidedWith(X*)
virtual void Entity::collidedWith(Y*)
en dan de implementaties
void X::collidedWith(X*)
void X::collidedWith(Y*)
void Y::collidedWith(X*)
void Y::collidedWith(Y*)
't is niet echt mooi, maar ik zie niet dadelijk iets beter.

... waarbij hij veronderstelde dat je gewoon een entity kon meegeven met collidedWith(), en dat polymorfisme er wel voor zou zorgen dat de ontvanger van collidedWith() wist met welke entiteit hij te maken had, en op de juiste manier reageerde (de juiste implementatie aanriep).

Mijn compiler deed echter wat ik verwachtte:
Code:
../main.cpp:16: error: call of overloaded `collidesWith(Entity*&)' is ambiguous
../Entity.h:12: note: candidates are: virtual void Entity::collidesWith(Avatar*) <near match>
../Entity.h:13: note:                 virtual void Entity::collidesWith(Enemy*) <near match>

Je kan wel een entity meegeven met collidesWith(), maar aangezien zowel Enemy als Avatar subklassen zijn van Entity, weet hij niet welke implementatie hij moet aanroepen.

Ben ik genoodzaakt mijn toevlucht te zoeken tot typeid om entities[p] en entities te downcasten en dan expliciet collidesWith(Avatar*) of collidesWith(Enemy*) aan te roepen, of is er een gemakkelijkere manier?

killgore

Legacy Member
in uw collidesWith(entity ...)
via dynamic_cast checken en een else-if structuur checken of het overeenkomt met een object dat je nodig hebt en dan de juiste interne methode oproepen (of als het kleine code is die daarin plakken).
Andere methoden is iets als unieke naam aan je entity geven en daarop controleren, ma komt uiteindelijk op het zelfde neer.
dus bv. in uw basis klasse:

virtual void collidesWith(Entity* entity) {}

dan in een afgeleide klasse:
Code:
virtual void collidesWith(Entity* entity) 
{
    if(dynamic_cast<Enemy*>otherEntity)
        ... juiste code
    else if(dynamic_cast<Wall*>otherEntity)
        ...
}
edit: ik werk niet graag met de typeid-info aangezien die nogal compiler afhankelijk is ;).

Vich

Legacy Member
Tip: ga geen collision code integreren met uw game code.

Splits physics code af in klasses zoals:
- PhysicsObject: Een Entity kan een PhysicsObject zijn of een physics object bevatten. PhysicsObject bevat bvb paramters zoals een PhysicsShape. Een PhysicsShape kan bijvoorbeeld een bounding box, sphere of polygonal mesh zijn.
- PhysicsWorld (heeft een lijst van PhysicsObjects en gaat interactie doen met deze objecten)

In de game code heb je bijvoorbeeld klassen als:
- Entity
- EntityMover: Een voorbeeld van een EntityMover zou eventueel een PhysicsMover kunnen zijn.
- GameWorld (verzameling van alle entities)

Hier heb je dus totaal geen polymorfisme voor nodig.

forloRn_

Legacy Member
Je moet toch altijd weten met welk soort entities je te maken hebt? collidedWith() bevat ook zaken als damage doen bijvoorbeeld.

Vich

Legacy Member
forloRn_ zei:
Je moet toch altijd weten met welk soort entities je te maken hebt? collidedWith() bevat ook zaken als damage doen bijvoorbeeld.

Ik weet niet of je op mij antwoordt, maar indien dat zo is:

Als een Entity een PhsyicsObject is, dan zorg je gewoon voor dat er virtuele als OnCollide bestaan bij PhysicsObject en die implementeer je dan in Entity. Zo kan je bij de OnCollide van bijvoorbeeld een Humanoid(afgeleid van Entity) damage doen.

Als een Entity een PhysicsObject heeft dan kan je:
- Een SetEntity op PhysicsObject zetten, zodat PhysicsObject Entity::OnCollide(entity*) kan aanroepen.
of:
- PhysicsObject::SetOnCollideCallback(function*) maken zodat je de callback van Entity kan registreren voor OnCollide
of:
- Een soort van Messenger maken in Entity die je registreert bij PhysicsObject, zodat een PhysicsObject een message kan sturen naar Entity met bijvoorbeeld "OnCollide".

Er zijn vast nog andere mogelijkheden om dit te implementeren.

Damage doen - met echte physics - doe je dus enkel dmv OnCollide messages te sturen of OnCollide aan te roepen op een object. Je geeft bij de OnCollide ook het object mee dat het andere object raakt.
Is dat Entity een kogel(check je door middel van RTTI - Runtime Type Information), dan kan je damage doen als je dat wil.

Eigenlijk kan je damage door kogels gewoon door raycasts doen en niet door echte physics, niemand zal dat merken als je op normale snelheid werkt. Granaten en andere soortgelijke projectielen kan je beter echte physics geven, maar die doen meestal enkel damage als ze ontploffen, niet als ze objecten raken.

Verder kan je physics zo maken dat je bij OnCollide gaat kijken hoe snel en hoe zwaar het object is dat je raakt en zo damage doen.




En om nog even ontopic te antwoorden:

In plaats van deze te maken:
Code:
virtual void Entity::collidesWith(Enemy*)
virtual void Entity::collidesWith(Avatar*)
Kan je beter gewoon deze maken:
Code:
virtual void Entity::collidesWith(Entity*)

Je gaat dan gewoon kijken van welk type je Entity* is en cast hem daar naartoe. Hier gebruik je de eerder genoemde RTTI óf maakt gewoon je code zo:

Code:
enum CollisionGroup
{
   avatar,
   enemy,
   friendly
};
virtual void Entity::collidesWith(Enemy*, ECollisionGroup group)

"collidesWith" zou ik zowiezo niet gebruiken, want door de naamgeving lijkt het alsof je een boolean gaat returnen. Je kan dus beter gewoon OnCollide nemen of iets dergelijks.

[edit] Eindelijk nog eens een deftig coding topic *smult*

forloRn_

Legacy Member
Dus... je moet in een switch nagaan welk dynamisch type de entiteit heeft, downcasten en gepast handelen. Dat moest ik weten. :)

killgore

Legacy Member
Is dat Entity een kogel(check je door middel van RTTI - Runtime Type Information), dan kan je damage doen als je dat wil.

Dat is dus basically wat ik in men reply stelde en wat zen probleem is :p.

Gij maakt het denk ik vreselijk ingewikkeld vo zen basis gamepje :s. Wel vrij interessant allemaal :), geen goesting om op gd.be es theoretische tuto te maken over hoe je best een engine construeert (zo gescheiden e.d.).

edit: en die collideswith was typo van mij :p, hij gebruikt collidedwith vich ;).

forloRn_

Legacy Member
My mistake: ik heb collidesWith en collidedWith door elkaar gebruikt.

Bedankt voor de uitleg over de physics, maar dat gaat misschien wel wat ver voor dit eenvoudige programmaatje. 't Is mijn eerste platformspel dus het moet niet perfect zijn. Desalniettemin, het is tot nu toe redelijk deftig gemaakt, al zeg ik het zelf. Met wat game-ervaring en gezond verstand kom je al een heel eind. :p

Vich

Legacy Member
killgore zei:
Dat is dus basically wat ik in men reply stelde en wat zen probleem is :p.

Gij maakt het denk ik vreselijk ingewikkeld vo zen basis gamepje :s. Wel vrij interessant allemaal :), geen goesting om op gd.be es theoretische tuto te maken over hoe je best een engine construeert (zo gescheiden e.d.).

edit: en die collideswith was typo van mij :p, hij gebruikt collidedwith vich ;).

Misschien dat ik dat ooit wel eens schrijf, maar dat is wel een hele hoop werk. Wat ik hier beschreef is enkel de physics engine. Een game is:
- game engine
- grafische engine
- physics engine
- audio engine
- ...

Als ik eens veel tijd heb :P

UniKorn

Legacy Member
Heb niet veel tijd om te antwoorden, maar aangezien mijn WoW server down is kom ik nog is efkes piepen :)

forloRn, het voordeel van een polymofe container is juist dat je verschillende classes erin kan steken die allemaal dezelfde basisklasse hebben.

Dus als player overerft van entity en wall ook, zou je normaal perfect een player->collidesWith(wall) moeten kunnen doen, en collidesWith kan in elk van die classes verschilllend zijn.

Als je dat op het niveau van de entities zelf wil doen is dat niet mogelijk (tenzij je verschillende ifjes gebruikt, maar dat is niet echt de oplossing)

Wat je ook zou kunnen doen is een property aan je entity class toevoegen die je physicsobject is. (Zoals vich voorstelt).

In C# (weet niet hoe het in c++ is, lang geleden.)

entity[p].physicsobject.Collidewith(entity.physicsobject)

hier is collidewith dan een onderdeel van je physicsobjec class.
Het archief is een bevroren moment uit een vorige versie van dit forum, met andere regels en andere bazen. Deze posts weerspiegelen op geen enkele manier onze huidige ideeën, waarden of wereldbeelden en zijn op sommige plaatsen gecensureerd wegens ontoelaatbaar. Veel zijn in een andere tijdsgeest gemaakt, al dan niet ironisch - zoals in het ironische subforum Off-Topic - en zouden op dit moment niet meer gepost (mogen) worden. Toch bieden we dit archief nog graag aan als informatiedatabank en naslagwerk. Lees er hier meer over of start een gesprek met anderen.
Terug
Bovenaan