AnpeilungsFormeln in der Praxis

Developer_X

Top Contributor
Hi, ich hab durch eure Hilfe mal ein kleines Game gemacht, nur nen Prototyp mit dem schießen.
Anbei ist es, einfach in die IDE einkopieren.

Probleme die ich leider immer noch habe:
  • Die Kugeln fliegen nicht perfekt zum roten Punkt im Cursor, obwohl sie da hin sollten.
  • Wenn man sich mit der Maus nicht mehr bewegt, kann man Kugeln so viel man will hintereinander abfeuern, (also aus dem Array benutzen), aber leider kann man wenn man die Maus bewegt, um den Winkel zu verändern,leider nicht schießen.

Das sind wirklich Probleme, bei denen ich mal wieder nicht verstehe, warum die auftreten, mit der Formel die ich bekommen habe, müsste das 100% funktionieren.
Aber man sieht deutlich, wie die Kugeln neben den Roten Punkt fliegen, passt auf die Translationen im Code auf, ich hab das extra so gemacht, dass der rote punkt genau da ist, wo die obere rechte spitze des Cursors sein sollte.

Hoffe ihr könnt mir helfen, DX
 

Quaxli

Top Contributor
Mal so auf die Schnelle geguckt: Es liegt irgendwie an Deiner Winkelfunktion. Wenn man senkrecht oder waagrecht schießt, paßt es ja.
 

hdi

Top Contributor
DevX, viel schlimmer als irgendeine falsche Winkelberechnung ist die Tatsache, dass du deinen Thread nicht mit dem EDT synchronisierst. Es kann bei jedem einzelnen Mausklick zu unvorhersehbaren Ereignissen kommen. Du musst auf die Objekte synchronisieren, die in der mouseClicked/paint sowie der run() vorkommen. Denn run = dein Thread, und mouseClicked/paint = EDT. Also 2 Threads, die z.T. die gleichen Resourcen (Objekte) verwenden. Stichwort Race Condition. Kuck dir auch die weiterführenden Links an (Dirty Read, Lost Update, das kommt in deinem Code beides vor soweit ich gesehen hab).

Das ist wichtig, beschäftige dich damit bevor du irgendwelche Winkelberechnungen anpasst. Wenn du (nach dem Lesen des Artikels) nicht verstehst wovon ich rede, dann frag nach.
 
Zuletzt bearbeitet:
S

Spacerat

Gast
Ich kann mich hdi nur anschliessen. Die Winkelfunktion ist jedenfalls soweit korrekt (...und das nicht nur, weil sie von mirstammt :D).
 

Developer_X

Top Contributor
Ah, ich verstehe, das problem ist, das mehrere Methoden gleichzeitig, da es ja einen Thread in meinem Programm gibt, auf die verschiedenen Variablen zu greifen.

Was sich ändern muss ist folgendes:
Eine Methode muss so lange den Zugriff auf die Variablen für andere Methoden sperren, bis sie selbst aller erledigt hat, doch wie kann ich das mit Java bzw. in meinem Code bewerkstelligen?
 
S

Spacerat

Gast
Soweit so gut... Also der Thread läuft jetzt und ruft als einziger [c]repaint()[/c] auf (ca. alle 10 ms). Womit ich 'nu auch nicht klar komme, ist, dass [c]mouseClicked()[/c] mehr oder weniger sporadich aufgerufen wird, also immer dann, wenn dem Rechner danach ist. Vorzugsweise dann, wenn die Maus eine Zeit lang nicht bewegt wurde.
Java:
package math;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
 
import javax.imageio.ImageIO;
import javax.swing.*;

 
@SuppressWarnings("serial")
public class Game extends JFrame implements MouseMotionListener, Runnable,MouseListener
{
	public static final double PI_2 = Math.PI / 2.0;
//  Attributes
    AlphaComposite ap;
    GradientPaint gp;
    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    final Thread thread;
    
    int width = getSize().width;
    int height = getSize().height;
    
    int mouseX = 1;
    int mouseY = 1;
    
    Image pistole_01,pistole_02;
    
    int pointX = 0;
    int pointY = 0;
    int radius = -1;
    
//  Constructor
    public Game()
    {
        setSize(d);
        setPreferredSize(d);
        setUndecorated(true);
        setLayout(new BorderLayout());
 
        JPanel p = new JPanel()
        {
            @Override
            public void paintComponent(Graphics g)
            {
                drawGame(g);
            }
        };
        p.addMouseMotionListener(this);
        p.addMouseListener(this);
        add(p,"Center");
        setVisible(true);
        
        width = getSize().width;
        height = getSize().height;
        
        try
        {
        	pistole_01 = ImageIO.read(new File("sources/Pistole_01.gif"));
        	pistole_02 = ImageIO.read(new File("sources/Pistole_02.gif"));
        }
        catch(Exception e)
        {
        	// empty
        }
        
        thread = new Thread(this);
        thread.start();
    }
    
//  Methods
	public void drawGame(Graphics g)
	{
		g.setColor(Color.green);
		g.fillRect(0,0,width,height);

        Vector2D killa = new Vector2D(width/2,height/2);
        
        double x = (mouseX - killa.getX()) / width;
        double y = (mouseY - killa.getY()) / height;
        double winkel = (Math.PI * ((y < 0)? 1 : 2) - Math.atan(x / y)); 

        pointX=(int) (radius*Math.cos(winkel+PI_2)); // Math.toRadians(90) ergibt PI/2... warum wohl?
        pointY=(int) (radius*Math.sin(winkel+PI_2)); // Erinnerst du dich an 2PI = 360°? Besser Konstanten verwenden...
        
        pointX += killa.getX();
        pointY += killa.getY();
        
        g.translate((int)killa.getX(),(int)killa.getY());
           ((Graphics2D) g).rotate(winkel);
        g.translate(-(int)killa.getX(),-(int)killa.getY());

        if(winkel<3&&winkel>0||winkel<8&&winkel>6)
        {
        	g.drawImage(pistole_01,(int)killa.getX()-10,(int)killa.getY(),20,100,this); 
        }
        else
        {
        	g.drawImage(pistole_02,(int)killa.getX()-10,(int)killa.getY(),20,100,this); 
        }
        g.translate((int)killa.getX(),(int)killa.getY());
            ((Graphics2D) g).rotate(-winkel);
        g.translate(-(int)killa.getX(),-(int)killa.getY());
        
        g.setColor(Color.pink);
        g.fillOval(pointX,pointY,10,10);
	}
    
	@Override
	public void run()
	{
		while(true) {
    		if(radius >= 0) {
    			radius += 3;
    		}
    		if(radius > 500) {
    			radius = -1;
    		}
			repaint();
    		synchronized(thread) {
    			try {
    				thread.wait(10);
    			} catch(InterruptedException e) {
    				// empty
    			}
    		}
		}
	}
    public void mouseMoved(MouseEvent arg0)
    {
        mouseX = arg0.getX();
        mouseY = arg0.getY();
    }
    
    public void mouseDragged(MouseEvent arg0) 
    {
    	// empty
    }
 
    public static void main(String[]args)
    {
        new Game().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

	public void mouseClicked(MouseEvent e) 
	{
		if(e.getButton()==2 && radius < 0)
		{
			radius = 0;
		}
	}
	public void mouseEntered(MouseEvent e) 
	{
    	// empty
	}
	public void mouseExited(MouseEvent e) 
	{
    	// empty
	}
	public void mousePressed(MouseEvent e) 
	{
    	// empty
	}
	public void mouseReleased(MouseEvent e)
	{
    	// empty
	}
}
[c]shot[/c] musste sterben... ging mit auf den Wecker... :D kann man aber leicht wieder einbauen und wenn ich ehrlich sein soll hab' ich die variable nur enfernt, um Sicher zu stellen, das sie sich nicht mit der anderen Bedingung überschneidet.
@Edit: [c]radius[/c] ist die einzige Variable, die in dem Thread verändert wird. Da es sich dabei um eine primitive handelt ist es nicht notwendig (genau genommen ohne zusätzliches LockObjekt sogar unmöglich) sie zu synchonisieren.
 
Zuletzt bearbeitet von einem Moderator:

OliverKroll

Aktives Mitglied
Die Anpeilfunktion könntest du ändern zu:
Java:
public void math()
	{
		double x = (mouseX - KillaPoint.getX());// / (double) width;
        double y = (mouseY - KillaPoint.getY());// / (double) height;
        if(x==0)
        {
        	if(y>=0)
        	{
        		winkel=Math.PI/2;
        	}
        	else
        	{
        		winkel=-Math.PI/2;
        	}
        }
        else
        {
        	winkel = /*(Math.PI * ((y < 0)? 1 : 2)*/ /*-*/(+ Math.atan(y / x));
            if(x<0)
            {
            	winkel=winkel+Math.PI;
            }
        }
dann zielt sie immer richtig.
 

hdi

Top Contributor
Äh... Leute, das ist doch noch immer nicht synchronisiert:

Java:
/* 1). eigener Thread: */
if(radius >= 0) {
      radius += 3; // sagen wir mal das ist jetzt 501
}

Java:
/* 2). EDT: */
pointX=(int) (radius*Math.cos(winkel+PI_2));

Java:
/* 3). eigener Thread: */
if(radius > 500) {
      radius = -1; // radius = -1
}

Java:
/* 2). EDT: */
pointY=(int) (radius*Math.sin(winkel+PI_2));

Der EDT nutzt nun für x und y verschiedene Radiusse (Radia, Radii, Radieschen??). Das nennt man "Non-repeatable Read", d.h. ich lese eine Variable 2x hintereinander, und es ist nicht beide male der selbe Wert (Was ich erwarte, wenn ich als Thread den Wert nicht ändere).
Warum in Spacerat's Code auf beim sleep auf "thread" synchronisiert wird verstehe ich auch nicht. Was soll das bewirken?

Sychronisiert werden muss immer auf jede Variable, die von 2+ Threads genutzt wird, wobei mindestens einer Schreibzugriffe darauf macht.

Da es sich dabei um eine primitive handelt ist es nicht notwendig (genau genommen ohne zusätzliches LockObjekt sogar unmöglich) sie zu synchonisieren.
Was in den Klammern steht ist korrekt, aber dass es nicht notwendig ist, stimmt deshalb leider noch nicht. Der Zugriff auf radius muss hier synchronisiert werden. Könnte man hier durch einen Wrapper lösen.
 
Zuletzt bearbeitet:
S

Spacerat

Gast
... verschiedene Radiusse (Radia, Radii, Radieschen??).
Wie wärs mit Radien?
Aber ok, has ja recht. Ich kann mir halt nicht vorstellen, das es hier was ausmacht, weil es 1. (mehr oder weniger gut) funktioniert (die Kugeln bewegt sich normal, wenn sie sich mal bewegt) und 2. mit dem radius nur gerechnet wird, was in der Regel recht fix geht und die Gefahr einer RC sehr gering ist.
Als ich deinen Beitrag gelesen habe, dachte ich mir, dass das was du da beschreibst nicht in [c]drawGame()[/c] sondern in [c]mouseClicked()[/c] zum tragen kommt. Ich habe [c]radius[/c] deswegen in einer Klasse Shot gekapselt und darauf synchronisiert, jedoch ohne Erfolg. Klicks werden nach wie vor nur sporadisch erkannt.
Warum ich da auf [c]thread[/c] synchronisiere? Statt dem, was im [c]synchronized[/c] Block steht schreibt man normalerweise [c]Thread.sleep()[/c]. [c]Thread.sleep()[c] ist aber nicht gerade OO, weil statisch. Für einen [c]wait()[/c] Aufruf benötigt man den Monitor des Objekts. Ob das performanter ist entzieht sich meiner Kenntnis. Hab' hier noch mal das Ergebnis meiner Experimente:
Java:
package math;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
 
import javax.imageio.ImageIO;
import javax.swing.*;

 
@SuppressWarnings("serial")
public class Game extends JFrame implements MouseMotionListener, Runnable,MouseListener
{
	public static final double PI_2 = Math.PI / 2.0;
//  Attributes
    AlphaComposite ap;
    GradientPaint gp;
    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    final Thread thread;
    List<Shot> shots = new ArrayList<Shot>();
    double angle;
    
    int width = getSize().width;
    int height = getSize().height;
    
    int mouseX = 1;
    int mouseY = 1;
    
    Image pistole_01,pistole_02;
    
//  Constructor
    public Game()
    {
        setSize(d);
        setPreferredSize(d);
        setUndecorated(true);
        setLayout(new BorderLayout());
 
        JPanel p = new JPanel()
        {
            @Override
            public void paintComponent(Graphics g)
            {
                drawGame(g);
            }
        };
        p.addMouseMotionListener(this);
        p.addMouseListener(this);
        add(p,"Center");
        setVisible(true);
        
        width = getSize().width;
        height = getSize().height;
        
        try
        {
        	pistole_01 = ImageIO.read(new File("sources/Pistole_01.gif"));
        	pistole_02 = ImageIO.read(new File("sources/Pistole_02.gif"));
        }
        catch(Exception e)
        {
        	// empty
        }
        
        thread = new Thread(this);
        thread.start();
    }
    
//  Methods
	public void drawGame(Graphics g)
	{
		g.setColor(Color.green);
		g.fillRect(0,0,width,height);

        Vector2D killa = new Vector2D(width/2,height/2);
        
        double x = (mouseX - killa.getX()) / width;
        double y = (mouseY - killa.getY()) / height;
        angle = (Math.PI * ((y < 0)? 1 : 2) - Math.atan(x / y));

        g.translate((int)killa.getX(),(int)killa.getY());
           ((Graphics2D) g).rotate(angle);
        g.translate(-(int)killa.getX(),-(int)killa.getY());

       	g.drawImage(pistole_02,(int)killa.getX()-10,(int)killa.getY(),20,100,this); 
        g.translate((int)killa.getX(),(int)killa.getY());
            ((Graphics2D) g).rotate(-angle);
        g.translate(-(int)killa.getX(),-(int)killa.getY());
        
        g.setColor(Color.pink);
		List<Shot> tmp = null;
		synchronized(shots) {
			tmp = new ArrayList<Shot>(shots);
		}
		for(Shot shot : tmp) {
			shot.calc();
			if(shot.radius > 500) {
				synchronized(shots) {
					shots.remove(shot);
				}
			}
			g.fillOval(shot.x, shot.y, 10, 10);
		}
	}
    
	@Override
	public void run()
	{
		while(!thread.isInterrupted()) {
			repaint();
    		synchronized(thread) {
    			try {
    				thread.wait(20);
    			} catch(InterruptedException e) {
    				Thread.interrupt();
    			}
    		}
		}
	}
    public void mouseMoved(MouseEvent arg0)
    {
        mouseX = arg0.getX();
        mouseY = arg0.getY();
    }
    
    public void mouseDragged(MouseEvent arg0) 
    {
        mouseX = arg0.getX();
        mouseY = arg0.getY();
    }
 
    public static void main(String[]args)
    {
        new Game().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

	public void mouseClicked(MouseEvent e) 
	{
		synchronized(shots) {
			if(e.getButton()==2 && shots.size() < 20)
			{
				shots.add(new Shot(angle));
			}
		}
	}
	public void mouseEntered(MouseEvent e) 
	{
    	// empty
	}
	public void mouseExited(MouseEvent e) 
	{
    	// empty
	}
	public void mousePressed(MouseEvent e) 
	{
    	// empty
	}
	public void mouseReleased(MouseEvent e)
	{
    	// empty
	}

	class Shot
	extends Point
	{
		int radius = 0;
		final double winkel;

		Shot(double angle)
		{
			super();
			this.winkel = angle+PI_2;
		}

		void calc() {
	        x=(int) (radius*Math.cos(winkel));
                y=(int) (radius*Math.sin(winkel));
	        
	        x += width / 2;
	        y += height / 2;
	        
	        radius += 3;
		}
	}
}
Langsam erinnerts an Asteroids im Western-Style (Eldoradoids?).
 

hdi

Top Contributor
Ich kann mir halt nicht vorstellen, das es hier was ausmacht
Das wird es wohl in den meisten Fällen auch nicht. Kommt halt drauf an wie oft man klickt, bzw. was für Auswirkungen eine RC haben kann. Aber genau das ist ja das fatale an RC's.

Warum ich da auf thread synchronisiere?
Ah ok, das hab ich so noch nie gesehen.

...ansonsten macht halt die Synchronisierung in deinem letzten Bsp keinen Sinn, da es ja nur der EDT ist der überhaupt irgendwas mit den Objekten tut. Aber ich denke mal dass hast du nur der Übersicht halber aus dem Gameloop rausgenommen.

Naja also.. das ist eben alles nicht so kompliziert. Das wichtigste hab ich schon gesagt:
Sychronisiert werden muss immer auf jede Variable, die von 2+ Threads genutzt wird, wobei mindestens einer Schreibzugriffe darauf macht.
Und damit ist gemeint dass jeder Zugriff auf diese Varialbe in jedem der betroffenen Threads synchronisiert sein muss. Am einfachsten (wenn auch nicht am performantesten, aber das ist wohl wieder Micro-Benchmarking) synchronisiert man in diesem Fall einfach alles:

Java:
run(){
  while(alive){ 
     sychronized(data){ 
         ...
     }
     repaint();
     Thread.sleep(10); // bzw deine Variante, die wohl schöner ist
}

paintComponent/mouseClicked/mouseMoved usw(){
    synchronized(data){
         ...
    }
}
 

faetzminator

Gesperrter Benutzer
Wenn die (primitive) Variable nur in einem Fall geschrieben wird, kann man grundsätzlich einfach den Wert an anderen Orten, an welchen diese mehrfach verwendet wird, kopieren. Also z.B. [c]int localRadius = radius;[/c]
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
Developer_X Anpeilungsformeln Spiele- und Multimedia-Programmierung 24

Ähnliche Java Themen

Neue Themen


Oben