Jantix - oder auch DOS Game Antix in Java

Hallo Leutz,

Ich habe da grad ein wenig Zeit und nun ist mir eingefallen das ich Antix immer Klasse fand und hab mich dran gesetzt das mal umzusetzen.

GUI steht, Xon fährt in der Gegend rum, Bälle fliegen und prallen ab. Soweit so Gut. Habe das aktuell mit Graphicobjekten gelöst und nutze zur Kollisionsabfrage graphic.intersects(). Spielfeld ist dazu ein Polygon. Nun schneidet man bei dem Spiel aus dem Spielfeld Stücke raus, diese Änderungen kommen als neue Punkte in das Polygon, nun ist das ein ewiger Aufwand dauernd zu berechnen an welcher Stelle man die neuen Punkte nun in das Polygon einfügen muss, wie rum usw.

Nun die Frage dazu:
Bin ich voll auf dem Holzweg da auf diesem Wege ran zugehen und es gibt ne viel bessere Möglichkeit das zu lösen?
Irgendwie finde ich das nämlich arg umständlich.

Wer Antix nicht kennt, dem sei gesagt, dass bei diesem Spiel von einem Feld Stücke abgeschnitten werden, indem man mit einem Punkt(mit Pfeiltasten gesteuert) durchs Spielfeld fährt. Im Spielfeld befinden sich Bälle, welche nicht an die Linie kommen dürfen, welche man gerade zieht. Ist ein Stück entfernt, gilt das als neuer Spielfeldrand. Ganz simpel.

Hat jemand eine Idee und kann mir sagen ob ich den Wald vor lauter Bäumen nicht sehe oder ob es sinnvoll wäre weiter mit Graphics zu arbeiten?

Kommt drauf an, wie performant das Ganze ist. Mein Ansatz wäre, das Polygon in ein Bild zu malen und über die Pixelfarbe auszukaspern, ob ein gewisser Prozentsatz umgefärbt ist.
Ob das jetzt immer schnell genug ist, müßte man ausprobieren, das kann ich jetzt aus dem Stand auch nicht abschätzen.:confused:

Hier mal eine schnelles, statisches Beispiel:

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class Test extends JPanel{

  private static final long serialVersionUID = 1L;
  JFrame frame;
  
  BufferedImage buf;
  
  public static void main(String[] args){
    new Test();
  }
  
  public Test(){
    setPreferredSize(new Dimension(300,300));
    
    buf = new BufferedImage(300,300,BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = buf.createGraphics();
    g2.setColor(Color.RED);
    g2.fillRect(0, 0, 300, 300);
    
    Polygon poly = new Polygon();
    poly.addPoint(20,30);
    poly.addPoint(250,60);
    poly.addPoint(270,290);
    poly.addPoint(10,120);
    
    g2.setColor(Color.BLUE);
    g2.fillPolygon(poly);
    
    frame = new JFrame("Test");
    frame.add(this);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
    
    checkPercentage();    
  }

  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    
    g.drawImage(buf, 0, 0, this);
    
  }
  
  private void checkPercentage(){
    
    int red = 0;
    int blue = 0;
    
    for(int x=0;x<buf.getWidth();x++){
      for(int y=0;y<buf.getHeight();y++){
        
        Color pix = new Color(buf.getRGB(x, y));
        
        if(pix.equals(Color.RED)){
          red++;
        }else{
          blue++;
        }
        
        
      }
    }
    
    System.out.println("red = " + red);
    System.out.println("blue = " + blue);
    
  }
  
  
}

Hallo Quaxli,

schöne Idee, werde das mal ausprobieren. Aber auch hier bleibt ja die Huddelei mit dem Polygon und den Punkten. Ist also das Polygon für das Spielfeld wirklich beste Handhabe?

Ist nur ein Fun-Projekt was ich gestern in Ermangelung tatsächlicher Arbeit angefangen habe, also bitte nicht über Unwissen im Spielebereich wundern.g
Stelle es gern zum testspielen zur Verfügung, wenn es sich dann mal spielen lässt.

Liebe Grüße
Jezira

PS: Immer her mit Ideen zu anderen Umsetzungsweisen :o)

Immerhin mußt Du auf die Art nicht 2 Polygone miteinander verwusteln. :wink:
So malst Du immer nur das eben abgeschlossene Polygon ins Bild und bist fertig.

Was meinst Du mit Polygon-Huddelei? Ich würde das wie folgt machen:
Bei jeder Richtungs-Änderung speichert man den aktuellen Punkt in einen Container (Array-List, Vector, etc.).
Damit zeichnet man auch den Weg des Spielers nach. (drawLine von Punkt zu Punkt).
Wenn der Spieler wieder am Rand und ein Stück ausgeschnitten ist, packt man die Punkte der Reihe nach in ein Polygon und malt das auf den Hintergrund (wie im Beispiel).
Ich seh’ da jetzt keine großen Umständlichkeiten… :smiley:

Jep, so mach ich es aktuell. Der Reihenfolge nach - das ist der schön einfach gesagte Teil g
Hab ich mir ja auch so gedacht. Das Polygon zeichnet sich ja selber an den Punkten von links nach rechts. Wenn nun mein Spieler in der rechten, oberen Ecke von rechts nach links was rausscheidet, kann ich die Punkte ja nicht mal eben einfügen, da die Punkte in falscher Reihenfolge sind.
Kein Problem, drehen wir eben um und gut. Nun ist das in der linken, unteren Ecke aber wieder andersrum. Muss also nach der Diagonale berechnen in welcher Reihenfolge die Punkte eingefügt werden müssen. Ausserdem muss ich immernoch rausfinden an welcher Stelle überhaupt.

Ist schriftlich irgendwie alles blöde erklärt. Vielleicht ein Beispiel:
spielfeld = 100x100
polygonPunkte = 0,0 / 0,100 / 100,100 / 100,0

schneide nun zwei Stufen aus dem oberen, rechten Eck mit den Punkten: 0,80 / 10,80 / 10,90 / 20,90 / 20,100
(von links nach rechts geschnitten sind die auch richtigrum)

Meine polygonpunkte müssen also so aussehen: 0,0 /// 0,80 / 10,80 / 10,90 / 20,90 / 20,100 /// 100,100 / 100,0

Schneidet der Spieler nun aber von rechts nach links sehen die Punkte normal eingefügt so aus:
0,0 /// 20,100 / 20,90 / 10,90 / 10,80 / 0,80 /// 100,100 / 100,0
das gäbe ein ziemlich unschönes Polygon, also umdrehen.

Wenn du dir nun das nochmal für die untere, linke ecke ausrechnest, merkt man das es da wieder anders rum ist.

Muss halt noch geprüft werden, an welcher Stelle des Polygons, die neuen Punkte eingefügt werden. Das ist durchaus auch unterschiedlich.

Ist alles kein Problem, lässt sich alles berechnen. Aber ich finde es halt irgendwie… naja umständlich. Ich war der Annahme es gäbe da vielleicht was effektiveres ^^ (was ich einfach nur nicht kenn oder nicht drauf komm ;o))

Ich finde Dein Lösung unnötig komplex.
Warum ist Dein Spielfeld extra nochmal ein Polygon?
Wenn Du das vom Spieler erzeugte Polygon, in ein Bild malst und alles weitere über das Bild auskasperst brauchst Du kein 2. Polygon.
Korrigiere mich, wenn ich falsch liege. :wink:

Ja genau, ich finde das auch irgendwie zu komplex :wink: deswegen meine Frage.

Brauche nur ein Polygon, das muss ich eben aber den Schnitten dann anpassen.
Male Polygon auf Spielfeld, schneide Stücke runter, merke die Punkte in ner Liste und baue dann mein Polygon um. Das eine brauch ich ja zur Kollisonsabfrage und zum malen.

Nur will mir nix anderen einfallen eben besagte abgeschnittenen Stücke in richtiger Reihenfolge in mein Polygon zu bekommen. Von mir auch auch ne Punktliste, bei der man dann Linien zieht, aber auch da ändert sich nix das ich alles richtig einsortieren muss. Sonst malts halt Blödsinn.

Wobei ich schon angedeutet habe, das es gut möglich ist, dass ich am einfachsten Weg vorbei denke -.- wäre nichtmal so untypisch xD

Nix Polygon umbauen…
Wozu brauchst Du noch ein Polygon? Du hast das Bild.

Leider Gottes arbeite ich nicht so wahnsinnig viel mit Bildern.

Wäre dann also:
Auf meinem Bildchen rumfahren und Punkte merken, nächsten Eckpunkt nehmen und alle eingeschlossenen Punkte umfärben.
Zur Kollisionsabfrage die Farbe vergleichen.

Ist das der Gedanke dabei?

Jepp

Denke nicht an rosa Elefanten(Polygon). Verlass dich drauf dir, fällt nix anderes ein xD

Ist in der Tat wesentlich weniger Huddelei :wink:
Werd das mal umsetzen und dann weiter nerven. Danke für die Augenöffner!

Ich erlaube mir mal folgende Anmassung (und zwar ungetestet, aber das werde ich noch ;)):

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;

public class AntixImage extends BufferedImage {
	private double numPixels;

	public AntixImage(int width, int height, Color a, Color b) {
		this(new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB), a, b);
	}

	private AntixImage(BufferedImage img, Color a, Color b) {
		super(img.getColorModel(), patchRaster(img.getRaster(), a.getRGB(), b.getRGB()), img.isAlphaPremultiplied(), null);
		numPixels = img.getWidth() * img.getHeight();
	}

	private static AntixRaster patchRaster(WritableRaster wr, int rgb1, int rgb2) {
		return new AntixRaster(wr, rgb1, rgb2);
	}

	private static SampleModel patchSampleModel(SampleModel source, int color1, int color2) {
		return new AntixSampleModel(source, color1, color2);
	}

	public double getColorAPercent() {
		AntixSampleModel m = (AntixSampleModel) getSampleModel();
		double rc = m.getCount1() / numPixels;
		return rc;
	}

	public double getColorBPercent() {
		AntixSampleModel m = (AntixSampleModel) getSampleModel();
		double rc = m.getCount2() / numPixels;
		return rc;
	}

	private static class AntixRaster extends WritableRaster {
		private AntixRaster(WritableRaster source, int color1, int color2) {
			super(patchSampleModel(source.getSampleModel(), color1, color2), new Point(source.getSampleModelTranslateX(), source.getSampleModelTranslateY()));
		}
	}

	private static class AntixSampleModel extends SampleModel {
		private int color1, color2, count1, count2;
		private SampleModel source;

		private AntixSampleModel(SampleModel source, int color1, int color2) {
			super(source.getDataType(), source.getWidth(), source.getHeight(), source.getNumBands());
			this.source = source;
			this.color1 = color1;
			this.color2 = color2;
		}

		@Override
		public int getNumDataElements() {
			return source.getNumDataElements();
		}

		@Override
		public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
			return source.getDataElements(x, y, obj, data);
		}

		@Override
		public void setDataElements(int x, int y, Object obj, DataBuffer data) {
			source.setDataElements(x, y, obj, data);
			if(obj instanceof int[]) {
				int c = ((int[]) obj)[0];
				if(c == color1) {
					count1++;
				} else if(c == color2) {
					count2++;
				}
			}
		}

		@Override
		public int getSample(int x, int y, int b, DataBuffer data) {
			return source.getSample(x, y, b, data);
		}

		@Override
		public void setSample(int x, int y, int b, int s, DataBuffer data) {
			source.setSample(x, y, b, s, data);
			if(s == color1) {
				count1++;
			} else if(s == color2) {
				count2++;
			}
		}

		@Override
		public SampleModel createCompatibleSampleModel(int w, int h) {
			return new AntixSampleModel(source.createCompatibleSampleModel(w, h), color1, color2);
		}

		@Override
		public SampleModel createSubsetSampleModel(int[] bands) {
			return new AntixSampleModel(source.createSubsetSampleModel(bands), color1, color2);
		}

		@Override
		public DataBuffer createDataBuffer() {
			return source.createDataBuffer();
		}

		@Override
		public int[] getSampleSize() {
			return source.getSampleSize();
		}

		@Override
		public int getSampleSize(int band) {
			return source.getSampleSize(band);
		}

		public int getCount1() {
			return count1;
		}

		public int getCount2() {
			return count2;
		}
	}
}```
Folgendes: Ein Java-Graphics-Objekt "pinselt" statt über setRGB usw. fröhlich über das SampleModel eines Rasters (WritableRaster) in einen BildPuffer. Folglich ist es ratsam, wenn man genau dort die entsprechenden Farben genau dann heraufzählt, sobald sie gesetzt werden. Was hier so umständlich aussieht, ist für ausgeprochen gute Performance leiner nötig, aber glücklicherweise noch recht überschaubar. Wenn ich nicht gerade meine Datatype-Images verwende, verwende ich ähnliche Konstrukte auch für Kollisionsabfragen, dann sind allerdings mehr die "get...()"-Methoden gefragt). An Performance ist das nicht zu über(unter?)bieten, maw: Es gibt nichts schnelleres, ausser meine DT_Images vllt ;).

edit: Okay... Ich hab vergessen, dass das ColorModel auch noch gepached werden muss, damits kompatibel wird...

*** Edit ***

Ich habe noch etwas mehr vergessen, nämlich den Umstand, dass seit Java7 statt nur dem SunWritableRaster nun auch jedes andere eigene "beschleunigt" werden kann, weil "stolen" nun nicht mehr Sache des Rasters sondern Sache des Buffers ist. Kurzum: Man kann sich dieses ganze gepatche der Models und Raster sparen und eigene implementieren...
Hier mal ein Demo... diesmal getestet ;)
```import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class AntixMain extends JPanel {
	private static final long serialVersionUID = 8299363071288831840L;

	private AntixImage img;
	private boolean running;

	private AntixMain(final int width, final int height) {
		img = new AntixImage(width, height, Color.RED, Color.BLUE);
		setPreferredSize(new Dimension(width, height));
		setFont(new Font(Font.MONOSPACED, Font.BOLD, 12));
		final double hWidth = width / 2.0;
		final Graphics2D g2d = img.createGraphics();
		g2d.setColor(getBackground());
		g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
		Thread anim = new Thread("AnixAnim") {
			@Override
			public void run() {
				int x, y, w, h;
				while(running) {
					double percentA = img.getColorAPercent();
					double percentB = img.getColorBPercent();
					if(percentA + percentB > .8) {
						g2d.setColor(getBackground());
						g2d.fillRect(0, 0, width, height);
					}
					x = (int) (Math.random() * width);
					y = (int) (Math.random() * height);
					w = (int) (Math.random() * (hWidth - (x % hWidth)));
					h = (int) (Math.random() * (height - y));
					g2d.setColor((x >= hWidth)? Color.RED : Color.BLUE);
					g2d.fillRect(x, y, w, h);
					repaint();
					try {
						Thread.sleep(40);
					} catch(InterruptedException e) {
						running = false;
					}
				}
			}
		};
		running = true;
		anim.start();
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		g.drawImage(img, 0, 0, getWidth(), getHeight(), this);
		g.drawString(String.format("RED : %2.2f%%", img.getColorAPercent() * 100.0), 10, 15);
		g.drawString(String.format("BLUE: %2.2f%%", img.getColorBPercent() * 100.0), 10, 30);
	}

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				JFrame f = new JFrame();
				JPanel p = new AntixMain(800, 600);
				f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				f.add(p);
				f.pack();
				f.setVisible(true);
			}
		});
	}
}

class AntixImage extends BufferedImage {
	private double numPixels;

	public AntixImage(int width, int height, Color a, Color b) {
		this(new AntixColorModel(a.getRGB(), b.getRGB()), width, height);
	}

	private AntixImage(AntixColorModel model, int width, int height) {
		super(model, model.createCompatibleWritableRaster(width, height), false, null);
		numPixels = width * height;
	}

	public double getColorAPercent() {
		AntixSampleModel m = (AntixSampleModel) getSampleModel();
		double rc = m.getCount1() / numPixels;
		return rc;
	}

	public double getColorBPercent() {
		AntixSampleModel m = (AntixSampleModel) getSampleModel();
		double rc = m.getCount2() / numPixels;
		return rc;
	}

	private static class AntixRaster extends WritableRaster {
		private AntixRaster(int width, int height, int color1, int color2) {
			super(new AntixSampleModel(width, height, color1, color2), new Point(0, 0));
		}
	}

	private static class AntixColorModel extends ColorModel {
		private int color1, color2;

		private AntixColorModel(int color1, int color2) {
			super(32);
			this.color1 = color1;
			this.color2 = color2;
		}

		@Override
		public int getRed(int pixel) {
			return (pixel >> 16) & 0xFF;
		}

		@Override
		public int getGreen(int pixel) {
			return (pixel >> 8) & 0xFF;
		}

		@Override
		public int getBlue(int pixel) {
			return pixel & 0xFF;
		}

		@Override
		public int getAlpha(int pixel) {
			return (pixel >> 24) & 0xFF;
		}

		@Override
		public boolean isCompatibleRaster(Raster raster) {
			return (raster instanceof AntixRaster);
		}

		@Override
		public SampleModel createCompatibleSampleModel(int w, int h) {
			return new AntixSampleModel(w, h, color1, color2);
		}

		@Override
		public WritableRaster createCompatibleWritableRaster(int w, int h) {
			return new AntixRaster(w, h, color1, color2);
		}

		@Override
		public Object getDataElements(int rgb, Object pixel) {
			if(pixel == null) {
				pixel = new int[1];
			}
			((int[]) pixel)[0] = rgb;
			return pixel;
		}
	}

	private static class AntixSampleModel extends SampleModel {
		private int color1, color2, count1, count2;

		private AntixSampleModel(int width, int height, int color1, int color2) {
			super(DataBuffer.TYPE_INT, width, height, 1);
			this.color1 = color1;
			this.color2 = color2;
		}

		@Override
		public int getNumDataElements() {
			return 1;
		}

		@Override
		public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
			if(obj == null) {
				obj = new int[1];
			}
			((int[]) obj)[0] = getSample(x, y, 0, data);
			return obj;
		}

		@Override
		public void setDataElements(int x, int y, Object obj, DataBuffer data) {
			int c = ((int[]) obj)[0];
			setSample(x, y, 0, c, data);
		}

		@Override
		public int getSample(int x, int y, int b, DataBuffer data) {
			return data.getElem(0, y * width + x);
		}

		@Override
		public void setSample(int x, int y, int b, int s, DataBuffer data) {
			int c = getSample(x, y, b, data);
			data.setElem(y * width + x, s);
			if(c == color1) {
				count1--;
			} else if(c == color2) {
				count2--;
			}
			if(s == color1) {
				count1++;
			} else if(s == color2) {
				count2++;
			}
		}

		@Override
		public SampleModel createCompatibleSampleModel(int w, int h) {
			return new AntixSampleModel(w, h, color1, color2);
		}

		@Override
		public SampleModel createSubsetSampleModel(int[] bands) {
			throw new UnsupportedOperationException("model doesn't have any subsets");
		}

		@Override
		public DataBuffer createDataBuffer() {
			return new DataBufferInt(width * height);
		}

		@Override
		public int[] getSampleSize() {
			return new int[] {32};
		}

		@Override
		public int getSampleSize(int band) {
			if(band != 0) {
				return 0;
			}
			return 32;
		}

		public int getCount1() {
			return count1;
		}

		public int getCount2() {
			return count2;
		}
	}
}```

Guten Morgen,

wow, klasse. Soviel Einsatz :slight_smile: danke.

Konnt’s noch nicht genau ansehen, werde ich aber auf jeden Fall bald tun. Aus dem anfänglich angedachten „schnell mal aus Langeweile“ ist inzwischen ein spaßiges Projektchen geworden. Habe leider aktuell nicht mehr so viel Zeit, bleibe aber dran :slight_smile:
Muss doch endlichmal ein erstes Spiel(abgesehen vom Game of Life) fertig bekommen.