Meine Gedanken zur Spieleprogrammierung - Teil 4 - Objekte

D

DocJunioR

Ambitioniertes Mitglied
7
Lange hab ich nichts von mir hören lassen- Ich hab halt irgendwie viel zu tun mit einer zeitweilig indisponierten Hand und drei Kindern..

Nichts desto trotz habe ich meine Gedanken weiter gesponnen und ihr müsst jetzt das Ergebnis ausbaden ;)

Dieses Mal geht es um die eigentlichen Objekte, die im Spiel darzustellen sind. Diese zeichnen sich duch eine Position und eine Dimension aus. Beides zusammen beschreibt ein Rechteck, welches interessanterweise schon als Klasse im Android existiert. Diese können wir zur Haltung und Bearbeitung unserer Positionsinformationen nutzen.

Code:
public class GameObject implements IBehaviourManager{
    Rect location = new Rect(0, 0, 31, 31);
    /**
     * @param newLeft
     * @param newTop
     * @see android.graphics.Rect#offsetTo(int, int)
     */
    public void offsetTo(int newLeft, int newTop) {
    location.offsetTo(newLeft, newTop);
    }

    /**
     * @param dx
     * @param dy
     * @see android.graphics.Rect#inset(int, int)
     */
    public void inset(int dx, int dy) {
    location.inset(dx, dy);
    }

    /**
     * @param dx
     * @param dy
     * @see android.graphics.Rect#offset(int, int)
     */
    public void offset(int dx, int dy) {
    location.offset(dx, dy);
    }

    /**
     * @param left
     * @param top
     * @param right
     * @param bottom
     * @see android.graphics.Rect#set(int, int, int, int)
     */
    public void set(int left, int top, int right, int bottom) {
    location.set(left, top, right, bottom);
    }

    /**
     * @return the location
     */
    public Rect getLocation() {
    return location;
    }

    /**
     * @param location
     *            the location to set
     */
    public void setLocation(Rect location) {
    this.location = location;
    }
}

So weit, so gut. jetzt muss eigentlich nur noch etwas dargestellt werden. Hierzu können wir ein einfaches Bitmap nutzen. bei den meisten Spielen ist das Spielerobjekt im Zentrum des Geschehens, weshalb wir eine Möglichkeit benötigen, es beim Darstellen dort hin zu bewegen. Hierzu sehen wir zwei Parameter vor, die beim Darstellen einfach von den originalen Koordinaten des Objekts abgezogen werden. Ich werde darauf später noch einmal zurück kommen, wenn es darum geht, den Hintergrund zu zeichnen.

Code:
    /**
     * @return the image
     */
    public Bitmap getImage() {
    return image;
    }

    /**
     * @param image
     *            the image to set
     */
    public void setImage(Bitmap image) {
    this.image = image;
    }

    /**
     * Zeichne das Objekt auf den Canvas
     * 
     * @param canvas
     */
    void draw(Canvas canvas, int dx, int dy) {
    if (image != null) {
        Rect src = new Rect(0, 0, this.image.getWidth(), this.image
            .getHeight());
        
        Rect rect = new Rect(this.getLocation());
        // bewege das Objekt relativ zum Hintergrund/Spielerobjekt
        rect.offset(-dx, -dy);
        
        canvas.drawBitmap(this.image, src, rect, null);
    } else {
        canvas.drawRect(this.location, null);
    }
    }

Somit haben wir schon einmal ein einfaches Darstellungsobjekt. Ein Spiel besteht aber eigentlich selten aus nur einem Objekt. Wir benötigen eine ganze Welt an Objekten. Hier können wir dann auch gleich noch den Hintergrund des Spieles setzen. Dieser besteht aus einem großen Bild, von dem immer nur der Ausschnitt dargestellt wird, bei dem der Spieler im Zentrum ist. Eigentlich sieht es dann so aus, als ob dich der Hintergrund unter dem Spielerobjekt bewegt. Hierzu wird das Spielerobjekt gesondert vorgehalten, es wird allerdings zudem in die normale Objektliste der Welt aufgenommen.

Code:
public class World {
    Vector<GameObject> objects = new Vector<GameObject>();
    GameObject player;
    
    Bitmap backgroundImage;
    // neues Viereck zum Darstellen des Hintergrunds;
    Rect view = new Rect(0,0,319,459);

    /**
     * 
     * @param context
     */
    public World (Context context) {
    try {
        InputStream is = context.getAssets().open("worldbg.png");
        backgroundImage = BitmapFactory.decodeStream(is);
    } catch (IOException e) {
        e.printStackTrace();
    }
    }
    
    /**
     * centers the view to the object
     * @param rect
     */
    public void centerTo (Point point) {
    int x = point.x;
    int y = point.y;
    
    x -= this.view.width() / 2;
    y -= this.view.height() / 2;
    
    this.view.offsetTo(x, y);
    
    }
    
    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.IWorld#getObjects()
     */
    public Iterator<GameObject> getObjects() {
    return objects.iterator(); 
    }
    
    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.IWorld#addObject(de.docjunior.gameengine.objects.GameObject)
     */
    public void addObject (GameObject obj) {
    this.objects.add(obj);
    }
    
    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.IWorld#removeObject(de.docjunior.gameengine.objects.GameObject)
     */
    public void removeObject (GameObject obj) {
    this.objects.remove(obj);
    }

    @Override
    public GameObject getPlayer() {
    return player;
    }

    @Override
    public void setPlayer(GameObject obj) {
    // set as player
    player = obj;
    // add to drawables
    addObject(obj);
    }
    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.IWorld#draw(android.graphics.Canvas)
     */
    public void draw(Canvas canvas) {
    // draw background image
    this.centerTo(player.getPosition());
    
    if (this.backgroundImage != null) {
        canvas.drawBitmap(backgroundImage, this.view, new Rect(0,0,319,459), null);
    }
    
    // draw child objects
    Iterator<GameObject> it = getObjects();
    while (it.hasNext()) {
        GameObject obj = it.next();
        obj.draw(canvas, this.view.left, this.view.top);
    }
    
    }

}

Diese Welt wird in unserem GamePanel instanziiert und aus diesem heraus wird die draw methode dann innerhalb von onDraw aufgerufen, welches- wir erinnern uns- alle 25ms von unserem GameThread gestartet wird. Sämtliche Objekte werden jetzt dargestellt.

Jetzt kann unsere Welt zwar alles darstellen, aber es ist noch sehr starr. Nichts bewegt sich, kein Objekt kann irgendwas. Ich habe jedem GameObjekt eine Liste von Verhaltensmanagern angehängt. Dies sieht dann ungefähr so aus:

Code:
public interface IBehaviourManager {
    public void update (World world, GameObject object);
}

Jedes Objekt bekommt einen Vektor dieser Verhaltensmanager, wodurch man bestimmte Dinge, wie Collision Detection nur einmal implementieren und an sämtliche Objekte anhängen muss. Unser Objekt muss jetzt natürlich aufgerufen. Das geschieht analog zum Draw vom GameThread im GameLoop über World und Object.

Das Object wird entsprechend erweitert:
Code:
    Vector<IBehaviourManager> managers = new Vector<IBehaviourManager>();
    
    /**
     * add a behaviour manager
     * @param manager
     */
    public void addBehaviourManager(IBehaviourManager manager) {
    this.managers.add(manager);
    }

    @Override
    public void update(IWorld world, GameObject object) {
    Iterator<IBehaviourManager> it = this.managers.iterator();

    // update children
    while (it.hasNext()) {
        it.next().update(world, object);
    }
    }

Unsere Welt ruft sämtliche Objekte zum Updaten auf.

Code:
    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.IWorld#update()
     */
    public void update() {
    Iterator<GameObject> it = getObjects();
    while (it.hasNext()) {
        GameObject obj = it.next();
        obj.update(this, obj);
    }
    }

Diese Verhaltensmanager können durch casting auch auf bestimmte Subklassen des GameObjects spezialisiert werden. Beispielsweise habe ich hier mal die Fortbewegung eines Raumschiffes über ein solches Verhaltens-Management gesteuert. Dieses dient nicht nur dem Spielerobjekt, sondern auch den anderen Objekten als Bewegungssteuerung. Die Klasse ist als Singleton instanziiert, weil das zu bearbeitende Objekt als Parameter übergeben wird. Dies spart eine Menge Ressourcen.
Lediglich das Anhängen des Managers darf man nicht vergessen.

Code:
public class SimpleSpaceShipBehaviour implements IBehaviourManager {
    private static SimpleSpaceShipBehaviour instance = null;
    
    /**
     * @return the instance
     */
    public static IBehaviourManager getInstance() {
    if (instance == null) {
        instance = new SimpleSpaceShipBehaviour();
    }
        return instance;
    }
    
    private SimpleSpaceShipBehaviour() {
    // do nothing - just for singleton instantiation
    }

    /* (non-Javadoc)
     * @see de.docjunior.gameengine.objects.GameObject#update(de.docjunior.gameengine.objects.IWorld, de.docjunior.gameengine.objects.GameObject)
     */
    @Override
    public void update(IWorld world, GameObject object) {
    SpaceShip ship = (SpaceShip) object;
    ShipData actual = ship.getActual(); 
    // collision detection
    Iterator<GameObject> it = world.getObjects();  
    while (it.hasNext()) {
        SpaceShip obj = (SpaceShip)it.next();
        // man kann nicht mit sich selbst kollidieren
        if (obj != ship) {
        Rect r = obj.getLocation();
        if (ship.intersects(r.left, r.top, r.right, r.bottom)) {
            ship.hitBy(obj.getWeight());
        }
        }
    }
    
    int diagonal = (int)(.71 * ship.getActual().getSpeed());
    // move
    switch (ship.getFrame()) {
    case 0:
        ship.offset(0, -actual.getSpeed());
        break;
    case 1:
        ship.offset (diagonal, -diagonal);
        break;
    case 2:
        ship.offset(actual.getSpeed(), 0);
        break;
    case 3:
        ship.offset (diagonal, diagonal);
        break;
    case 4:
        ship.offset(0, actual.getSpeed());
        break;
    case 5:
        ship.offset (-diagonal, diagonal);
        break;        
    case 6:
        ship.offset(-actual.getSpeed(), 0);
        break;
    case 7:
        ship.offset (-diagonal, -diagonal);
        break;
        
    }
    }
}

So, damit habe ich mich erst einmal wieder lang und breit ausgelassen.
Der Einfachheit halber (ich denke, es ist schon so ne Menge Info gewesen) habe ich animierte Objekte weg gelassen.
Als Hinweis hierzu: das Canvas.drawBitmap() kann auch Bereiche eines Bildes darstellen, wie im Hintergrund Malen von World gezeigt. Hiermit ist es möglich, Animationsschritte eines Objekts in ein Bild zu legen und dann den jeweiligen Bereich darzustellen.

Anmerkungen, konstruktive Kritik und Spenden werden immer gern angenommen ;)
 
  • Danke
Reaktionen: junior2
Hi...

vielen Dank für die tolle Anleitung :)

Was (ich denke nicht nur) mir zum Verständnis helfen würde, wäre dein Projekt im SourceCode...
Ich stecke bei ein zwei Stellen fest und wollte mal spicken :D

Ich bedanke mich einfach schonmal ...
 
das hab ich befürchtet. gerade diese objekte sind dann ziemlich verwirrend..
muss ich mal schauen, weil mein aktuelles projekt rück ich nicht raus ;)
aaber fragen kannst ja, hier haben sicher einige leute auch interessante gedanken.
 

Ähnliche Themen

Temucin
Antworten
1
Aufrufe
535
jogimuc
J
D
Antworten
3
Aufrufe
452
jogimuc
J
C
  • coreytaylor211
Antworten
7
Aufrufe
2.406
coreytaylor211
C
Zurück
Oben Unten