Toon posts:

[OOP/AS2] Juiste methode + paar vraagjes

Pagina: 1
Acties:

Verwijderd

Topicstarter
Hallo allemaal, ik ben bezig met het maken met een spelletje in flash. Nu dacht ik, laat ik het gelijk goed doen en het ook netjes in objecten programmeren. Nadat ik meerdere tutorials heb doorgelezen, heb ik een aardig idee hoe het zou moeten. Ik heb dus een begin gemaakt aan mijn weapon class, en het leek me aardig goed, maar dat wilde ik dus graag even controleren. (het is btw actionscript 2.0)

Ik heb de volgende directory structuur voor de classes:
Classes
|- Weapon.as
|- Weapons (dir)
   |- Colt.as
   |- Sten.as


En mijn weapon.as object ziet er als volgt uit:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class Weapon
{
    //STATIC DEFINES
    private static var AMMO_MAX:Number = 1000;
    private static var AMMO_LEFT:Number =  100;
    private static var AMMO_CLIP_SIZE:Number =  14;
    private static var AMMO_CLIP_LEFT:Number =  14;

    //DEFINES
    private var _ammoMax:Number = AMMO_MAX;
    private var _ammoLeft:Number = AMMO_LEFT;
    private var _ammoClipSize:Number = AMMO_CLIP_SIZE;
    private var _ammoClipLeft:Number = AMMO_CLIP_LEFT;
    
    private var _fireSound:Sound;
    private var _emptySound:Sound;
    
    //CONSTRUCTOR
    public function Weapon ()
    {
        this._emptySound = new Sound();
        this._emptySound.attachSound("emptychamber");
    }
    
    //
    //SET FUNCTIONS
    //
    private function setMaxAmmo(max:Number)
    {
        this._ammoMax = max;
    }
    private function setAmmoClipSize(size:Number)
    {
        this._ammoClipSize = size;
    }
    private function setAmmoLeft(left:Number)
    {
        this._ammoLeft = left;
    }
    private function setAmmoClipLeft(left:Number)
    {
        this._ammoClipLeft = left;
    }
    
    //
    //GET FUNCTIONS
    //
    public function getAmmoClipLeft():Number
    {
        return this._ammoClipLeft;
    }
    
    public function getAmmoLeft():Number
    {
        return this._ammoLeft;
    }

    //
    //PUBLIC FUNCTIONS
    //
    public function attachSound(soundName:String)
    {
        this._fireSound = new Sound();
        this._fireSound.attachSound(soundName);
    }
    
    public function shoot():Number
    {
        if(this._ammoClipLeft > 0)
        {   if(this._fireSound)
                this._fireSound.start();
            this._ammoClipLeft -= 1;
            
            return 1;
        }
        else{
             this._emptySound.start();
             return -1;
        }
    }
    
    public function reload()
    {
        this._ammoLeft += this._ammoClipLeft;
        this._ammoClipLeft = 0;
        if(this._ammoClipSize < this._ammoLeft) {
            this._ammoClipLeft = this._ammoClipSize;
            this._ammoLeft -= this._ammoClipSize;
        }else{
            this._ammoClipLeft = this._ammoLeft;
            this._ammoLeft = 0;
        }
    }
    
}


Colt.as:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Weapons.Colt extends Weapon 
{
    //STATIC DEFINES
    private static var AMMO_MAX:Number = 1000;
    private static var AMMO_LEFT:Number = 100;
    private static var AMMO_CLIP_SIZE:Number = 14;
    private static var AMMO_CLIP_LEFT:Number = 14;
    
    private static var SOUND_NAME:String = "colt";
    
    //CONSTRUCTOR
    public function Colt ()
    {
        super();
        setMaxAmmo(AMMO_MAX);
        setAmmoClipSize(AMMO_CLIP_SIZE);
        setAmmoLeft(AMMO_LEFT);
        setAmmoClipLeft(AMMO_CLIP_LEFT);
        attachSound(SOUND_NAME);
    }

}

en sten.as:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Weapons.Sten extends Weapon 
{
    //STATIC DEFINES
    private static var AMMO_MAX:Number = 2000;
    private static var AMMO_LEFT:Number = 200;
    private static var AMMO_CLIP_SIZE:Number = 30;
    private static var AMMO_CLIP_LEFT:Number = 30;
    
    private static var SOUND_NAME:String = "sten";
    
    //CONSTRUCTOR
    public function Sten ()
    {
        super();
        setMaxAmmo(AMMO_MAX);
        setAmmoClipSize(AMMO_CLIP_SIZE);
        setAmmoLeft(AMMO_LEFT);
        setAmmoClipLeft(AMMO_CLIP_LEFT);
        attachSound(SOUND_NAME);
    }
    
}

Ik test mijn code met:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
weapon = new Weapon();
colt = new Weapons.Colt();
sten = new Weapons.Sten();

weapon = colt;

_root.bckButton.onPress = function() {
    weapon.shoot();
}
_root.stenButton.onPress = function() {
    weapon = sten;
}
_root.coltButton.onPress = function() {
    weapon = colt;
}
_root.reloadButton.onPress = function() {
    weapon.reload();
}

Mijn vragen hierover, uiteraard mag je ook slechts 1 van de vragen beantwoorden, als je alleen daar wat op of aan te merken hebt ;) :
  1. Is dit inderdaad netjes OOP, of is dit waardeloos? :D Als er grove fouten in zitten kun je me daar op wijzen zodat ik deze verder kan onderzoeken?
  2. Is de classe structeer zo correct (weapon.as vs. weaponS.colt)? En doe ik het inheriten op de juiste manier?
  3. Horen de sounds (en later graphics) zoals het nu is in de classes? of moet ik die ergens anders heen verplaatsen?
  4. Intern in de reload functie bv, is het daar beter om zoals ik nu doe rechtstreeks de variabelen aan te passen, of moet ik daar ook functies voor gebruiken (zodat je er meer restricties bv maximale waarde aan toe kunt kennen)?
  5. In de test code, is het netjes om zo de wapens te wisselen en te gebruiken?
Hartelijk dank voor alle help!

Verwijderd

ad 1 en 2) Het object georiënteerd denken is goed. Echter, de klasse Colt voegt in deze context niks toe. Wat je hier in feite hebt gedaan, is een klasse gemaakt die een instantie van de klasse Weapon simuleert.

De bedoeling van "inheritance" is dat de ervende klasse iets toevoegd aan de klasse waar deze van erft. In het geval van de Colt, zou je dus bijvoorbeeld een "alternative fire" in de vorm van een zoom lens kunnen hebben.

Wat je hier doet, kun je ook bewerkstelligen door een instantie van de klasse Weapon te maken en daar middels de setters de waarden aan toe kennen.
(Dit geldt overigens ook voor sten.)

ad 3) Alles met betrekking tot een bepaalde klasse kun je het beste in de context van die klasse houden (en het dus als een soort "black box" beschouwen). Nadelig in geval van sounds en graphics zou kunnen zijn dat je telkens je code moet aanpassen als het wapen een ander uiterlijk krijgt. Misschien zou je hiervoor een soort repository kunnen gebruiken, waaruit een bepaald wapen een uiterlijk kan selecteren wanneer deze geïnstantieerd wordt.

ad 4) Dat ligt natuurlijk weer aan de complexiteit van je code (lange code => opdelen). Overigens is het ook iets persoonlijks. Voordeel van de taktiek die je nu hebt gekozen, is dat je restricties slechts eenmalig implementeerd en er dus aan deze restricties voldaan wordt als deze procedure wordt aangeroepen.

ad 5) Dat ligt eraan wat je ermee wilt bereiken, als jij verwacht dat er binnen jou spel een scenario voor kan komen dat met wapen wisseling te maken heeft dan is dit natuurlijk een goede test.
Niet goed gelezen. Ik zie wat je hier mee wil bereiken, maar het ligt me niet lekker. Wat je nu feitelijk doet is het geheugenadres van het object Weapon laten wijzen naar resp. het geheugen van Colt en Sten. Dit is niet echt de juiste manier van object georiërenteerd werken.

Verder maak je ook gebruik van een globale variabele Weapon, probeer globale variabelen altijd te vermijden. In dit geval is het ook niet nodig, aangezien je een instantie van het object wat je nodig hebt tegelijk in de desbetreffende procedure kunt instantiëren en vrijgeven.

[ Voor 18% gewijzigd door Verwijderd op 23-01-2006 13:12 ]


Verwijderd

Topicstarter
Bedankt voor je reactie Vrieler. Ik had zelf ook mijn twijfels over dat weapon gebeuren, vandaar dat hij er ook tussen stond. Ik heb de code herschreven naar (voor nu):
Java:
1
2
3
4
5
6
7
8
9
    switch(weapon){
        case "sten":
            sten.shoot();
            break;
        default:
        case "colt":
            colt.shoot();
            break;
    };


En over de rest. Nou het is uiteraard ook de bedoeling om de classes etc verder uit te breiden en inderdaad specials er in te brengen (zoom, misschien grenade). Ben het aan het maken voor hobby en wat ervaring ermee opdoen :)
Maar moet nog maar eens kijken hoe ik die classes beter kan maken inderdaad. Misschien is het ook wel een idee om een folder per wapen aan te maken met daarin geluiden / graphics en data over het wapen. Heb nog geen idee hoe ik dat dan zou moeten doen, maar klinkt wel leuk om uit te denken.
Het gaat me nu even over het gebruiken van klasses... Een game, enemies, weapons klasse zodat ik die meerdere malen kan gebruiken, maar dan wel op de juiste manier zodat ik het me niet fout aan leer, waar ik bang voor ben.

Om een idee te krijgen van het spel, het wordt een FPS in flash :) Aangezien het om te leren is, bedenk ik dingen "on the go" maar om te kijken of ik het mij inderdaad zou lukken had ik een snel testje gemaakt waarin het idee wel goed duidelijk wordt: Idee

Verwijderd

Dat zijn maar een paar kleine dingen waar je rekening mee moet houden. Ik sneed mijzelf de laatste jaren in de vingers door bijvoorbeeld globale variabelen te gebruiken en deze rechtstreeks te benaderen (bijvoorbeeld in Delphi, waar globale variabelen rechtstreeks te benaderen zijn zodra een form in een ander form wordt ingesloten). Er is eigenlijk nooit een perfecte manier van object georiënteerd denken, en er zijn telkens weer verbeteringen kijk eens hier, een aardig topic.

Het is een kwestie van "separating the concerns" zoals een wijs man ooit eens prevelde.

Je idee ziet er in ieder geval erg goed uit, ik heb ooit eens gestoeid met action script, maar ik heb daar (of had daar toen) geen geduld voor :) ....

Succes!

Verwijderd

Topicstarter
Nu loop ik tegen het volgende probleem aan:
Neem de volgende code snippets:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Player
{
    private var name:String="Undefined";
    private var hitPoints:Number=100;
    private var score:Number=0;
    
    private var weapons:Array;
    private var selectedWeapon:String;
    
    public function Player (name:String)
    {
        this.name = name;
        weapons = new Array();
    }
    
    public function addWeapon (weapon:Weapon)
    {
        weapons[weapon.getName()] = weapon;
    }
    
    public function weapon() : Weapon
    {
        return weapons[selectedWeapon];
    }
    
    public function selectWeapon(name:String)
    {
        this.selectedWeapon = name;
    }
}
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Weapon
{
    //Langere codes, om het overzicht te bewaren implementaties verwijderd

    //DEFINES
    //-knip- (hier staan standaard defines)
    
    //CONSTRUCTOR
    public function Weapon (name:String)
    {   
        // -knip-
    }
    
    //
    //GET AND SET FUNCTIONS
    // -knip- (standaard get and set functies naar de private variabelen)

    //
    //PUBLIC FUNCTIONS
    //
    public function shoot () : Number
    {
        // -knip-
    }
    public function reload () : Void
    {
        // -knip-
    }
    public function addAmmo (amount:Number)
    {
        // -knip-
    }
}
en de volgende code om deze te gebruiken:
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//Initialize the game
player = new Player("--B--");
player.addWeapon(new Weapon("colt"));
player.addWeapon(new Weapon("sten"));
player.addWeapon(new Weapon("shotgun"));
player.selectWeapon("colt");
updateAmmoText();

//Game code
_root.bckButton.onPress = function()
{
    player.weapon().shoot();
}
_root.reloadButton.onPress = function()
{
    player.weapon().reload();
}
_root.stenButton.onPress = function()
{
    player.selectWeapon("sten");
}
_root.coltButton.onPress = function()
{
    player.selectWeapon("colt");
}
_root.shotgunButton.onPress = function()
{
    player.selectWeapon("shotgun");
}
(p.s. ik weet dat de klasses incompleet zijn, maar het gaat om het weapon gedeelte in player voornamelijk atm)

Ben ik hiermee niet juist weer in over treding waar Vrieler mij in ad5 op wees? Ik bedoel, ik verwijs weer naar de bestaande weapon functie via het terug krijgen van de weapon class met player.weapon() (zoals player.weapon().shoot() en player.weapon().reload() ) ?

Waar ik nu benieuwd naar ben is wat de juiste techniek is om in player meerdere wapens te hebben, en met het geselecteerde wapen handelingen uitvoeren zoals schieten en herladen.
Als iemand me hier wat uitleg over zou kunnen geven, graag. Dank!

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 13-04 20:08

Robtimus

me Robtimus no like you

Verwijderd schreef op maandag 23 januari 2006 @ 13:34:
Bedankt voor je reactie Vrieler. Ik had zelf ook mijn twijfels over dat weapon gebeuren, vandaar dat hij er ook tussen stond. Ik heb de code herschreven naar (voor nu):
Java:
1
2
3
4
5
6
7
8
9
    switch(weapon){
        case "sten":
            sten.shoot();
            break;
        default:
        case "colt":
            colt.shoot();
            break;
    };
Met inheritance is dit niet nodig.

Als je Colt en Sten nou subclasses van Weapon laat zijn, en Weapon een shoot method geeft, dan hoef je niet te switchen.
Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Weapon {
    // code here

    public void shoot() {
        // code here
    }
}

...

public class Colt extends Weapon {
    // code here
}
Je kan dan generieker declareren, wat als voordeel heeft dat een wapen veranderen makkelijker is:
Java:
1
Weapon colt = new Colt();
Ook kun je dankzij inheritance gewoon een Weapon meegeven aan functies - of dat nou een Colt, een Sten of een Hauwitzer is maakt niet uit:
Java:
1
2
3
public void doSomething(Weapon w) {
    w.shoot();
}

More than meets the eye
There is no I in TEAM... but there is ME
system specs


Verwijderd

Topicstarter
IceManX schreef op maandag 23 januari 2006 @ 21:49:
[...]
Met inheritance is dit niet nodig.

Als je Colt en Sten nou subclasses van Weapon laat zijn, en Weapon een shoot method geeft, dan hoef je niet te switchen.

Je kan dan generieker declareren, wat als voordeel heeft dat een wapen veranderen makkelijker is.

Ook kun je dankzij inheritance gewoon een Weapon meegeven aan functies - of dat nou een Colt, een Sten of een Hauwitzer is maakt niet uit:
Dat is waar, maar dat kan nog steeds (heb mede na ad1/2 van Vrieler weapon class weer terug gebracht naar 1 class) waardoor het (weer/nog steeds) mogelijk is om weapons apart te defineren:
Java:
1
2
3
4
5
var colt = new Weapon("colt");
var colt = new Weapon("sten");

colt.shoot()
sten.shoot()

Maar het punt is juist dat een player meerdere wapens met zich mee draagt, met verschillende eigenschappen (kracht, snelheid, precisie) en met een van de wapens schiet, wisselen moet dus wel gebeuren.

Na het posten bedacht ik me dat ik uiteraard person een functie shoot() en reload() kan meegeven, en daarin:
Java:
1
2
3
4
    public function shoot()
    {
        return weapons[selectedWeapon].shoot();
    }
Maar dan ben je wel veel van wat je in weapon gedaan hebt aan het herdefineren. Ik weet niet of dit nu juist goed is in OOP, of dat je dat zoveel mogelijk niet wil doen?

Verwijderd

Topicstarter
Ik heb nog eens wat verder gedacht 8)7, en een reply gezien van een eerde draadje waarin iemand probeerde uit te leggen wanneer een functie bij een klasse hoort als het een logische vraag is. In mijn geval zou het dan bijvoorbeeld een logische vraag kunnen zijn om te vragen: kan een player schieten en herladen? Het antwoord daarop lijkt me wel, dus ik denk dat het vrij logisch is om dan inderdaad een functie aan te maken waarin de player class het huidige wapen herlaad
Java:
1
player.reload();
doormiddel van
Java:
1
2
3
public function reload() {
    return weapons[selectedWeapon].reload();
}
Dus ik denk dat ik inderdaad de functies maar moet overnemen die relevant zijn voor de player klasse. Redenatie correct?

Om bijvoorbeeld in de interface(/gui) te laten zien hoeveel kogels er nog voor elk wapen beschikbaar zijn, moet ik wel alle wapens kunnen 'iteraten', en daarvan per wapen de kogels opvragen enz. Ik kan daarvoor wel een iteratie functie maken in weapons, maar als deze meerdere keren opgevraagd worden door andere delen van de code, lijkt het me dat dit problemen gaat geven? correct?
Het lijkt me beter om gewoon de array terug te geven d.m.v.
Java:
1
2
3
public function getWeaponsArray(name:String) : Array {
    return weapons;
}
en dan met een for-loop er door heen te lopen. Of zijn hiervoor betere methoden, rekening houdend met het bovenstaande (d.w.z. meerdere instanties tegelijk aan het iteraten)?

Bedankt voor (eventuele) hulp, naar mijn mening kan ik het beter in 1x goed leren dan eerst fout en daarna proberen het goed aan te leren :)

  • Robtimus
  • Registratie: November 2002
  • Laatst online: 13-04 20:08

Robtimus

me Robtimus no like you

Functionaliteit delegaten komt regelmatig voor. Een persoon schiet inderdaad, een wapen doet dat niet uit zichzelf. Maar het wapen wordt wel afgevuurd. Dus zo'n opzet zou ik zelf prima vinden.

More than meets the eye
There is no I in TEAM... but there is ME
system specs


Verwijderd

Topicstarter
Ik denk dat ik het bovenstaande verhaal nu aardig onder de knie heb, maar nu loop ik tegen het probleem op dat er onvermijdelijk acther aan komt: hoe te koppelen?

Ik heb al avonden het internet lopen af struinen op informatie en geprobeerd zelf alles uit te tekenen en probeer codes opgezet, maar ik kan maar niet beslissen/uitvinden wat nu de juiste methode is, en wordt er helemaal 8)7 van :P

Als ik mijn objects heb ( b.v. GameScene, UserInterface, Player, Enemy, Weapon, Impact, PowerUp, etc), dan kan ik een levelObject alles (lineair?) laten regelen ( b.v. de tegenstanders heen en weer bewegen, laten schieten, controleren of de schoten doel treffen, score bijhouden, powerups toevoegen aan de player). Maar met deze code ben je eigenlijk helemaal niet zo flexibel en opereren de objecten niet op zich zelf. Uiteraard werkt deze methode goed, maar het lijkt me niet dé methode?

Nu kwam ik later op een idee om de objecten events te laten vuren en de objecten met elkaar te koppelen door middel van deze events:
  • koppel mouse en keyboard met UserInterface
  • UserInterface vuurt b.v. een event af onShoot
  • het Impact object registreerd dit shoot event en tekent een inslag op het scherm
  • Impact vuurt vervolgens een event af onImpact
  • Andere objecten zoals enemies, powerUps en objecten als explosieven registreren deze events en controleren of zij geraakt zijn of niet door de inslag.
  • Objecten die geraakt zijn vuren een event af b.v. onDie/onHit voor enemies.
  • Player detecteerd dat hij een enemy heeft geschoten en voegt punten toe aan zijn score
  • enz, enz enz.
Nu lijkt me dit wel een mooie methode omdat je zo alles mooi zichzelf kan laten regelen, en als je iets nieuws toevoegd (zoals b.v. powerups) dan hoef je alleen maar de events die van invloed kunnen zijn op dat object aan elkaar te knopen.

Maar volgens mij is dit minder efficient dan het lijkt, want dan moet je een referentie meesturen van object naar object naar object naar object waarbij er telkens een toegevoegd wordt als een event een ander event opwekt (hoe kun je b.v. anders weten dat player1 enemy heeft geschoten en niet player2?).

Nu eigenlijk de vraag: hoe kom ik er nu achter wat een mooie methode is om de objecten op elkaar te laten reageren waarin het wel flexibel en overzichtelijk blijft? Kunnen jullie misschien wat keywoorden geven dat ik er op kan googelen. Of een methode kort beschrijven die gangbaar is voor dit soort problemen? Ik ben namelijk al tijden aan het zoeken en lezen maar schiet er weinig mee op, omdat ik niet duidelijk voor mezelf kan krijgen waar ik nu precies naar opzoek ben (ja, een methode, maar welke :? )
Pagina: 1