PDA

Volledige versie bekijken : variabele opvragen vanuit andere class


W0utR
%Europe/Berlin %511 %2009, 12:16
Ik zit met een toch wel vrij groot probleem, ik ben bezig met een klein spelletje (soort van tower defence)
Wat ik nu heb is hier te zien: link (http://test.woutr.be/test.html)

Ik heb het spel als volgt opgebouwd:

TimeLine

Enemy
MachineGun



Voorlopig werkt alles nog goed, maar die torens moeten natuurlijk schieten, dit is geen probleem, maar het probleem zit hem erin wanneer ik de health van een Enemy moet opvragen.

In mijn MachineGun class heb ik deze functie gemaakt voor een vijand op te vragen (hij pakt dus de eerste vijand die voorbij komt):
private function getEnemy():Object
{
for(var i:int = 0; i < stage.numChildren; i++)
{
if(stage.getChildAt(i).name == "enemy")
{
var dx:Number = this.x - stage.getChildAt(i).x;
var dy:Number = this.y - stage.getChildAt(i).y;

if (Math.sqrt(dx * dx + dy * dy) < _maxDistance) {
return stage.getChildAt(i);
}
}
}
return null;
}

Hij gaat dus door alle children op de stage en kijkt welke de naam enemy hebben, daarvan berekend hij dan of hij binnen een cirkel van 100 pixels zit, zoja, dan returnt hij deze vijand.

In mijn Enemy class heb ik dan een functie voor de health te veranderen / op te vragen
public function set health(damage:int):void
{
_health -= damage;
}
public function get health():int
{
return health;
}

Maar waneer ik dan in mijn MachineGun dit probeer te doen, dit staal wel in een enter_frame, omdat hij ook telkens de rotatie moet aanpassen:
trace(getEnemy().health);

krijg ik deze error:
Error: Error #1023: Stack overflow occurred.
at Enemy/get health()

Nu ben ik al een tijdje aan het zoeken, maar ik kan echt niet uitvissen waarom hij daar een error op geeft

Dauntless
%Europe/Berlin %519 %2009, 12:28
1. Houdt een array bij met alle enemies in. Altijd alle children doorzoeken naar enemies is erg inefficiënt.
2. Je hebt alle enemies dezelfde instancenaam gegeven, dat mag niet. (vandaar dus ook dat je de array van hierboven nodig hebt)
3. Return een DisplayObject (of evt Sprite of MovieClip) ipv een Object

Als je dat aanpast, krijg je dan nog steeds je stack overflow ?

W0utR
%Europe/Berlin %525 %2009, 12:37
In het begin had ik een array van alle enemies, maar telkens wanneer er een nieuwe bijkwam moest deze dan worden doorgegeven aan de MachineGun.

Daarom dat ik dacht dat het met de children te overlopen beter was, of is er een mogelijkheid om vanuit MachineGun die array op te vragen?

Of gewoon werken met een setter?

B-Mantis
%Europe/Berlin %530 %2009, 12:44
die stack overflow:


public function get health():int {
return health;
}

moet worden


public function get health():int {
return _health;
}

Anders roep je steeds weer dezelde functie op, en dan krijg je.. een stack overflow!

Jan
%Europe/Berlin %531 %2009, 12:45
public function get health():int
{
return health;
}
Moet het niet _health zijn ipv health? Anders heb je een recursive function.

Groeten,
Jan

W0utR
%Europe/Berlin %535 %2009, 12:50
Jah inderdaad, daarmee was het probleem ook opgelost.

Maar ik heb Dauntless zijn raad gevolgd en alle vijanden in een array gestoken, telkens wanneer er een nieuwe bijkomt update ik de arrays van de towers:

function updateAllEnemyArrs():void
{
for(var i:int = 0; i < towerArr.length; i++)
{
towerArr[i].enemyArr = enemyArr;
}
}

Dit werkte perfect, bedankt allemaal :)

Dauntless
%Europe/Berlin %541 %2009, 13:00
Dat is dan ook weer niet zo mooi. Behoud gewoon je functie van in het begin (getEnemy()) en in plaats van de display list te doorlopen, doorloop je nu gewoon de array ...

//EDIT
Oh, die getEnemy() had je in tower staan...

Geef aan tower een referentie mee naar de hoofd-applicatie. Daar maak je de getEnemy() op aan met als argument de toren of de locatie van de toren. De applicatie doorloopt dan de enemies lijst, kijkt welke het dichtste bij is en geeft deze terug aan de toren.

W0utR
%Europe/Berlin %552 %2009, 13:15
Ja, ik merkte toch af en toe problemen doorrdat je een tower kon bouwen en hij de array niet update

Maar dat met een referentie meegeven snap ik ff niet.
Bedoel je dan bij het aanmaken van mijn Tower een parameter meegeven naar die functie?

Dauntless
%Europe/Berlin %554 %2009, 13:18
Ja.
//hoofd applicatie:
var newTower:Tower = new Tower(this);
//tower constructor
public function Tower(main:*)
{
this.game = main;
}
En dan kan je 'this.game.getEnemy()' gebruiken om een enemy te krijgen.

W0utR
%Europe/Berlin %558 %2009, 13:24
public function MachineGun(main:*):void
{
this.enemyArr = new Array();
this.enemyArr = main.enemyArr;
}

Jouw oplossing werkt zoals het zou moeten, maar ik heb dit ff getest en dit werkt ook perfect.

Mijn getEnemy() staat dan nog altijd in mijn Tower class.

Want op hij nu telkens die functie aanroept, of gewoon de array meegeeft lijkt mij niet zo veel verschil, of ben ik hierin fout?

Dauntless
%Europe/Berlin %568 %2009, 13:38
Fout :).

De enemies array is iets van de Game classe (ik ga er even van uit dat Game je hoofd applicatie is waar de enemies in worden aangemaakt). Als je deze array ook beschikbaar stelt voor anderen (zoals je nu doet dmv een public var) dan kan eender welke andere classe deze array aanpassen, zonder dat de Game classe weet dat er iets veranderd is. Dit gaat in tegen een paar belangrijke design principles. Een classe moet altijd volledige controle hebben over zichzelf. Indien je die array rechtstreeks beschikbaar maakt voor anderen, is dat niet meer het geval.

Wat jij nu hebt is net hetzelfde als de code die je eerst had waar je de arrays instelde in een loop op over alle towers.

W0utR
%Europe/Berlin %582 %2009, 13:58
Dus als ik het goed begrijp mag je een variabele vanuit de ene class niet veranderen in een andere class?
Gewoon via getters/setters dan?

Ik heb het dan aangepast naar jouw manier:
Een referentie meegeven naar mijn hoofd class (tijdlijn in dit geval).
In mijn tower heb ik dan een enter_frame die een enemy opvraagt
private function enterFrameHandler(event:Event):void
{
if(this.main.getEnemy(this, _maxDistance) != null)
{
var newRotation:Number = Math.atan2(this.y - this.main.getEnemy(this, _maxDistance).y, this.x - this.main.getEnemy(this, _maxDistance).x) * 180 / Math.PI;
this.rotation = newRotation;
}
}

En de functie op mijn tijdlijn:
function getEnemy(tower, maxDistance):Object
{
if(enemyArr.length >= 0)
{
for(var i:int = 0; i < enemyArr.length; i++)
{
var dx:Number = tower.x - enemyArr[i].x;
var dy:Number = tower.y - enemyArr[i].y;

if (Math.sqrt(dx * dx + dy * dy) < maxDistance) {
return enemyArr[i];
}
}
}
return null;
}

Zo zit ik hopelijk juist?

B-Mantis
%Europe/Berlin %772 %2009, 18:31
Dit gaat in tegen een paar belangrijke design principles. Een classe moet altijd volledige controle hebben over zichzelf.

maar geldt dit ook niet voor het meegeven van de gameclasse aan de tower classe? Da's toch ook geen goede design (maakt de twee classes afhankelijk van elkaar). Is het niet beter om de game classe de hittests te laten uitvoeren voor de tower classe? Aangezien de gameclasse zowel over data van de enemies als data van de towers beschikt.

Ik zou een functie aanmaken in de tower class:


public function canShootEnemy(enemy:Enemy):Boolean {

}


en vervolgens in de enterFrame van de game classe:


private function enterFrameHandler(e:Event):void {
for (var i:int = 0; i < towerArray.length; i++) {
for (var j:int = 0; j < enemyArray.length; i++) {
if (Tower(towerArray[i]).canShootEnemy(Enemy(enemyArray[j]))) {
//..
}
}
}
}

Dauntless
%Europe/Berlin %776 %2009, 18:38
Dat zou inderdaad nog beter zijn. Een tussenin optie, is om een interface te maken 'IEnemyDeliverer' welke dan een getEnemy() heeft. Op die manier zijn ze niet zo echt afhankelijk.

if(enemyArr.length >= 0)
{
//...
}
- Als enemyArr null is heeft hij ook geen length en krijg je een error
- Een length kan niet negatief zijn
- Als hij = 0 is kan je weinig doen
-> Wellicht bedoel je > 0 ?

W0utR
%Europe/Berlin %791 %2009, 19:00
Jah, dat controleren was een foutje van mij.

Maar als ik goed begrijp wat je bedoeld zou het beter zijn om een extra classe aan te maken die de main classe meekrijgt?