# Rundenbasiertes Kampfsystem (RPG)



## Gregorius (16. Mai 2008)

Hallo,
Ich habe mir vorgenommen ein ganz simples jedoch graphisches rundenbasiertes Kampfsystem zu schreiben. Damit ihr euch vorstellen könnt was ich damit meine, hier einige links:
Splendid Genesis (http://www.dark-matter-soft.de/index.php?site=splendidg/beschreibung&nz=1) ist eigentlich genau das was ich machen will. Natürlich ist es schon sehr ausgereift. Von der Komplexität soll es ungefähr so sein wie das Video: http://youtube.com/watch?v=TNOMQDinBg8&feature=related
Den prinzipiellen Aufbau eines rundenbasierten RPG Kampfsystemes finde ich relativ einfach:

```
BATTLE:

    * initialize battle & combatant vars
    * start timer
    * MAIN LOOP:
          o blit bg & under-elements
          o blit combatants & main elements
          o draw displays & over-elements
          o FlipScreen()
          o HANDLE EVENTS:

                process turn queue (do action of who's up next) 
                process new events (if enemy's ready, have "AI" determine next action & add them to the queue; if player's ready, bring up menu & process menu action) 
                process constant events (update the readygauge, update the counter for any effects like glowing or death sentence, update the timer, etc) 
                END HANDLE EVENTS 

          o HANDLE COMBATANTS:

                process actions 
                process status changes 
                END HANDLE COMBATANTS 

          o check for battle end conditions
          o if conditions are not met, continue
          o else END MAIN LOOP 
    * stop timer
    * resolve battle's end (give out spoils if won, else handle battle lost/game overs)
    * reset battle info (heal enemies, remove temporary statuses, reset timers, etc)
    * END BATTLE
```
Quelle: http://www.spheredev.org/wiki/Battle_System_Tutorial_by_NeoLogiX
Jedoch habe ich ein grundsätzliches Problem (ich benutze das XNA Gamestudio aber das Problem würde auch bei JAva 2d Engines auftreten). Wenn ich dieses Kampfsystem als Conosolenapp programmieren (http://www.freewebs.com/campelmxna/tutorials.htm) würde, wäre alles okay, denn da gibt es keine Animationen der Charaktere und Wartezeiten zwischen den Angriffen.
Wenn es aber graphisch wird gibt es ja immer dieses Modell, dass es eine Art Update- und eine Drawfunktion gibt die in einer Schleife ausgeführt werden. 

```
Init() //graphicken laden, music
Loop()
{
   Update() //Spielelogik
   Draw() //Animationen abspielen...
}
```
Ich will zum Beispiel programmieren:


```
KampfrundeBeginn(Angreifer, Monster)
    Angreifer.Angriffsanimation
    Wenn die Animation zu ende ist
    Monster.Angriffsanimation
    Wenn die Animation zu ende ist
    Monster.leben -= Angreifer.stärke
KampfrundeEnde
```

Wenn ich dieses aber in die Update(oder Draw)Methode schreibe, dann stoppt die Updatemethode ja nicht solange bis die Angriffsanimation zu ende ist und setzt dann mit der MonsterAngriffsanimation fort. Es wird also z.B. die AngreiferAngriffsanimation ausgeführt und danach sofort die Monster.Angriffsanimation und danach wird sofort das leben abgezogen und das wird millionenmal wiederholt solange das Spiel läuft.


```
Loop(Solange Monster und Angreifer noch leben)
KampfrundeBeginn(Angreifer, Monster)
    Angreifer.Angriffsanimation
    Loop(Solange 2 Sekunden nicht vorbei)
    {
    }
    Monster.Angriffsanimation
    Loop(Solange 2 Sekunden nicht vorbei)
    {
    }
    Monster.leben -= Angreifer.stärke
KampfrundeEnde
LoopEnde
```

Wenn ich nun leere Schleifen einbaue, habe ich zwar diese Pausenzeiten, nur kann nichts geupdatet oder gezeichnet werden, sodass ich gar keine Animationen sehe.


```
Loop(Solange Monster und Angreifer noch leben)
KampfrundeBeginn(Angreifer, Monster)
    
    Loop(Solange 2 Sekunden nicht vorbei)
    {
     Angreifer.Angriffsanimation
     Render
    }
    
    Loop(Solange 2 Sekunden nicht vorbei)
    {
     Monster.Angriffsanimation
     Render
    }
    Monster.leben -= Angreifer.stärke
KampfrundeEnde
LoopEnde
```

So könnte man das bei Verge(http://www.verge-rpg.com/docs/view.php?libid=3&section=138) machen. Aber das ist irgendwie auch nicht die Lösung.
Also ich hoffe ihr habt mein grundsätzliches Problem verstanden.

Meine Frage also: Wenn ich also diese Update und Drawschleifen habe, wie muss ich an die Sache rangehen um prinzipiell ein Kampfsystem wie dieses hinzubekommen: http://youtube.com/watch?v=TNOMQDinBg8&feature=related ?


----------



## Illuvatar (16. Mai 2008)

Mal zum Beispiel ein Vorschlag, ich weiß jetzt nicht, wie praktikabel das in deinem Fall wäre - da gäbe es aber noch einige ähnlichen Möglichkeiten:

 - Eine Klasse Animation. Die Klasse besitzt Information darüber, wie weit die jeweilige Animation bereits fortgeschritten ist (und kann sich evtl. selbst zeichnen). Außerdem müsste es eine Art Listenersystem geben (so wie in Java AWT die XXXListener).

 - Eine Klasse, die als Listener fungiert, und den startenden Thread wieder aufweckt.

 - Das was du als "Angreifer.Angriffsanimation" könnte man dann so in der Art aufschlüsseln:


```
// Vorarbeit
Animation animation = ...;
Listener listener = new Listener (this);
animation.addAnimationListener(listener);

// Und in der Schleife
animation.start();
synchronized(this){
  this.wait();
}

// im Listener muss dann stehen
public void animationFinished (AnimationEvent e)
{
  synchronized (myObject){
    myObject.notify();
  }
}
```


----------



## Evil-Devil (16. Mai 2008)

Das Problem der Animationen würde ich über States lösen. Je nachdem in welchem State der Actor ist wird die jeweilige Animation/Aktion ausgeführt. So könnte deine Kampf-Spielfigur zb. über die States IDLE, ESCAPE, ATTACK, SPECIAL, DEFEND verfügen.

Selbiges für den Gegner. Das ist ja beliebig erweiterbar


----------



## Gregorius (17. Mai 2008)

Danke erstmal für die Denkanstöße.
@Illuvator
Ich habe versucht deinen Vorschlag zu übernehmen. Nach einigen Versuchen habe ich dann eine etwas abgewandelte Version gebastelt die auch evil-devil's vorschlag ein wenig miteinbezieht.

Mein Aufbau sieht folgendermaßen aus:
Die Klasse GameLogic erzeugt zwei Kämpfer.  Die Klasse hat zwei Methoden: updateLogic() und Draw().
updateLogic() wird sequentiell abgearbeitet während Draw() eine Schleife beinhaltet, die zum Zeichnen gedacht ist und "parallel" (da in einem eigenen Thread) zu updateLogic() läuft. 
Die Dauer einer Animation wird im Prinzip durch die entsprechende Funktion in der Klasse Fighter festgelegt. Die Attackframes sollen von 0-4 gehen und Idle Frames immer abwechselnd von 0-1;
Hier die beiden Klassen und das Beispiel einer Konsolenausgabe (irgendwie ist die Ausgabe nicht ganz korrekt. Meine Vermutung: es liegt an der Asynchronität der Threads).

GameLogic.java

```
package Threads;


public class GameLogic{

	public Fighter hero = new Fighter("hero");
	public Fighter monster = new Fighter("monster");
	
	public static void main(String[] args) {
		new GameLogic();
	}
	
	public GameLogic() {
		//new Thread( this ).start();
		updateLogic();
	}
	
	
	public void updateLogic() {
		Draw();
		
		System.out.println("Angreifer.Angriffsanimation");
		hero.setAttackAnimation();
		hero.setIdleAnimation();
		
		System.out.println("Monster.Angriffsanimation");
		monster.setAttackAnimation();
		monster.setIdleAnimation();
		
		System.out.println("Kampf beendet!");
		
		System.exit(0);
	}
	
	public void Draw() {
		new Thread()
		{
			public void run() {
				while(true) {
					try {
						Thread.sleep(400);
					} catch (InterruptedException e) {}

					hero.Draw();
					monster.Draw();
				}
			}
		}.start();
	}

}
```

Fighter.java

```
package Threads;

public class Fighter {
	private String state;
	private String name;
	private int frame;
	
	public Fighter(String name)
	{
		this.name = name;
		setIdleAnimation();
	}
	
	public void setAttackAnimation()
	{
		state = "ATTACK";
		int numberOfFrames = 5;
		for( int i = 0; i < numberOfFrames; i++ ) {
			frame = i;
			try {
				Thread.sleep(400);
			} catch (InterruptedException e) {}
		}
		frame = 0;
	}
	public void setIdleAnimation()
	{
		state = "IDLE";
		new Thread()
		{
			public void run() {
				while(state == "IDLE") {
					int numberOfFrames = 2;
					for( int i = 0; i < numberOfFrames && state == "IDLE"; i++ ) {
						frame = i;
						try {
							Thread.sleep(400);
						} catch (InterruptedException e) {}
					}
					frame = 0;
				}
			}
		}.start();
	}
	
	public void Draw() {
		System.out.println(name+": "+state+" Frame: "+frame);
	}
}
```

Konsolenausgabe

```
Angreifer.Angriffsanimation
hero: ATTACK Frame: 1
monster: IDLE Frame: 1
hero: ATTACK Frame: 1
monster: IDLE Frame: 1
hero: ATTACK Frame: 2
monster: IDLE Frame: 0
hero: ATTACK Frame: 3
monster: IDLE Frame: 1
hero: ATTACK Frame: 4
monster: IDLE Frame: 0
Monster.Angriffsanimation
hero: IDLE Frame: 0
monster: ATTACK Frame: 1
hero: IDLE Frame: 1
monster: ATTACK Frame: 2
hero: IDLE Frame: 0
monster: ATTACK Frame: 3
hero: IDLE Frame: 1
monster: ATTACK Frame: 3
hero: IDLE Frame: 0
monster: ATTACK Frame: 4
Kampf beendet!
```

Probleme:
Ich will beispielsweise, dass die IDLE Animation solange läuft bis eine andere Animation gestartet ist. Die ATTACK Animation soll zum Beispiel aber nur einmal laufen. Irgendwie habe ich es schlecht gelöst, wie würdet ihr das machen?
Überhaupt habe ich das dumpfe Gefühl, dass meine Realisierung mit Threads extrem schlecht ist. Wie würdet ihr das machen?


----------



## Illuvatar (17. Mai 2008)

Du hast Glück, mir war langweilig  Ich hab mal was gebastelt. Das ganze allerdings grafisch, nicht auf der Konsole.


```
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;

public class GameExample extends JFrame implements Runnable
{
  private DrawingPanel dp;
  
  public GameExample(String title)
  {
    super(title);
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setSize(400, 300);
    this.setLocationRelativeTo(null);
    this.setContentPane(dp = new DrawingPanel());
    setResizable(false);
    setVisible(true);
    
    new Thread(){ // anonymer Repaint-Thread
      public void run()
      {
        while (!interrupted()){
          GameExample.this.repaint();
          try{
            Thread.sleep (50);
          }catch (InterruptedException ie){
            return;
          }
        }
      }
    }.start();
    
    Thread gameLoop = new Thread(this);
    gameLoop.start();
  }
  
  public void run() // "Gameloop"
  {
    Hero hero = new Hero();
    Monster monster = new Monster();
    dp.setFighters(hero, monster);
    ListenerImpl listener = new ListenerImpl(this);
    hero.addAnimationListener(listener);
    monster.addAnimationListener(listener);
    
    while (true){
      try{
        hero.attack();
        synchronized (this){
          this.wait();
        }
        monster.attack();
        synchronized (this){
          this.wait();
        }
      }catch (InterruptedException e){
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

  public static void main(String[] args) {
    new GameExample("GameExample");
  }
}

class DrawingPanel extends JComponent
{
  private Fighter fighter1, fighter2;
  public void setFighters (Fighter f1, Fighter f2)
  {
    fighter1 = f1;
    fighter2 = f2;
  }
  @Override
  public void paintComponent (Graphics g)
  {
    if (fighter1 == null || fighter2 == null)
      return;
    fighter1.paint(g, new Point (100, 100));
    fighter2.paint(g, new Point (200, 100));
  }
}

abstract class Fighter
{
  private static final long timePerPic = 200L;
  private Image[] idlePics;
  private Image[] fightPics;
  private boolean fighting = false;
  private long aniStartTime = System.currentTimeMillis();
  private java.util.List<AnimationListener> listeners = new java.util.ArrayList<AnimationListener>();
  
  protected Fighter(Image[] idlePics, Image[] fightPics)
  {
    this.idlePics = idlePics;
    this.fightPics = fightPics;
  }
  public void paint (Graphics g, Point where)
  {
    int num = (int)((System.currentTimeMillis() - aniStartTime) / timePerPic);
    // sowas sollte auf keinen fall in die paint-methode, aber ich wills nicht noch komplizierter machen
    if (fighting && num >= fightPics.length){
      for (AnimationListener al : listeners){
        al.animationFinished();
      }
      fighting = false;
      aniStartTime = System.currentTimeMillis();
      num = 0;
    }
    Image[] array = fighting ? fightPics : idlePics;
    num %= array.length;
    
    g.drawImage(array[num], where.x, where.y, null);
  }
  public void attack()
  {
    fighting = true;
    aniStartTime = System.currentTimeMillis();
  }
  public void addAnimationListener(AnimationListener al)
  {
    listeners.add (al);
  }
}

interface AnimationListener
{
  public void animationFinished();
}
class ListenerImpl implements AnimationListener
{
  private Object wakeUp;
  public ListenerImpl(Object wakeUp)
  {
    this.wakeUp = wakeUp;
  }
  public void animationFinished()
  {
    synchronized (wakeUp){
      wakeUp.notify();
    }
  }
}

// --------------- jetzt kommt nichts wichtiges mehr ;) ----------------------------

class Hero extends Fighter
{
  private static final Image[] heroIdlePics = new Image[5];
  private static final Image[] heroFightPics = new Image[11];
  static{  // ich bau mir hier halt die bilder der animationen zusammen, die würden normal von der platte geladen
    for (int i = 0; i < heroIdlePics.length; i++){
      heroIdlePics[i] = new BufferedImage (100, 100, BufferedImage.TYPE_INT_ARGB);
      Graphics g = heroIdlePics[i].getGraphics();
      g.setColor (Color.BLUE);
      g.fillRect (0, 0, 100, 100);
      g.setColor (Color.WHITE);
      g.drawString ("Hero (Idle)", 5, 20);
      g.drawString (Integer.toString(i), 25, 80);
    }
    for (int i = 0; i < heroFightPics.length; i++){
      heroFightPics[i] = new BufferedImage (100, 100, BufferedImage.TYPE_INT_ARGB);
      Graphics g = heroFightPics[i].getGraphics();
      g.setColor (Color.BLUE);
      g.fillRect (0, 0, 100, 100);
      g.setColor (Color.BLACK);
      g.drawString ("Hero (Fight)", 5, 20);
      g.drawString (Integer.toString(i), 25, 80);
    }
  }
  public Hero()
  {
    super (Hero.heroIdlePics, Hero.heroFightPics);
  }
}

class Monster extends Fighter
{
  private static final Image[] monsterIdlePics = new Image[4];
  private static final Image[] monsterFightPics = new Image[10];
  static{  // ich bau mir hier halt die bilder der animationen zusammen, die würden normal von der platte geladen
    for (int i = 0; i < monsterIdlePics.length; i++){
      monsterIdlePics[i] = new BufferedImage (100, 100, BufferedImage.TYPE_INT_ARGB);
      Graphics g = monsterIdlePics[i].getGraphics();
      g.setColor (Color.RED);
      g.fillRect (0, 0, 100, 100);
      g.setColor (Color.WHITE);
      g.drawString ("Monster (Idle)", 5, 20);
      g.drawString (Integer.toString(i), 25, 80);
    }
    for (int i = 0; i < monsterFightPics.length; i++){
      monsterFightPics[i] = new BufferedImage (100, 100, BufferedImage.TYPE_INT_ARGB);
      Graphics g = monsterFightPics[i].getGraphics();
      g.setColor (Color.RED);
      g.fillRect (0, 0, 100, 100);
      g.setColor (Color.BLACK);
      g.drawString ("Monster (Fight)", 5, 20);
      g.drawString (Integer.toString(i), 25, 80);
    }
  }
  public Monster()
  {
    super (Monster.monsterIdlePics, Monster.monsterFightPics);
  }
}
```

Anzumerken ist: Das ist teilweise noch nicht so ganz durchdacht, aber es wäre ein erster Ansatz. Das Problem hier ist, dass Logik innerhalb des Event-Dispatch-Threads steht, und das ist ganz böse  Falls das synchronized in Zeile 140 aus irgendeinem Grunde blockieren würde, würde der ganze Frame stillstehen. Man müsste also die Animation irgendwo anders noch updaten. Aber das ist jetzt dein Job


----------



## Newgame (22. Mai 2008)

Vielen Dank für deine Mühe (ich bin Gregorius). Trotz vielen Nachdenkens und Informierens über EventListener blicke ich bei deinem Beispiel nicht völlig durch. Da ich noch zu dumm bin, habe ich mir vorgenommen erstmal kleinere Brötchen zu backen um dann etwas später wieder hier zurückzukehren.
Hier mein kleineres Brötchen: http://www.java-forum.org/de/topic69731_snake-programmier-stil.html


----------

