/* * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine. * * Copyright (c) 2011 - 2014 Michael Andonie and contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ea; import ea.internal.collision.BoxCollider; import ea.internal.collision.Collider; import ea.internal.collision.ColliderGroup; import ea.internal.gra.PixelFeld; import ea.internal.util.Logger; import java.awt.*; import java.util.ArrayList; /** * Eine Figur ist eine aus einer Datei geladene Sammlung von Pixeln, die orientierungsmaessig * rechteckig gehandelt werden.<br /> <code> //Die Figur laden<br /> Figur meineFigur = new * Figur(30, 100, "meineFigurDateiImProjektordner.eaf"); //Laedt die Figur und setzt sie an Position * (30|100) <br /><br /> //Die Bewegung der Figur starten (muss nicht ausgefuehrt werden, Sa * standard) <br /> meineFigur.animiertSetzen(true);<br /><br /> // Die Figur an dem entsprechenden * Knoten zum halten und Zeichnen anmelden (In diesem Fall die Wurzel in der Klasse Game)<br /> * wurzel.add(meineFigur);<br /> </code><br /> <br /> <br /> Dies ist einfachste Methode, eine Figur * zu laden.<br /> Der Figureneditor zum Erstellen der zu ladenden ".eaf"-Dateien ist als * ausfuehrbare ".jar"-Datei fester Bestandteil des Engine-Alpha-Programmierkits. * * @author Michael Andonie */ public class Figur extends Raum { private static final long serialVersionUID = -1063599158092163887L; /** * Eine Liste aller Figuren. */ private static ArrayList<Figur> liste; /** * Die einzelnen Bilder der Figur.<br /> hat es mehr als eines, so wird ein periodischer Wechsel * vollzogen. */ protected PixelFeld[] animation; /** * In diesem Intervall wird die Figur animiert. */ private int intervall = 100; /** * Der Index des Aktuelle benutzten PixelFeldes. */ private int aktuelle = 0; /** * Gibt an, ob die Figur gerade animiert werden soll. */ private boolean laeuft; /** * Gibt an, ob diese Figur gerade entlang der X-Achse (waagrecht) gespiegelt wird. */ private boolean spiegelX = false; /** * Gibt an, ob diese Figur gerade entlang der Y-Achse (senkrecht) gespiegelt wird. */ private boolean spiegelY = false; static { liste = new ArrayList<>(); Manager.standard.anmelden((new Ticker() { int runde = 0; @Override public void tick () { runde++; try { for (Figur f : liste) { if (f.animiert()) { f.animationsSchritt(runde); } } } catch (java.util.ConcurrentModificationException e) { // } } }), 1); } /** * Erstellt eine Figur <b>ohne Positionsangabe</b>. Die Figur liegt an Position (0|0). Dieser * Konstruktor vereinfacht das Laden <i>vieler Figuren</i>, z.B. für eine * <code>ActionFigur</code>. * * @param verzeichnis * Das Verzeichnis, aus dem die Figur zu laden ist. * * @see ea.ActionFigur */ public Figur (String verzeichnis) { this(0, 0, verzeichnis); } /** * Standart-Konstruktor für Objekte der Klasse <code>Figur</code>. * * @param x * X-Position; die links obere Ecke * @param y * Y-Position; die links obere Ecke * @param verzeichnis * Das verzeichnis, aus dem die Figur zu laden ist. */ public Figur (float x, float y, String verzeichnis) { this(x, y, verzeichnis, true); } /** * Besonderer Konstruktor fuer Objekte der Klasse <code>Figur</code>. Dieser Konstruktor wird * vor allem intern (fuer Actionfiguren) verwendet. Anders ist nur die Option darauf, dass die * Figur am Animationssystem direkt teilnimmt. Dies ist beim Standart-Konstruktor immer der * Fall. * * @param x * X-Position; die links obere Ecke * @param y * Y-Position; die links obere Ecke * @param verzeichnis * Das verzeichnis, aus dem die Figur zu laden ist. * @param add * Ob diese Figur am Animationssystem direkt teilnehmen soll. (Standard) */ public Figur (float x, float y, String verzeichnis, boolean add) { super(); position = new Punkt(x, y); this.animation = DateiManager.figurEinlesen(verzeichnis).animation; if (add) { liste.add(this); } laeuft = true; } /** * Der parameterlose Konstruktor.<br /> Hiebei wird nichts gesetzt, die Figur hat die Position * (0|0) sowie keine Animationen, die Referenz auf die einzelnen Pixelfelder ist * <code>null</code>.<br /> Dieser Konstruktor wird intern verwendet, um Figurdaten zu laden.<br * /> Daher ist er nicht für die direkte Verwendung in Spielen gedacht. */ public Figur () { liste.add(this); } /** * Löscht ein Animationsbild an einem bestimmten Index und rückt den Rest nach. * * @param index * Der Index des zu löschenden Einzelbildes. * * @throws java.lang.ArrayIndexOutOfBoundsException * Wenn ein Index außerhalb der Größes des internen Arrays gewählt wurde. * @throws java.lang.RuntimeException * Falls nur noch ein Element vorhanden war. Das letzte Element darf nicht entfernt werden! */ @API @SuppressWarnings ( "unused" ) public void animationLoeschen (int index) { // TODO War das vorher buggy? if (animation.length < 2) { throw new RuntimeException("Es muss mindestens ein Pixelfeld erhalten bleiben! Eine " + "weitere Löschung hätte das letzte Element entfernt."); } PixelFeld[] neu = new PixelFeld[animation.length - 1]; System.arraycopy(animation, 0, neu, 0, index); System.arraycopy(animation, index + 1, neu, index, neu.length - index); aktuelle = 0; animation = neu; } /** * Setzt das Animationsbild auf einer bestimmten Position auf ein neues Pixelfeld. * * @param bild * Neues Pixelfeld an der angegebenen Position. Darf nicht <code>null</code> sein! * @param index * Index des zu ersetzenden Pixelfeldes * * @throws java.lang.ArrayIndexOutOfBoundsException * Wenn ein Index außerhalb der Größes des internen Arrays gewählt wurde. * @throws java.lang.IllegalArgumentException * Wenn der Parameter <code>bild</code> <code>null</code> war. */ @NoExternalUse // da PixelFeld in ea.internal ist @SuppressWarnings ( "unused" ) public void animationsBildSetzen (PixelFeld bild, int index) { if (bild == null) { throw new IllegalArgumentException("Parameter bild darf nicht null sein!"); } animation[index] = bild; } /** * Verschiebt die Position eines Animationsbildes.<br /> Hierbei wird ein bisschen mit den * Werten des Arrays gespielt, jedoch kein neues Array erstellt. Sind beide Eingabeparameter * exakt gleich, passiert gar nichts, auch wenn die beiden Werte außerhalb des Arrays liegen * sollten. * * @param indexAlt * Index des zu verschiebenden Bildes * @param indexNeu * Index, den das Bild nach dem verschieben haben soll. * * @throws ArrayIndexOutOfBoundsException * Wenn ein Index außerhalb der Größes des internen Arrays gewählt wurde. */ @API @SuppressWarnings ( "unused" ) public void animationsBildVerschieben (int indexAlt, int indexNeu) { if (indexAlt == indexNeu) { return; } PixelFeld bild = animation[indexAlt]; if (indexAlt > indexNeu) { System.arraycopy(animation, indexNeu + 1, animation, indexNeu, indexAlt - indexNeu); } else { System.arraycopy(animation, indexAlt, animation, indexAlt - 1, indexNeu - indexAlt); } animation[indexNeu] = bild; } /** * Ruft das naechste Bild im Animationszyklus auf.<br /> Sollte nicht von aussen aufgerufen * werden, stellt aber in keinem mathematisch greifbaren Fall ein Problem dar. * * @param runde * Die Runde dieses Schrittes; dieser Wert wird intern benoetigt, um zu entscheiden, ob etwas * passieren soll oder nicht. */ @NoExternalUse public void animationsSchritt (int runde) { if (runde % intervall != 0) { return; } if (aktuelle == animation.length - 1) { aktuelle = 0; } else { aktuelle++; } } /** * Setzt eine neue Animationsreihe. * * @param a * Die neue Animationsreihe. Das Array muss mindestens ein Pixelfeld zum Inhalt haben<br /> * Diese <b>muss</b> aus Pixelfeldern gleicher Maße bestehen! * * @throws java.lang.IllegalArgumentException * Falls <code>animation</code> <code>null</code> war oder keine Elemente enthalten hat. */ public void animationSetzen (PixelFeld[] a) { if (a == null) { throw new IllegalArgumentException("Parameter a darf nicht null sein!"); } else if (a.length < 1) { throw new IllegalArgumentException("Parameter a muss mindestens die Länge 1 haben."); } animation = a; aktuelle = 0; } /** * Setzt die Animationsarbeit bei dieser Figur. * * @param animiert * ob die Figur animiert werden soll, oder ob sie ein Standbild sein soll. */ public void animiertSetzen (boolean animiert) { this.laeuft = animiert; } /** * Gibt an, ob das Bild momentan animiert wird bzw. animiert werden soll. */ public boolean animiert () { return laeuft; } /** * Setzt das aktuelle Animationsbild.<br /> Die Figur, die im Pixelfeldeditor erstellt wurde * besteht den Bildern (1, 2, ..., n), aber <b>ACHTUNG</b>, die Indizes fangen bei <b>0</b> an * und hören dann eins frueher auf (0, 1, ..., (n-1)). Das heißt, dass wenn als Index * <code>5</code> eingegeben wird, ist das Bild gemeint, das im Figureneditor als <code>Bild * 6</code> bezeichnet wurde. * * @param bildIndex * Der Index des anzuzeigenden Bildes */ public void animationsBildSetzen (int bildIndex) { if (bildIndex < 0 || bildIndex >= animation.length) { Logger.error("Achtung! Der zu setzende Bildindex war größer als der größte " + "vorhandene Index oder kleiner 0! Daher wird nichts gesetzt."); return; } aktuelle = bildIndex; } /** * Setzt den Größenfaktor dieser Figur neu. * <p/> * Das heisst, ab sofort wird ein Unterquadrat dieser Figur eine Seitenlänge von Pixeln in Größe * des Faktors haben * * @param faktor * Der neue Größenfaktor */ public void faktorSetzen (int faktor) { for (int i = 0; i < animation.length; i++) { animation[i].faktorSetzen(faktor); } } /** * Setzt sämtliche Farbwerte sämtlicher Bilder der Figur in ihr Negativ.<br /> Dadurch ändert * sich die Erscheinung der Figur. * * @see #heller() * @see #dunkler() * @see #farbenTransformieren(int, int, int) */ public void negativ () { for (int i = 0; i < animation.length; i++) { animation[i].negativ(); } } /** * Hellt alle Farbwerte der Figur auf.<br /> Gegenstück zur Methode <code>dunkler()</code>.<br * /> <b>Achtung:</b><br /> Wegen Rundungsfehlern muss der Aufruf von <code>dunkler()</code> * nach dem Aufruf von <code>heller()</code> nicht zwanghaft zum urspruenglichen Zustand * fuehren! * * @see #dunkler() * @see #negativ() * @see #farbenTransformieren(int, int, int) */ public void heller () { for (int i = 0; i < animation.length; i++) { animation[i].heller(); } } /** * Dunkelt alle Farbwerte der Figur ab.<br /> Gegenstueck zur Methode <code>heller()</code>.<br * /> <b>Achtung:</b><br /> Wegen Rundungsfehlern muss der Aufruf von <code>dunkler()</code> * nach dem Aufruf von <code>heller()</code> nicht zwanghaft zum urspruenglichen Zustand * fuehren! * * @see #heller() * @see #negativ() * @see #farbenTransformieren(int, int, int) */ public void dunkler () { for (int i = 0; i < animation.length; i++) { animation[i].dunkler(); } } /** * Sorgt fuer eine Farbtransformation.<br /> Das heißt, zu jeder Farbe der Figur werden die * RGB-Werte um feste Betraege geaendert (positive oder negative). Sprengt ein entstehender Wert * den Rahmen [0; 255] (zum Beispiel 160 + 100 oder 50 - 80), so bleibt der Farbwert am Rand des * moeglichen Bereiches (also 0 bzw. 255). * * @param r * Der Rot-Aenderungswert (positiv und negativ moeglich) * @param g * Der Gruen-Aenderungswert (positiv und negativ moeglich) * @param b * Der Blau-Aenderungswert (positiv und negativ moeglich) * * @see #heller() * @see #dunkler() * @see #negativ() */ public void farbenTransformieren (int r, int g, int b) { for (int i = 0; i < animation.length; i++) { animation[i].transformieren(r, g, b); } } /** * Faerbt alle Elemente in einer Farbe ein.<br /> Dieser Zustand laesst sich zuruecksetzen mit * der Methode <code>zurueckFaerben()</code>. * * @param farbe * Die Farbe, mit der alle farbenthaltenden Unterquadrate der Figur eingefaerbt werden.<br /> * Eingabe als <code>String</code>, so wie bei den anderen einfachen Farbeingaben auch. * * @see #zurueckFaerben() * @see #einfaerben(Farbe) */ public void einfaerben (String farbe) { einfaerben(Farbe.vonString(farbe)); } /** * Faerbt alle Elemente in einer Farbe ein.<br /> Dieser Zustand laesst sich zuruecksetzen mit * der Methode <code>zurueckFaerben()</code>. * * @param f * Die Farbe, mit der alle farbenthaltenden Unterquadrate der Figur eingefaerbt werden. * * @see #zurueckFaerben() * @see #einfaerben(String) */ public void einfaerben (Farbe f) { for (int i = 0; i < animation.length; i++) { animation[i].einfaerben(f.wert()); } } /** * Setzt, ob diese Figur bei der Darstellung waagrecht zentral gespiegelt werden soll oder * nicht.<br /> Dies aendert die Drehungsrichtung einer Figur von N nach S bzw. umgekehrt. * * @param spiegel * Ist dieser Wert <code>true</code>, so wird die Figur waagrecht gespiegelt im Vergleich zu * ihrer Quelldatei dargestellt. Durch <code>false</code> kann dieser Zustand schnell wieder * zurueckgesetzt werden. * * @see #spiegelYSetzen(boolean) * @see #yGespiegelt() * @see #xGespiegelt() */ public void spiegelXSetzen (boolean spiegel) { this.spiegelX = spiegel; } /** * Setzt, ob diese Figur bei der Darstellung senkrecht zentral gespiegelt werden soll oder * nicht.<br /> So laesst sich extrem schnell z.B. Drehung einer Spielfigur von links nach * rechts im Spiel realisieren. * * @param spiegel * Ist dieser Wert <code>true</code>, so wird die Figur senkrecht gespiegelt im Vergleich zu * ihrer Quelldatei dargestellt. Durch <code>false</code> kann dieser Zustand schnell wieder * zurueckgesetzt werden. * * @see #spiegelXSetzen(boolean) * @see #yGespiegelt() * @see #xGespiegelt() */ public void spiegelYSetzen (boolean spiegel) { this.spiegelY = spiegel; } /** * Diese Methode gibt aus, ob diese Figur derzeit an der X-Achse (waagrecht) gespiegelt ist. * * @return Ist dieser Wert <code>true</code>, wird diese Figur derzeit genau an der X-Achse * gespiegelt dargestellt, im Verhältnis zu der ursprünglichen Figurdatei. * * @see #spiegelXSetzen(boolean) * @see #spiegelYSetzen(boolean) * @see #yGespiegelt() */ public boolean xGespiegelt () { return spiegelX; } /** * Diese Methode gibt aus, ob diese Figur derzeit an der Y-Achse (senkrecht) gespiegelt ist. * * @return Ist dieser Wert <code>true</code>, wird diese Figur derzeit genau an der Y-Achse * gespiegelt dargestellt, im Verhältnis zu der ursprünglichen Figurdatei. * * @see #spiegelXSetzen(boolean) * @see #spiegelYSetzen(boolean) * @see #xGespiegelt() */ public boolean yGespiegelt () { return spiegelY; } /** * Sorgt dafuer, dass nach dem Aufruf von <code>einfaerben(Farbe)</code> die Figur wieder ihre * normalen Farbgegebenheiten kriegt. * * @see #einfaerben(Farbe) * @see #einfaerben(String) */ public void zurueckFaerben () { for (int i = 0; i < animation.length; i++) { animation[i].zurueckFaerben(); } } /** * Diese Methode wird verwendet, um die Figur vom direkten Animationssystem zu loesen. Sie ist * <i>package private</i>, da diese Einstellung nur intern vorgenommen werden soll. */ void entfernen () { liste.remove(this); } /** * Zeichnet das Objekt. * * @param g * Das zeichnende Graphics-Objekt * @param r * Das BoundingRechteck, dass die Kameraperspektive Repraesentiert.<br /> Hierbei soll * zunaechst getestet werden, ob das Objekt innerhalb der Kamera liegt, und erst dann * gezeichnet werden. */ @Override public void zeichnen (Graphics2D g, BoundingRechteck r) { if (r.schneidetBasic(this.dimension())) { super.beforeRender(g, r); animation[aktuelle].zeichnen(g, (int) (position.x - r.x), (int) (position.y - r.y), spiegelX, spiegelY); super.afterRender(g, r); } } /** * @return Ein BoundingRechteck mit minimal nötigem Umfang, um das Objekt <b>voll * einzuschließen</b>. */ @Override public BoundingRechteck dimension () { if (animation != null && animation[aktuelle] != null) { // FIXME: animation[aktuelle] == null => else => Nullpointer return new BoundingRechteck(position.x, position.y, animation[0].breite(), animation[0].hoehe()); } else { return new BoundingRechteck(position.x, position.y, animation[aktuelle].breite(), animation[aktuelle].hoehe()); } } /** * {@inheritDoc} */ @Override public Collider erzeugeCollider () { ColliderGroup cg = new ColliderGroup(); for (BoundingRechteck r : flaechen()) { cg.addCollider(BoxCollider.fromBoundingRechteck(new Vektor(r.x - position.x, r.y - position.y), r)); } return cg; // TODO: Entscheiden, ob genauere Collisionsbehandlung es wirklich wert ist. // return new BoxCollider(new Vektor(dimension().breite, dimension().hoehe)); } @Override public BoundingRechteck[] flaechen () { return animation[aktuelle].flaechen(position.x, position.y); } /** * Gibt den Index des aktuellen Bildes zurueck.<br /> Die Figur, die im Pixelfeldeditor erstellt * wurde besteht den Bildern (1, 2, ..., n), aber <b>ACHTUNG</b>, die Indizes fangen bei * <b>0</b> an und hoeren dann eins frueher auf (0, 1, ..., (n-1)). Das heisst, dass wenn als * Index <code>5</code> zurueckgegeben wird, wird zur Zeit das Bild gezeigt, das im * Figureneditor als <code>Bild 6</code> bezeichnet wurde. * * @return Der Index des aktuell angezeigten Bildes */ public int aktuellesBild () { return aktuelle; } /** * @return Alle PixelFelder der Animation. */ public PixelFeld[] animation () { return animation; } /** * Gibt das Intervall dieser Figur zurueck. * * @return Das Intervall dieser Figur. Dies ist die Zeit in Millisekunden, die ein * Animationsbild zu sehen bleibt * * @see #animationsGeschwindigkeitSetzen(int) */ public int intervall () { return intervall; } /** * Setzt die Geschwindigkeit der Animation, die diese Figur Figuren steuert.<br /> Jed groesser * Die Zahl ist, desto langsamer laeuft die Animation, da der Eingabeparamter die Wartezeit * zwischen der Schaltung der Animationsbilder in Millisekunden angibt! * * @param intervall * Die Wartezeit in Millisekunden zwischen den Bildaufrufen dieser Figur. */ public void animationsGeschwindigkeitSetzen (int intervall) { this.intervall = intervall; } }