Tutorial: Spieleprogrammierung mit Flex Part 2

Dieser Artikel wurde von Hanisch, Timo und Do, Hoang Viet am Sonntag, 26. August 2012 verfasst. Aktualisiert: 20.11.2013 16:40:53

Der zweite Teil vom ActionScript Tutorial, bei dem ein Snake-Klon programmiert wird. Enthält den Aufbau von einem Tile-Based Spielfeld sowie dem Anfang vom erstellen der anderen benötigten Klassen.

In dem heutigen Part werden wir hauptsächlich auf die Idee der Spielewelt in unserem Snake-Klon eingehen. Aber zuvor gibt es noch ein paar Sachen zu ActionScript allgemein, die sich doch etwas von anderen Programmiersprachen unterscheiden.

ActionScript hat einige Besonderheiten

Es gibt drei Dinge, auf die wir zuvor eingehen sollten, da diese innerhalb des Tutorials angewandt werden. Dies sind zum einen Arrays, zum anderen getter/setter und das dynamische Typsystem von ActionScript.

Dynamische Typisierung

In ActionScript kann man ähnlich wie in z.B. Python und JavaScript Variabeln mit dynamisch e Typisierung deklarieren

var i = 1;
i = true;
i = "Hallo!";

i kann sowohl Integer- als auch Boolean- oder String-Werte speichern.

Diese Freiheit stellt jedoch eine potentielle Fehlerquelle dar. So ist zum Beispiel möglich, dass 0 gleich 1 ist

var a = "0";
var b = false;
var c = "1";
trace(a == b == c);

Tatsächlich wird in dem Beispiel zuerst "0" mit false verglichen. 0 wird bei AS dynamisch zu false konvertiert, daher gilt

trace(true == "1");

"1" ist für true. Daher gilt die Aussage als wahr. Dieses Beispiel verdeutlich eine potentielle Fehlerquelle bei dynamische Typisierung. Daher sollte man sich von Anfang an versuchen dynamische Typisierung zu vermeiden. Eine Möglichkeit ist es, bei der Variablendeklaration Typen zu spezifizieren:

var i : int = 0;
var j : Boolean = true;
var l : String = "Hallo";

Inwiefern löst es aber das "0" == "1" Problem? In dem der Programmierer sich selbst zwingt, immer den Typ anzugeben, vermeidet er die dynamische Konvertierung. Eine Anweisung der Art

var a:int = "0";
var b:int = false;
var c:int = "1";

liefert einen Compilerfehler, da der Typ nicht mit der Deklaration überein stimmt. AS kennt folgende primitive Typen:

  • Int: 32-Bit Ganzezahl
  • UInt: 32-Bit vorzeichenlose Ganzezahl
  • Number: 64-Bit Gleitkommazahlen
  • String: Zeichenketten
  • Boolean: True oder False

Arrays

Arrays in ActionScript werden nicht wie in anderen Sprachen einfach als Array von einem Typ erzeugt

//Java z.B.
int [] array = new int[10];

sondern als eigenes Objekt vom Typ Array. In diesem Array können dann verschiedene Typen gespeichert werden.

var array : Array = new Array();

array[0] = "test";
array[1] = 1.25;

AS kennt neben indizierte Arrays auch assoziative Arrays. Es ist also möglich neben Intergerwerte auch Strings oder beliebige andere Objekte als "Key" zu verwenden:

array["ciao"] = "bye";

Wir erzeugen also keinen wirklichen Array sondern eine Dictionary. Das erklärt auch, warum eine durchgängige Nummerierung bei indizierten Arrays nicht nötig ist:

array[0]   = "a";
array[1]   = "b";
array[10] = "j";

Es wird also nicht wie bei Programmiersprachen wie C/C++ etwa Pointerarithmetik über den Index veranlasst, sondern ein Look-Up in einer Dictionary. AS erlaubt sogar die Verwendung von verschiedenen Typen als Index zur selben Zeit:

array[0]  = "a";
array["a"]  = 0;

Statt einfacher String-, Integer- oder Booleanwerte können auch Arrays übergeben werden. Auf dieser Weise erhalten wir mehrdimensionale Arrays :

var array2d : Array;
array2d = new Array();
array2d[0] = new Array();
array2d[0][0] = "hallo";
trace(array2d[0][0]); //Gibt hallo aus!

Getter/Setter Methoden

Als nächstes kommen wir zu einem weiteren Sprachelement von AS, dass vorallem bei C# Entwickler längst bekannt ist: Getter und Setter-Methoden! Hierbei wird im Grunde eine Klassenvariable über eine get und eine set Methode emuliert. Die get Methode wird immer aufgerufen, wenn der Wert abgefragt werden soll und die set Methoden, wenn ein neue Wert gesetzt werden soll.

Hierfür stellt die Sprache die Schlüsselworte get und set bereit. Wie man sie verwendet verdeutlicht folgender Codeausschnitt.

class MyClass {
    var _angle : Number;
    
    public function set angle(_angle : Number) : void {
        if(_angle < 0) {
            this._angle = _angle + 360;
        } else if(_angle > 360) {
            this._angle = _angle - 360;
        } else {
            this._angle = _angle;
        }
    }
    public function get angle() : Number {
        return _angle;
    }
}

Die Klasse MyClass hat nun eine Klasseneigentschaft "angle" über die beiden get und set Methoden realisiert. Die Verwendung diese Eigentschaft ist für den Anwenderprogrammierer nicht anders als anderen Klasseneigentschaften:

var meins : MyClass = new MyClass();
    
meins.angle = 42;
trace(meins.angle);

 Es ist jedoch ein mächtiges Sprachelement. Weil die get und set Methoden im Grunde Funktionen sind, können Sie innerhalb von Interfaces definiert werden und somit auch überschrieben werden.

Zudem kann durch das Weglassen von get oder set eine readonly oder writeonly Klasseneigentschaft definiert werden.

Auf ins Spiel

Anfangen werden wir mit dem Spielfeld, welches neben der Spielidee eines der wichtigsten Elemente eines Spieles ist. Unser Snake-Klon wird ein tilebasierendes Spielfeld nutzen. Dies erlaubt es uns später relativ einfach eine Kollisionsabfrage einzubauen, um z.B. Hindernisse auf dem Spielfeld zu verteilen. Zuvor werden wir jedoch nocheinmal auf unsere Main.mxml (In unserem Fall Tutorial.mxml) eingehen und deren Aufbau. 

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" 
               width="640" 
               height="640"
               frameRate="30"
               creationComplete="init()">
    <fx:Declarations>
        <!-- For non-visual elements (e.g. services, value-objects) -->
    </fx:Declarations>
    <fx:Script><![CDATA[
        public function init() : void { 

        }
    ]]></fx:Script>
</s:Application>

Die erste zusätzliche Klasse, die wir uns erstellen, ist die Game-Klasse. In ihr werden wir die gesamte Logik des Spiels auslagern (Gameloop, etc.). Anfangs sieht die Gameklasse noch sehr leer aus.

package de.draphony.actionscript.tutorial{
    public class Game{
        public function Game(){
            
        }
        
        //Wird später für regelmäßige Gameupdates genutzt
        public function gameloop(delta : Number) : void {

        }
    }
}

Diese Game-Klasse ist ohne weitere Klassen relativ nutzlos. Wir werden sie jedoch zunächst einmal in der Tutorial.mxml instanziieren. Dazu fügen wir in der Tutorial.mxml im Codeblock (außerhalb der init-Methode) folgende Zeile hinzu

private var game : Game = new Game();

Der nächste Schritt, den wir machen, ist das Erstellen einer Klasse, die sich um das Spielfeld kümmert. 

package de.draphony.actionscript.tutorial {
    import mx.core.UIComponent;
    
    public class GameField extends UIComponent {
        private var tiles : Array = new Array();
    
        public function GameField(tilesWidth : int, tilesHeight : int){

        }
    }
}

Das Spielfeld wird unsere Hauptdarstell-Klasse, deshalb erbt sie von UIComponent und wird später als Element von unserem Flashfilm (Flashobjekte sind immer Flashfilme) hinzugefügt. Das Array tiles soll unser Spielfeld repräsentieren und alle Tiles enthalten. Dafür brauchen wir zusätzlich noch eine Tile-Klasse.

package de.draphony.actionscript.tutorial {
    import flash.display.Sprite;

    public class Tile extends Sprite {
        //Standard Tile-Größen
        public static var TILE_WIDTH : int = 32;
        public static var TILE_HEIGHT: int = 32;
        
        private var color : int;
        private var _walkable : Boolean;

        public function Tile(color : int = 0x000000, _walkable : Boolean = true){
            this.color = color;
            this._walkable = _walkable;
        }

        public function set walkable(_walkable : Boolean) : void {
            this._walkable = _walkable;
        }

        public function get walkable() : Boolean {
            return _walkable;
        }

        public function draw(x : int, y : int) : void {
            this.x = x;
            this.y = y;
            graphics.beginFill(color);
            graphics.drawRect(0,0,TILE_WIDTH,TILE_WIDTH);
            graphics.endFill();
        }
    }
}

Die Tile-Klasse erbt von der Sprite-Klasse, da wir sie später zeichnen und unserem Spielfeld hinzufügen wollen. Zunächst dürfen Tiles nur aus Farben bestehen und keine Bilder repräsentieren. Dazu brauchen wir ein Feld color, welches die Farbe des Tiles repräsentiert. Hinzu kommt das Feld _walkable. Dieses Feld soll später für unsere Kollisionsabfragen genutzt werden um zu bestimmen, ob der Spieler sich an dieser Stelle überhaupt bewegen darf. Standardwert für color ist Schwarz (0x000000) und für _walkable true. Die wohl wichtigste Methode innerhalb dieser Klasse ist draw. Ihr werden x und y Koordinaten übergeben, um das Tile an diesen Koordinaten auf der Fläche des Flashfilms zu zeichen. In der Methode setzen wir zunächst die neuen Koordinaten des Tile-Sprites und beginnen dann das Zeichnen auf der Grafik des Tiles. Damit ist die Tile-Klasse auch ersteinmal fertig und kann in GameField verwendet werden. Dort müssen wir nun den Konstruktor anpassen.

public function GameField(tilesWidth : int, tilesHeight : int){
    for(var i : int = 0; i < tilesWidth;i++){
        tiles[i] = new Array(); //Wir wollen mit einem 2D-Array arbeiten!
        for(var j : int = 0; j < tilesHeight; j++){
            if(i == 0 || i == tilesWidth - 1 || j == 0 || j == tilesHeight - 1) {
                //Der Rand der Map soll nicht begehbar sein!
                tiles[i][j] = new Tile(0x555555,false);
            } else {
                //Die restliche Spielfläche soll grün sein
                tiles[i][j] = new Tile(0x00FF00); 
            }
            // Da tiles nur Objekte speichert, müssen die Tiles berechnet werden
            (tiles[i][j] as Tile).draw(i * Tile.TILE_WIDTH,j * Tile.TILE_HEIGHT);
            addChild(tiles[i][j]);
        }
    }
}

Der Konstrukter erstellt nun ein 2D-Array aus Tiles, welche die einzelnen Felder des Spielfeldes repräsentieren. Nun wollen wir unser Spielfeld aber auch auf dem Bildschirm sehen. Dazu erzeugen wir in unserer Game-Klasse eine Variable für das Spielfeld.

private var _gamefield : GameField;

public function Game(){
    _gamefield = new GameField(640/Tile.TILE_WIDTH,640/Tile.TILE_HEIGHT);
}

public function get gamefield() : GameField {
    return _gamefield;
}

Das Spielfeld erzeugen wir mit sovielen Tiles, dass der gesamte Flashfilm ausgefüllt ist. Um es nun endgültig zu sehen, fügen wir folgende Zeile in der init Methode unserer Tutorial.mxml Datei hinzu:

this.addElement(game.gamefield);

Wenn man nun das Programm startet, wird man folgendes vor sich haben:

BeispielSpielfeld.PNG

Ein schönes grünes Rechteck! Im nächsten Part des Tutorials werden wir uns mit dem Spieler und seiner Steuerung und Darstellung auseinandersetzen.

 
blog comments powered by Disqus

Google Anzeigen

Neusten Blogeinträgen

  • CSS: Animationen

    CSS3 bietet von Haus aus Möglichkeiten zur Definition von Animationen über die Property animation. Wie es funktioniert zeigen wir in diesem Artikel.

  • CSS: Alternierende Tabellenzeilen

    Um die Lesbarkeit von Tabellen zu steigern, bietet die CSS die Möglichkeit Tabellenzeilen alternierend zu färben. Wie das funktioniert zeigen, wir ...

  • Unity3D: Konsolenausgabe formatieren

    Die Unitykonsole ist einer der meistgenutzten Feature wenn es bei Unity um Fehlersuche und -behandlung geht. Sie wird dabei schnell voll und verli ...

  • Unity3D: Singleton für langsame Operationen

    Operationen wie Object.FindObjectOfType werden in der Dokumentation von Unity3D Entwickler selbst als sehr langsam beschrieben. Aus dem Grund wird ...

  • Unity3D: Optionale Parameter

    Unity3D (4.3.1) unterstützt keine optionale Parameter, wenn die betroffene Skriptdatei in einem Namespace definiert wird.

Popular Threads

Share it on your network