# 2D Projektil Flugbahn



## LoN_Nemesis (13. Sep 2006)

Hallo,

ich habe folgendes Problem:

Ich programmiere gerade ein Rollenspiel mit rundenbasierenden Kämpfen. Die Spielewelt ist in Tiles aufgebaut. Nahkampfangriffe habe ich schon implementiert, war kein Problem. Nun hänge ich aber bei Fernkampfangriffen wie Bogenschüssen oder auch Zauber wie Feuerbälle und ähnliches etwas fest.

Ich hätte gerne, dass das Projektil in gerade Linie auf das gewählte Ziel zufliegt. Das geht auch soweit, nur sieht es sehr bescheiden aus, da das Projektil seine Ausrichtung nicht verändert. Also als Beispiel:




```
0 0 0 0 0 0 0 0 0 0
0 0 0 A 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 Z 0 0 0 0
0 0 0 0 0 0 0 0 0 0
```

A ist der Angreifer und Z ist das Ziel. Wenn ich nun als Projektil Grafik ein beliebiges Bild verwende, dann "zeigt" z.B. ein von mir gemalter Pfeil immer nur nach unten, obwohl er nach unten rechts fliegt. Die groben Fälle habe ich schon abgefangen, also z.B. wenn Z jetzt der Angreifer wäre und A das Ziel, dann zeigt er nach oben. Ich habe einfach 4 verschiedene Bilder, und je nachdem ob die x- oder y-Distanz grösser ist, wähle ich das entsprechende aus. Aber ich bin damit nicht zufrieden. Gibt es eine Möglichkeit Bilder stufenlos zu rotieren? Habe in der API nichts gefunden.

Wäre nett wenn jemand einen Rat wüsste.

Edit: Achja um das klarzustellen: Das Projektil selbst fliegt nicht in Tiles, sondern pixelgenau. Wenn ich es in Tiles fliegen lasse, dann bräuchte ich ja im Grunde nur 8 verschiedene Bilder (für jede mögliche Bewegungsrichtung 1). Aber das sieht dann auch sehr unnatürlich und abgehackt aus.


----------



## m@nu (13. Sep 2006)

gucksch'du hier...
:arrow: http://java.sun.com/docs/books/tutorial/2d/display/transforming.html

das ist lediglich ne möglichkeit, das Sprite zu rotieren... wie du den entsprechenden "rotationswert" berechnen musst, wär ich jetzt auch überfragt...


----------



## LoN_Nemesis (13. Sep 2006)

Das verstehe ich irgendwie nicht, da wird ein Graphics2D Objekt rotiert, also der gesamte Bildschirm oder wie?


----------



## LoN_Nemesis (13. Sep 2006)

So, ganzen Nachmittag rumprobiert, das mit dem Rotieren ist sehr sehr umständlich und einfach keine gute Lösung. Ich habe jetzt einfach 8 verschiedene Bilder pro Projektil, und wähle dann je nach Richtung das am besten Passende aus. Sieht eigentlich ganz ok aus, zwar nicht perfekt realistisch, aber naja 

Falls es nur wenig verschiedene Projektile gibt, kann man die auch selbst mit Pixel darstellen und so je nach Flugrichtung anpassen. Oder man nimmt einfach perfekt symetrische Projektile, wie zum Beispiel Kugeln oder Quadrate, dann hat man das Problem nicht.


----------



## Wildcard (13. Sep 2006)

Was genau ist an dieser Methode kompliziert?
http://java.sun.com/j2se/1.5.0/docs/api/java/awt/Graphics2D.html#rotate(double)  ???:L


----------



## LoN_Nemesis (14. Sep 2006)

Das Problem ist, dass ich damit nicht ein Image rotiere sondern ein Graphics2D Objekt, und ich weiss ehrlich gesagt nicht wie ich das ineinander umwandele bzw richtig darstellen lassen kann


----------



## Moonlight1234 (15. Sep 2006)

Mit createGraphics() erzeugst du aus der BufferedImage ein Graphics-Object.
Änderungen am Graphics-Object finden auch an der BufferedImage statt.
Du mußt also nicht das Graphics-Object wieder in eine BufferedImage umwandeln.


----------



## Apo (15. Sep 2006)

das Rotationsproblem hatte ich auch
Mir hatte man hier gut geholfen und so funktioniert es wunderbar:

Kopiere vor dem Erstellen der Rotationstransformation die vorherige Transformation:


```
AffineTransform kopie = (AffineTransform)g.getTransform().clone();
```

Dann kannst Du nach dem Zeichnen des gedrehten Images einfach die alte Transformation wieder herstellen:


```
g.setTransform(kopie);
```


----------



## LoN_Nemesis (17. Sep 2006)

Ich komme nicht weiter und verstehe nicht wo das Problem ist. Beim Rotieren bin ich noch gar nicht, ich schaffe es nichtmal mein Image ganz normal als BufferedImage auf dem Screen ausgeben zu lassen. Hier mein Code:


```
projectile = spell.getFlyingImage();
rotProjectile = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = rotProjectile.createGraphics();
g2.setColor(Color.red);
g2.drawLine(0,0,100,100);
g2.drawImage(projectile, 0, 0, null);
```

projectile enthält mein normales Image welches ich zeichnen will, es ist 100% nicht *null*.

In meiner Draw Methode male ich das Projektil dann mit:


```
if (rotProjectile!=null)
	g.drawImage(rotProjectile, arrow.x, arrow.y, null);
```

Man sieht aber nur einen schwarzen Kasten an der Stelle, wo eigentlich das Projektil sein soll. Das Komische ist, die Linie, welche ich testhalber oben in den Code eingefügt habe, die sieht man. Aber das Bild nicht. Jemand eine Ahnung woran das liegen könnte? Meine Bilder sind übrigens .GIF wenn das von Bedeutung ist.


----------



## Bert Brenner (18. Sep 2006)

Vielleicht ist das Bild noch gar nicht fertig geladen? Dafür ist der ImageObserver da.


----------



## LoN_Nemesis (18. Sep 2006)

Das halte ich für relativ unwahrscheinlich, da das Bild erstens nur 124 Bytes gross ist und es zweitens funktioniert wenn man projectile direkt zeichnet. Nur wenn ich aus projectile ein BufferedImage mache (rotProjectile) und dieses dann zeichnen will, geht es nichtmehr 

Edit: hab es jetzt trotzdem mal ausprobiert und als ImageObserver mein Applet eingetragen, ändert nix


----------



## Vorbote der Apokalypse (19. Sep 2006)

-> g.drawImage(rotProjectile, arrow.x, arrow.y, null);
vielleicht solltest du statt "null" "this" schreiben


----------



## LoN_Nemesis (20. Sep 2006)

Habs jetzt endlich (fast) perfekt hinbekommen, hier meine Klasse für alle Leute die ein ähnliches Problem haben:



```
package destiny;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.geom.AffineTransform;

public class RotateImage{

 
  public static BufferedImage rotateImage(BufferedImage inputImage, double angle) {

    //Eckpunkte des Ursprungsbildes
    Point a = new Point(0,0);
    Point b = new Point(inputImage.getWidth(),0);
    Point c = new Point(0,inputImage.getHeight());
    Point d = new Point(inputImage.getWidth(),inputImage.getHeight());
    
    //Eckpunkte nach Rotation
    Point newA = drehe(a, angle);
    Point newB = drehe(b, angle);
    Point newC = drehe(c, angle);
    Point newD = drehe(d, angle);
    
    //berechne minimal umschliessendes Rechteck
    int X = Math.max(Math.max(newA.x, newB.x), Math.max(newC.x, newD.x)) - Math.min(Math.min(newA.x, newB.x), Math.min(newC.x, newD.x)); 
    int Y = Math.max(Math.max(newA.y, newB.y), Math.max(newC.y, newD.y)) - Math.min(Math.min(newA.y, newB.y), Math.min(newC.y, newD.y));
    
    //Zwischenspeicherbild
    BufferedImage tempRI = new BufferedImage(2*X, 2*Y, BufferedImage.TYPE_INT_ARGB);
    //Neues Bild in der benötigten Größe
    BufferedImage RI = new BufferedImage(X, Y, BufferedImage.TYPE_INT_ARGB);
    
    AffineTransform af = AffineTransform.getRotateInstance(angle, X/2, Y/2);
        
    Graphics2D g = tempRI.createGraphics();
    //Male das Ursprungsbild in die Mitte des Zwischenspeicherbildes
    g.drawImage(inputImage, X-inputImage.getWidth()/2, Y-inputImage.getHeight()/2, null);
    Graphics2D g2 = RI.createGraphics();
    //Male das Zwischenspeicherbild auf das Endbild und drehe es dann
    g2.setTransform(af);
    g2.drawImage(tempRI, -X/2, -Y/2, null);
    g.dispose();
    g2.dispose();
    return RI;
  }
  
  public static BufferedImage rotateImage(BufferedImage inputImage, int deltaX, int deltaY) {
	  double rotArc = Math.asin(Math.abs(deltaY)/Math.sqrt(deltaX*deltaX+deltaY*deltaY));
		if (deltaY<0 && deltaX>0)
			rotArc = Math.PI/2 - rotArc;
		else if (deltaY>0 && deltaX>0)
			rotArc += Math.PI/2;
		else if (deltaY>0 && deltaX<0)
			rotArc = 1.5*Math.PI-rotArc;
		else if (deltaY<0 && deltaX<0)
			rotArc += 1.5*Math.PI;
		else if (deltaX==0 && deltaY<0)
			rotArc = 0;
		else if (deltaX==0 && deltaY>0)
			rotArc = Math.PI;
		else if (deltaY==0 && deltaX>0)
			rotArc = Math.PI/2; 
		else if (deltaY==0 && deltaX<0)
			rotArc = 1.5*Math.PI;  
		
		return rotateImage(inputImage, rotArc);
  
  }
  
  private static Point drehe(Point p, double angle) {
	  Point pnew = new Point(0,0);
	  pnew.x = (int)(p.x*Math.cos(angle)-p.y*Math.sin(angle));
	  pnew.y = (int)(p.x*Math.sin(angle)+p.y*Math.cos(angle));
	  return pnew;
  }
}
```

Die Methode ist überladen, man kann sie einmal normal mit Angabe des Winkels aufrufen oder indem man jeweils den Abstand auf der X- und Y-Achse angibt, dann wird der Winkel berechnet. Das ist wahrscheinlich nur sinnvoll in meinem Anwendungsfall, also wenn ich ein Projektil in die richtige Richtung rotieren will, vielleicht sollte man die Methode deshalb besser umbenennen, aber naja, kann ja jeder selbst machen 
Es gibt manchmal den Fall, dass 1 oder 2 Pixel abgeschnitten werden, ich weiss ehrlich gesagt nicht warum. Ich habe meinen Projektilen jetzt einfach 2 Pixel transparenten Rand gegeben, damit funktioniert es wunderbar.


----------



## LoN_Nemesis (9. Okt 2006)

Ich habe noch eine bezüglich der Projektile in meinem Spiel die mich stört. Und zwar:
Geht mal auf http://mitglied.tripod.de/destinyjava/new.

Nehmen wir jetzt zum Beispiel den Kampf gegen die Banditen (direkt unterhalb vom Startpunkt). Man kann das Problem sehen wenn diese ihre Pfeile schiessen oder man selbst den Feuerball Zauber benutzt. Zaubert man den Feuerball genau in y Richtung, also nach oben so ist alles normal. Wenn man nun aber minimal abweicht, also sagen wir 7 Felder nach oben schiesst und 1 nach rechts oder links, so sind die Projektile sehr schnell. Wenn man hingegen nur in x Richtung schiesst, also gerade nach links oder rechts, so sind die Projektile sehr langsam.

Das Fliegen der Projektile funktioniert so: Ich habe ja die Koordinaten des Angreifers und des Ziels. Aus diesen 2 Punkten errechne ich eine Gerade der Form f(x) = m*x+b mittels der Formel m = deltaY / deltaX und 				b = Angreifer.Y - m*Angreifer.X
Dann lasse ich die Projektile einfach diese Gerade langfliegen, indem ich den X Wert des Projektils in jeder Iteration um einen festen Wert erhöhe und mir dazu dann den Y-Wert ausrechnen lasse. Wenn deltaX = 0 ist, dann gibt es einen Sonderfall, da man durch 0 nicht teilen kann. Dann erhöhe ich statt dem X-Wert einfach den Y-Wert pro Iteration.

Warum sind die Geschwindigkeiten so unterschiedlich? Hängt das vielleicht damit zusammen, dass die Auflösung 640x480 beträgt, und es deshalb mehr Pixel in horizontaler Richtung gibt und deshalb kommt es einem nur so vor, als wären die Projektile langsamer?


----------



## Soulfly (9. Okt 2006)

Du hast es ja selbst beschrieben, dass du je nach Situation ein anders Verfahren anwendest. Naja dann kommt es halt dazu. brauchst ja einfach mal nachrechnen.

Ich würde mir den Vektor zwischen den Punkten ermitteln.
Dann einen festen Betrag festlegen, den das Projektil pro Schritt, auf den Vektoren zurücklegen soll.

Dieser feste Betrag ist dazu da, um eine konstante geschwindigkeit zu ermöglichen. Hierzu muss du halt einige Rechnungen im voraus machen, deren Ergebniss, dann einmal die x- und y- werte (float) pro Schritt sind und z.B., wie viele Schritte, das Projektil bis zum Ziel benötigt.

[EDIT]

Die Auflösung hat nun so garnichts damit zu tun, weil ich mal davon ausgehe, dass du das Bild ja in keiner Weise streckst, sondern eine  1:1 abbildung machst.


----------



## VdA (11. Okt 2006)

noch ne frage:
was sind eigentlich deltaX und deltaY
wie berechne ich die wenn ich ein Koordinatensystem mit 2 punkten(Anfang und Ende) habe


----------



## LoN_Nemesis (11. Okt 2006)

deltaX = ziel.x - start.x
deltay = ziel.y - stark.y


----------



## VdA (11. Okt 2006)

Danke


----------



## LoN_Nemesis (12. Okt 2006)

Nun geht es immer gleichschnell, die Idee mit dem Einheitsvektor im Float Format hat geklappt. Danke


----------

