Moving a circle on a map using Observer pattern - java

I have a (probably an easy) problem with my java program. I'm trying to create a circle that will move from one place to another. The map is a part of an easy dialogue game that says "go here" and the map should react to it. It has to be using an Observer design pattern.
So far I have the map in the game implemented but I just can't fathom how to make the circle function as it should, while using Observer, too. Thanks for any help
package GUI;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Circle;
import logika.Game;
import logika.IGame;
import main.Main;
import utils.Observer;
public class Mapa extends AnchorPane implements Observer{
private IGame game;
private Circle dot;
//private double posTop = 0.0;
//private double posLeft = 0.0;
public Mapa(IGame game){
this.game = game;
game.getGamePlan().registerObserver(this);
init();
}
private void init(){
ImageView obrazekImageView = new ImageView(new Image(Main.class.getResourceAsStream("/zdroje/mapa.gif"),255,255,false,true));
tecka = new Circle(10, Paint.valueOf("red"));
//this.setTopAnchor(tecka, 15.0);
//this.setLeftAnchor(tecka, 20.0);
this.getChildren().addAll(obrazekImageView, dot);
update();
}
public void newGame(IGame newGame){
game.getGamePlan().removeObserver(this);
game= newGame;
game.getGamePlan().registerObserver(this);
update();
}
#Override
public void update(){
this.setTopAnchor(this, game.getGamePlan().getCurrentPlace().getPosTop());
this.setLeftAnchor(this, game.getGamePlan().getCurrentPlace().getPosLeft());
}
}

You should implement the pattern like seen in this UML diagram from Wikipedia:
That is you will have an interface like CommandObserver with a method like notify that is called on each observer every time the event occurs. The event contains the exact command and all stuff that is neccessary to move the circle around. I'll assume you can move it by only having the reference to the circle. All in all it may look like
public interface CommandObserver {
void notify(String command, Circle circle);
}
Next the invoker (let's call it CommandReceiver) of the event must have some register method where all observers can be registered. And it must have some kind of notifyObservers that it calls if the event occurs:
public class CommandReceiver {
// The reference to the circle
private Circle circle = ...
private Set<CommandObserver> observers = new HashSet<>();
public void registerObserver(CommandObserver observer) {
observers.add(observer);
}
public void unregisterObserver(CommandObserver observer) {
observers.remove(observer);
}
private void notifyObserver(String command, Circle circle) {
for (CommandObserver observer : observers) {
observer.notify(command, circle);
}
}
// If a command was entered
public void commandEntered(String command) {
notifyObserver(command, circle);
}
}
And last you will implement observers for all possible commands like:
public PositionMoveObserver implements CommandObserver {
private int x;
private int y;
private String command;
public PositionMoveObserver(int x, int y, String command) {
this.x = x;
this.y = y;
this.command = command;
}
#Override
public void notify(String command, Circle circle) {
// Not interested in, reject
if (!this.command.equals(command)) {
return;
}
// Move the circle to our destination
circle.moveTo(x, y);
}
}
And then create and register it for every location:
private void createObservers(CommandReceiver invoker) {
invoker.registerObserver(new PositionMoveObserver(0, 5, "bottomLeft"));
invoker.registerObserver(new PositionMoveObserver(100, 5, "bottomRight"));
invoker.registerObserver(new PositionMoveObserver(0, 50, "middleLeft"));
...
}
Note that for a specific command only one observer should listen to it, otherwise multiple instances will move the circle around and the result will depend on the iteration order of invokers HashSet.

Related

AffineTransform: centered zooming

I'm doing a small program as a school assignment. Its rather simple; open an image in a JPanel and then be able to zoom (in or out) and translate using the mouse dragged. I got the translation working so far and the zoom works but its not centered. I would like the zoom to be centered, even after a translation was done. Here is my class so far:
package modele;
import java.awt.geom.AffineTransform;
import java.util.Observable;
import java.io.Serializable;
import vue.JPanelImage;
public class Perspective extends Observable implements Serializable {
private static final long serialVersionUID = 1L;
private JPanelImage panneauSource;
private AffineTransform transformation;
private double zoom = 1;
public Perspective(JPanelImage panneauImage) {
this.panneauSource = panneauImage;
this.transformation = new AffineTransform();
this.addObserver(panneauImage);
}
public AffineTransform getTransformation() {
return transformation;
}
public void setTransformation(AffineTransform transformation) {
this.transformation = transformation;
setChanged();
notifyObservers();
}
public void zoom(double wheelRotation) {
zoom = 1;
zoom += (.1 * -wheelRotation);
System.out.println("Zoom: " + zoom);
transformation.scale(zoom, zoom);
setChanged();
notifyObservers();
}
public void translate(double[] coordSouris) {
double newX = coordSouris[2] - coordSouris[0];
double newY = coordSouris[3] - coordSouris[1];
transformation.translate(newX*3, newY*3);
setChanged();
notifyObservers();
}
}
Any idea how to get this working? This project is made under the constraint of using various programming templates such as Command and Singleton. So the methods zoom and translate will be called at each mousescroll and mousedrag event. And sorry for the french!
Thanks for the help.

How can I get my score in an other class? JAVA

I'm making a game with multiple asteroids going down. The goal is to dodge as many asteroids for as long as you can. I made it so that as soon as an asteroid reaches the bottom of the screen it is send back up, now i want to count every time it gets to the bottom and add it to the score.
My problem is that all the asteroids are the same class so if I use:
if(y>700){
y=-50; //
x= (int) (Math.random()*670); // To send the asteroid back up
setLocation(x,y); //
score++; // To add up the score
System.out.println(score); // To print the score
Every asteroid adds op his own amount of times it has reached the bottom, but I want to know how many asteroids have reached the bottom in total. So I figured I have to get the score out of the asteroid class and add it up in an other class but I don't know how.
This is the code of the asteroid class:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Astroid extends JPanel implements ActionListener
{
public int yVelocity = 1;
public int x = (int) (Math.random()*650), y = (int) (Math.random()*-1000);
public Timer timer;
private int score;
public Astroid(int x,int y)
{
this.setLocation(x, y);
this.setSize(25, 25);
this.setBackground(Color.WHITE);
this.setVisible(true);
}
{
this.timer = null;
this.timer = new Timer(10,new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0)
{
setLocation(x,(y+=yVelocity));
timer.setDelay(10);
repaint();
if(y>700){
y=-50;
x= (int) (Math.random()*670);
setLocation(x,y);
score++;
System.out.println(score);
}
}
});
timer.start();
}
}
This is the code of the class that creates the asteroids:
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SterTest extends JFrame implements ActionListener
{
public int a;
public SterTest()
{
this.setSize(700, 700);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
final JPanel p = new JPanel();
p.setBackground(Color.BLACK);
p.setLayout(null);
for(a=0;a<3;a++) {
Astroid astroid = new Astroid(1,1);
p.add(astroid);
} //Creates 3 asteroids to start with
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
Astroid astroid2 = new Astroid(1,1);
p.add(astroid2);
a++;
System.out.println("Het aantal asteroids in game is:"+a);
}
}, 5000, 5000); // creates a new asteroid every 5 seconds or so
this.add(p);
this.setVisible(true);
}
Keeping track of the score probably isn't the job of the Asteroid class, so that variable would be best kept in a more central class. Then you can add an instance of the central class to each asteroid, and call a method to increment the score. Something like this:
public class ScoreKeeper {
private AtomicInteger score;
public void incrementScore (int points) {
score.getAndAdd (points);
}
public int getScore () {
return score.get ();
}
}
In you class SterTest you would create a single instance of ScoreKeeper and pass it to each new Astroid instance you create.
public class Astroid {
private ScoreKeeper scoreKeeper;
public Astroid(int x,int y, ScoreKeeper scoreKeeper) {
//... existing code ...
this.scoreKeeper = scoreKeeper;
}
// ... when you want to increment the score, do this:
scoreKeeper.incrementScore (1);
}
Note: There are better and more OO approaches to this. This answer is that fastest route to working code from your current solution.
Declare score as static.
private static int score
This will give you a single copy of that variable.
However
I see you have some threads. In this case you're going to need a little bit of synchronization to keep score consistent.
I recommend adding
private static final Object scoreLock
to your fields. And replace score++ with
synchronized(scoreLock)
{
Asteroid.score++;
}
If you're not interested in keeping score of an individual asteroid than there is no reason to add a score variable to that class. The score should be maintained at a higher level, perhaps in your SterTest() class. When you detect an asteroid has made it to the bottom, just increment that score. I don't want to tell you how to design your program, but the most obvious way would be to just have actionPerformed return the score.
Create an AsteroidListener interface. Maybe like this:
public interface AsteroidListener {
public void impact(Asteroid asteroid);
}
Make your SterTest class implement this interface. Add a new parameter to your Asteroid constructor, for the listener.
public Asteroid(int x, int y, AsteroidListener listener) { }
When you need to call the listener to notify it that there was an impact:
listener.impact(this);
Tally up the score in your SterTest class.
Use the Java Observer/Observable pattern:
public class Score extends Observable {
private AtomicInteger counter = new AtomicInteger();
public void increment() {
setChanged();
notifyObservers(Integer.valueOf(counter.incrementAndGet()));
}
}
This class is added to Astroid :
public Astroid(Score score, int x,int y) {
// ...
#Override
public void actionPerformed(ActionEvent event) {
// ...
score.increment();
}
}
StarTest implements the Observer interface:
public class StarTest implements Observer {
// ...
#Override
public void update(Observable o, Object arg) {
Integer count = (Inter)arg;
// do something with the score
}
}
You connect Observer and Observable as follows:
StarTest starTest = new StarTest();
Score score = new Score();
score.addObserver(starTest);
Astroid astroid = new Astroid(score, x, y);

How to delete an JPanel Object?

Im on to create a little "game", something like an 2d AirForce Shooter.
So, i have a problem with deleting unused enemys.
An Enemy is an simple JPanel, which is saved in the main logic as an array List.
public static ArrayList<Enemy> enemys = new ArrayList<Enemy>();
The Enemy run logic does the following:
while(!destroyed){
if(Game.running){
x--;
if(getBounds().intersects(Field.player.getBounding())){
Player.death = true;
}
if(x < 0){
Field.deleteEnemy(this);
}
setBounds((int) x, (int) y, 100, 50);
try{Thread.sleep(10);}catch(InterruptedException e){}
}
}
So you can seem there i already tried to call the method deleteEnemy, and just give it the unused Enemy.
But it isnt possible - when i just do this:
public static void deleteEnemy(Enemy e){
System.out.println("test");
enemys.remove(e);
}
It will be just removed from the list, but coninues existing on the Main JPanel.
And i cannot say
remove(e);
Because then i try to call a non static function in a static.
So, how could i delete an Enemy? Someone knows?
Thanks for help!
The hole code: (Game.java)
And, Enemy.java:
package Game;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class Field extends JPanel implements Runnable{
public static Player player = new Player();
public static ArrayList<Enemy> enemys = new ArrayList<Enemy>();
private Thread moveBackground = new Thread(this);
private boolean bgMoving = false;
public static boolean addMob = false;
private int x = 0;
private int bgSpeed = -1;
public Field(){
setBounds(0, 0, 800, 600);
setFocusable(true);
setLayout(null);
addKeyListener(new Handler());
add(player);
}
public void paintComponent(Graphics g){
Field.super.paintComponent(g);
g.drawImage(Images.images[0], x, 0, this);
}
public static void deleteEnemy(Enemy e){
System.out.println("test");
enemys.remove(e);
}
public void run(){
while(!Player.death){
if(bgMoving){
bgMoving = true;
x += bgSpeed;
if(x < -(Images.images[0].getWidth(this) - this.getWidth() - 20)){
bgMoving = false;
}
repaint();
try { Thread.sleep(20); } catch (InterruptedException e) {}
}
if(addMob){
enemys.add(new Enemy());
add(enemys.get(enemys.size() - 1));
addMob = false;
}
}
JOptionPane.showMessageDialog(null, "DIED!");
}
public class Handler extends KeyAdapter {
public void keyPressed(KeyEvent e) {
player.KeyPressed(e);
if(!bgMoving){
if(Game.running){
bgMoving = true;
if(moveBackground.getState().toString() == "NEW"){
moveBackground.start();
}
}
}
}
public void keyReleased(KeyEvent e) {
player.KeyReleased(e);
}
}
}
And, Enemy.java:
package Game;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Enemy extends JPanel implements Runnable{
Thread t = new Thread(this);
private double x = Game.width();
private double y = Math.random() * Game.height();
private double xF = 0, yF = 0;
private boolean destroyed = false;
public Enemy(){
setBounds((int) x, (int) y, 100, 50);
setOpaque(false);
t.start();
}
public void paintComponent(Graphics g){
Enemy.super.paintComponent(g);
g.setColor(Color.GREEN);
g.drawImage(Images.images[2], 0, 0, this);
}
public void run() {
while(!destroyed){
if(Game.running){
x--;
if(getBounds().intersects(Field.player.getBounding())){
Player.death = true;
}
if(x < 0){
Field.deleteEnemy(this);
}
setBounds((int) x, (int) y, 100, 50);
try{Thread.sleep(10);}catch(InterruptedException e){}
}
}
}
}
After removing you will need to call revalidate() and repaint()
[Too long for a comment]
I think the problem is in your logic on removing an Enemy/JPanel:
You are removing it from the ArrayList only, what about the containing JPanel/JFrame you added it to?
You must remove the JPanel from its container (maybe another JPanel or the JFrame) not just the ArrayList via Component#remove(Component c).
If you drew the Enemy images directly in paintComponent(...) of your container via iterating the ArrayList; removing it from the ArrayList would be sufficient, as it will no longer be in the Array and thus no longer drawn on the next repaint().
+1 to #Optional, you may need to call revalidate() and repaint() on the container for the affects of the removed JPanel/Enemy to be shown.
Also as #darijan mentioned, the use of static variables along with instance is not really a great design (though for certain designs this may be fine).
In your case if you need access to an instance method of another class, within another class, simply pass the instance of the class whos method you would like to access to the object which will access it.
Here is some psuedo code expressing much of the above mentioned problems / solutions:
public class Field extends JPanel {
private ArrayList<Enemy> enemies;
public Field() {
...
enemies.add(new Enemy(this));//create a new enemy and pas it the JPanel instance so it may access instance methods of this class
}
//ONLY USED IF JPanel for Enemy is ommited and Enemy class created which represents Enemy object and not Enemy object and aJPanel
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
ArrayList<Enemy> enemiesClone = new ArrayList<>(enemies);//copy array into another so we don't get a ConcurrentModificaton exception if removeEnemy is called while iterating the list
if(!enemiesClone.isEmpty())
for(Enemy e:enemiesClone) {//iterate through array of images
draw(e.getImage(),e.getX(),e.getY(),this);
}
}
public void removeEnemy(Enemy e) {
enemies.remove(e);//remove from the array
//ONLY USED IF JPanels are used as Enemy
remove(e);//remove from the JPanel
//so the changes of removed panel can be visible seen
revalidate();
repaint();
}
}
class Enemy extends JPanel //extends JPanel should be ommited for paintComponent method of drawing an enemy onscreen
{
private int x,y;
private BufferedImage image;
private Field f;
public Enemy(Field f) {//constructor accepts Field instance to access instance method for the class
this.f=f;
}
public void update() {
if(offscreen||dead) {
f.removeEnemy(this);//call removeEnemy which is an instance method of Field
}
}
//BELOW METHODS ONLY USED WHEN Enemy represents object and not a JPanel which can draw its image itself (and update position by simply changing co-ordinates)
public BufferedImage getImage() {
return image;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
For a more detailed look check Game Development Loop, Logic and Collision detection Java Swing 2D I made which will give you the basics needed for most 2D games. However I do not use JPanels rather draw directly to a container.
Where do you add an Enemy to JPanel?
Basically, you should call remove on Field JPanel:
public void deleteEnemy(Enemy e){
System.out.println("test");
enemys.remove(e);
this.remove(e);
}
The method should not be static.

I have a somewhat (read: EXTREMELY) broken hitwall function and I don't see what I am doing wrong

Alright, now I am working on a small derp-ish game based on Brownian motion, and I am working on a hit-wall function for the particles, but it isn't working. Basically, I am expressing the direction of the particles in radians, and whenever it hits a wall, I add Pi radians to the direction to flip it, but for some reason, it either isn't getting called or not working. These are the peices of code I have, any suggestions are welcome.
import java.awt.*;
public class Particle implements Actor {
protected double xPos;
protected double yPos;
protected Velocity v;
protected Color hue;
protected boolean needsUpdate;
public Particle(){
this((Math.random()*500),(Math.random()*500),new Velocity((int)(Math.random()*500),(Math.random()*Velocity.TAU)));
}
public Particle(double x, double y, Velocity vel){
xPos=x;
yPos=y;
v=vel;
hue=Color.red;
needsUpdate=false;
}
public void draw(Graphics g) {
g.setColor(hue);
g.fillOval((int)xPos, (int)yPos, 16, 16);
}
public void act() {
xPos+=v.getSlopefromDirection();
yPos+=1;
}
public void onHitWall(int dir) {
v.setDirection((v.getDirection()+Math.PI)%(Math.PI*2));
}
public void onHitOther(Actor other) {
}
public boolean canCollide() {
return true;
}
public int getLeftX() {
return (int)xPos;
}
public int getRightX() {
return (int)xPos+4;
}
public int getTopY() {
return (int)yPos;
}
public int getBottomY() {
return (int)yPos+4;
}
}
And this is the class I am using to display it:
import java.awt.*;
import java.util.*;
import javax.swing.*;
import static java.lang.System.out;
public class Feild extends JFrame{
protected ArrayList<Actor> actors;
private final int size=500;
protected Thread th;
public Feild(ArrayList<Actor> a){
super("A Brownian Love Story");
setSize(size, size);
actors=a;
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
th = new Thread();
while(true){
paint(getGraphics());
}
}
public void paint(Graphics g){
super.paint(g);
//g.fillRect(0, 0, 500, 500);
for(int i=0;i<actors.size();i++){
if(actors.get(i).getLeftX()<=0)
actors.get(i).onHitWall(1);
if(actors.get(i).getRightX()>=500)
actors.get(i).onHitWall(2);
if(actors.get(i).getTopY()<=0)
actors.get(i).onHitWall(1);
if(actors.get(i).getBottomY()>=500)
actors.get(i).onHitWall(2);
actors.get(i).act();
actors.get(i).draw(g);
}
for(int i=0;i<1000;i++){out.println(i);}
}
}
So i tried changing the paint function to checking for collisions after acting, and it still looks like onHitWall is getting skipped over, even though after putting in a print statement it isn't.
The problem you have is that your particles will move until they hit a wall, and then the next time you go through the look, they will still be in the wall, turn around again, and continue going into the wall.
If you check for collisions after your act() function this should solve the problem.
They will go into the wall, see they are in the wall, and turn around. The next loop, they will move back out of the wall, and continue on.

Where can I put mouse event handling code so that it is out of the way but can still manipulate objects?

I'm learning Java here and trying to get to grips with classes and how best to catch mouse events etc. for my little game, so please be patient and give my some hints.
The main class of my project extends JFrame and upon construction adds to itself an object of a class which extends JPanel. This JPanel covers the whole window and it is this I am using to paint stuff to the screen. Then I have a class called Scene which extends nothing but which stores all the references to the current objects on the screen and is responsible for compiling them all and passing them back to the JPanel. I didn't want to put the full mouse event code in the JPanel because it would be so messy so I though I'd create another class for that called MEs and let each scene have one. My thinking was, this way each mes object can access the objects in each scene easily. So my code is looking like this:
class DPanel extends JPanel {
Scene scCurrent;
public DPanel() {
scCurrent = new Scene();
addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent me) { scCurrent.mes.moved(me); }
});
...
}
...
but of course, inside scCurrent.mes.moved() I don't even know how to change the cursor. It doesn't recognise setCursor() in there. How can I change the cursor and access objects that are higher up the tree like I'd need to to switch scene? Or is there some other place I can tuck my mouse event handling code that will not bumph-out out my JPanel?
You are trying to separate out the control code from the view (GUI) code, and this is a good thing. A solution is to give the view code public methods that allow outside code to change its state, and give the control classes a reference to the view so that the controls (i.e., MouseListeners, MouseMotionListeners, ActionListeneres) can call these methods on a proper reference and change the view's states.
Edit 1
It may be easier just to show you an example:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class ViewControl {
private static void createAndShowGui() {
View view = new View();
Control control = new Control(view);
view.addMouseAdapter(control);
JFrame frame = new JFrame("ViewControl");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class View extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private List<Point> points = new ArrayList<Point>();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (points.size() > 1) {
for (int i = 0; i < points.size() - 1; i++) {
int x1 = points.get(i).x;
int y1 = points.get(i).y;
int x2 = points.get(i + 1).x;
int y2 = points.get(i + 1).y;
g.drawLine(x1, y1, x2, y2);
}
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void addMouseAdapter(MouseAdapter adapter) {
// addMouseListener(adapter);
addMouseMotionListener(adapter);
}
public void addPoint(Point p) {
points.add(p);
repaint();
}
}
class Control extends MouseAdapter {
private View view;
public Control(View view) {
this.view = view;
}
#Override
public void mouseDragged(MouseEvent e) {
view.addPoint(e.getPoint());
}
}

Categories