2D Game camera logic - java

I'm trying to implement a camera for a 2D game that I'm making... The goal will to have the cam keep the player in the center and the sprites relative to the camera.
To get the hang of normalocity's post, I tried starting off simple by making a Camera Test project, where I'd simulate a camera by drawing a sprite to a JPanel, and moving a "camera" object (which is the JPanel) around and setting the sprite's x,y relative to that.
The Camera, as I said, is the JPanel... and I've added a "world", which is a class with an x,y of 0,0, and w=1000, h=1000. I've included the sprite's location relative to the world as well as the camera. When I move the camera up, the sprite moves down and the player stays in the middle as expected..
But if I keep pressing down, the sprite seems to keep drawing over itself.
My questions are:
Am I on the right track in implementing a camera given the code below?
Why does the sprite start to draw over itself there? It should just disappear off the viewPort/JPanel
Thanks!
Now with PaintComponent(g) added, my JPanel bg color of gray now slides off. Is this supposed to happen?
EDIT: SSCCE of my program:
Main Class:
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MainSSCCE extends JFrame {
static MainSSCCE runMe;
public MainSSCCE() {
JFrame f = new JFrame("Camera Test");
CameraSSCCE cam = new CameraSSCCE(0, 0, 500, 500);
f.add(cam);
f.setSize(cam.getWidth(), cam.getHeight());
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
f.setLocation( (screensize.width - f.getWidth())/2,
(screensize.height - f.getHeight())/2-100 );
}
public static void main(String[] args) {
runMe = new MainSSCCE();
}
}
Camera Class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
//Camera is the JPanel that will draw all objects... each object location will be in relation to the World
public class CameraSSCCE extends JPanel implements KeyListener {
//add world to camera...
private static final long serialVersionUID = 1L;
private int camX, camY, camH, camW;
private SpriteSSCCE sprite;
private PlayerSSCCE player;
private WorldSSCCE world;
public CameraSSCCE(int x, int y, int w, int h) {
camX = x;
camY = y;
camW = w;
camH = h;
sprite = new SpriteSSCCE(this, 300, 300, 20, 20);
player = new PlayerSSCCE(this, camW/2, camH/2, 25, 40);
world = new WorldSSCCE(this, 0, 0, 1000, 1000);
addKeyListener(this);
setFocusable(true);
}
public int getWidth() {
return camW;
}
public int getHeight() {
return camH;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//cam is 500 x 500
g.setColor(Color.gray);
g.fillRect(camX, camY, camW, camH);
//draw sprite at JPanel location if in camera sight
if (((sprite.getX()-camX) >= camX) && ((sprite.getX()-camX) <= (camX+camW)) && ((sprite.getY()-camY) >= camY) && ((sprite.getY()-camY) <= (camY+camH))) {
g.setColor(Color.green);
g.fillRect(sprite.getX()-camX, sprite.getY()-camY, 20, 20);
//Cam Sprite Location
g.setColor(Color.white);
g.drawString("Camera Sprite Location: (" + (sprite.getX()-camX) + ", " + (sprite.getY()-camY) + ")", sprite.getX()-camX, sprite.getY()-camY);
}
//Player location (center of Camera... Camera follows player)
g.setColor(Color.cyan);
g.fillRect(player.getX()-player.getWidth(), player.getY()-player.getWidth(), player.getWidth(), player.getHeight());
g.setColor(Color.white);
//World Sprite Location
g.drawString("World Sprite Location: (" + sprite.getX() + ", " + sprite.getY() + ")", sprite.getX(), sprite.getY());
//Cam Player Location
g.drawString("Cam Player Location: (" + (camW/2-player.getWidth()) + ", " + (camH/2-player.getHeight()) + ")", camW/2-player.getWidth(), camH/2-player.getHeight());
}
public void keyPressed(KeyEvent e) {
//move camera right in relation to World
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
camX+=5;
}
//move camera left in relation to World
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
camX-=5;
}
//move camera up in relation to World
if (e.getKeyCode() == KeyEvent.VK_UP) {
camY-=5;
}
//move camera down in relation to World
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
camY+=5;
}
repaint();
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
}
World Class:
public class WorldSSCCE {
private int x, y, w, h;
private CameraSSCCE camera;
public WorldSSCCE(CameraSSCCE cam, int x, int y, int w, int h) {
camera = cam;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.w;
}
public int getHeight() {
return this.h;
}
}
Player Class:
import java.awt.Dimension;
public class PlayerSSCCE {
private int x, y, w, h;
private CameraSSCCE cam;
public PlayerSSCCE(CameraSSCCE cm, int x, int y, int w, int h) {
cam = cm;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.w;
}
public int getHeight() {
return this.h;
}
public void setX(int val) {
this.x += val;
}
public void setY(int val) {
this.y += val;
}
}
Sprite Class:
import java.awt.Color;
import java.awt.Graphics;
public class SpriteSSCCE {
private int xLoc, yLoc, width, height;
private CameraSSCCE world;
public SpriteSSCCE(CameraSSCCE wld, int x, int y, int w, int h) {
xLoc = x;
yLoc = y;
width = w;
height = h;
world = wld;
}
public int getX() {
return xLoc;
}
public int getY() {
return yLoc;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void paintComponent(Graphics g) {
g.setColor(Color.green);
g.fillRect(xLoc, yLoc, width, height);
}
}

1) You have not honored the paint chain by calling super.paintComponent(g) in paintComponent(..):
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//do drawing here
}
As per Java docs:
protected void paintComponent(Graphics g)
Further, if you do not invoker super's implementation you must honor
the opaque property, that is if this component is opaque, you must
completely fill in the background in a non-opaque color. If you do not
honor the opaque property you will likely see visual artifacts.
2) Also notice the #Override annotation I added and the fact that I changed public modifier to protected as thats what the access level is defined as in the implementation class which we should keep unless for a specific reason.
3) Also Swing uses Keybindings have a read on How to Use Key Bindings
4) Also have a read on Concurrency in Swing specifically on The Event Dispatch Thread which dictates all swing components be created on EDT via SwingUtillities.invokeXXX(..) block:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//create and manipulate swing components here
}
});
5) You extend the JFrame class and create an instance, this is not what you want rather remove the extends JFrame from the class declaration:
public class MainSSCCE extends JFrame { //<-- Remove extends JFrame
public MainSSCCE() {
JFrame f = new JFrame("Camera Test");//<-- instance is created here
}
}

Your world is a virtual area larger than the screen (or your jpanel for what matters). All objects' positions are relative to the world. Let's call them absolute coordinates.
Your camera is a small rectangular portion of the world (your panel). By moving it you see different world portions. If you could move the camera like in the post you link to, then at some point you would not be able to see neither the player nor the other sprite.
Since your goal is to keep the player centered on the screen what does this mean for our world? This means that the player and the camera are moving together in relation to the world.
Given the above it does not make sense to draw a camera sprite as in your first screenshot. The camera sprite should be either invisible or it should be drawn in the same position with the player sprite. Nor it makes sense to change the camera's absolute coordinates without changing the player's. Those two are moving together. (take this into account in your keyPressed() methods)
Now when you are drawing, you are drawing from the camera's point of view (or in other words in the camera's coordinate system). From that point of view, the camera always see a rectangle of (0, 0, cameraWidth, cameraHeight). That's what you should use when clearing the area with gray color. This will fix your moving background issue. Since camera and player always have the same absolute coordinates the player will always be in the same place (this is what we want). The rest of the sprites will be seen relative to camera.
For each one of them you translate them in the camera's coordinate system when you do (sprite.x - cam.x) and (sprite.y - cam.y). Since they are translated, you only need to check if they are inside the camera's rectangle (0, 0, cameraWidth, cameraHeight). If they are you go ahead and draw them. If not ignore them.
I hope that helps
Note: cameraWidth, cameraHeight are your jpanel's dimensions

Related

Trying to draw to a window in JFrame is not exactly working

I'm trying to draw a rectangle to the screen based on mouse listeners. I'm having a few problems:
To start with the shape, it has an offset by a few pixels relative to the
release location of the mouse.
Secondly I can get only the shape to display if I set the screen to
visible each time after adding a JPanel component.
Could anyone tell me what I'm doing wrong or point me in the direction of some documentation? (I’ve read the Oracle documentation but I didn’t really understand it)
I know this isnt the greatest code but i just want to get it working for the moment.
This is my first time asking a question on stackoverflow so if I'm doing something wrong pls tell me.
This is my code
public class Frame {
private JFrame frame;
public Frame() {
this.frame = frameSetup("Graph");
this.frame.addMouseListener(new Mouse());
}
private JFrame frameSetup(String heading) {
JFrame f = new JFrame(heading);
f.setBackground(Color.WHITE);
f.setSize(800, 800);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
return f;
}
public void draw(int xIn, int yIn, int widthIn, int heightIn) {
int x = xIn;
int y = yIn;
int width = widthIn;
int height = heightIn;
this.frame.add(new Panel(x, y, width, height));
this.frame.pack();
//this.frame.setVisible(true);
}
private class Mouse implements MouseListener {
int x = 0;
int y = 0;
int height = 0;
int width = 0;
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == 1) {
x = e.getX();
y = e.getY();
System.out.println(x + ": " + y);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == 1) {
width = (e.getX() - x);
height = (e.getY() - y);
System.out.println(width + ": " + height);
draw(x - 7, y -30, width, height);
}
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
}
and
public class Panel extends JPanel {
private int x;
private int y;
private int width;
private int height;
public Panel(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
}
Welcome to the Site!
The offset is something I've had plenty of trouble with in the past aswell. It's caused by adding the MouseListener to the JFrame rather than it's contentPane. Basically, the JFrame is the thin border and the bar with the name, the icons and the buttons at the top. The content pane sits inside that, here it's the white surface. When you add the MouseListener to the JFrame, the coords are relative to this outline (the top left corner of the frame), rather than the white panel. This is fixed as follows:
// in the Frame constructor
this.frame.getContentPane().addMouseListener(new Mouse());
// in Mouse.mouseReleased(), remove the offset you added.
draw(x, y, width, height);
The Panel problem is a different story. I don't know how the code is doing what it does or how it works at all in the first place, but I can still give you some general tips.
Don't pack() the frame in the drawing method. It's used to calculate the window size while respecting the layout and the content of it. For me, it made the window as small as the OS allowed it to be.
Don't use JPanels like that! You'll run into weird problems like this, since they're made to be used in GUIs. For this reason, they're also way to heavy-weight to be used as a graphic. #user16320675 commented a tutorial on custom painting, I suggest you check that out.
Lastly, I suggest reworking the rectangle drawing. Right now, it only works when dragging from the top left to the bottom right.

How do you rotate a circle about a point in JPanel?

I am trying to rotate a circle around a separate point in a program. right now I can get the circle to rotate but it slowly starts getting closer and closer to the point it's rotating from. I am trying to do this using JPanel and implementing it as a rectangle.
package WoffindenZone;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.*;
import java.lang.Math;
public class Protector extends Rectangle{
double Velocity;
int speed = 3;
Protector(int x, int y, int PROTECTOR_DIAMETER){
super(x,y,PROTECTOR_DIAMETER,PROTECTOR_DIAMETER);
}
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(speed);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(speed);
move();
}
}
public void keyReleased(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(0);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(0);
move();
}
}
public void setDirection(int Direction){
Velocity = Direction*Math.PI/180;
}
public void move(){
x = (int)Math.round(500 + Math.cos(Velocity) * (x-500) - Math.sin(Velocity) * (y-((1000*0.5555)/2)));
y = (int)Math.round(((1000*0.5555)/2) + Math.sin(Velocity) * (x-500) + Math.cos(Velocity) * (y-((1000*0.5555)/2)));
System.out.println(x);
System.out.println(y);
}
public void draw(Graphics g){
g.setColor(Color.blue);
g.fillOval(x,y,width,height);
}
Use a rotation instance of an AffineTransform. See getRotateInstance(theta,anchorx,anchory) for details.
Returns a transform that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3).
How do you rotate a circle about a point in JPanel?
Here's how I rotate a circle about a point in a JPanel.
I don't know how to make an animated GIF. Just imagine the blue circle rotating clockwise around the center of the drawing JPanel.
So, let's start at the beginning. Basically, I have a circle rotating on the circumference of another circle. So, I create a Circle model class from plain Java.
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
The class consists of basic getters and setters. I make the radius and color final because they don't change value in this Java application.
The calculateCircumferencePoint method is the only interesting method. It takes an int angle in degrees, and calculates the point on the circumference represented by that angle, rounded to the nearest X and Y integer points.
Next, we create two Circle instances, an inner circle and an outer circle. Here's the class constructor setting the preferred size of the drawing area, the inner circle, and the outer circle. We start the outer circle at zero degrees (to the right);
private Circle innerCircle;
private Circle outerCircle;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
Now, we can start coding the GUI. First, we start the Java application by calling the SwingUtilities invokeLater method. This method ensures that we create and execute the Swing components on the Event Dispatch Thread.
Next, we define the JFrame. Here's the code we have so far.
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
The JFrame methods must be called in a certain order. This is the order I use for most of my SWwing applications.
I pack the JFrame. I don't set a JFrame size. I let the Swing layout managers set the size of my JFrame. The default layout of a JFrame content pane is a BorderLayout. I put my drawing JPanel in the center of the BorderLayout.
Next, I create the drawing JPanel.
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
All the drawing JPanel does is paint a Circle object. Pretty straightforward.
The fillOval method paints an oval from the upper left hand corner. We calculate the upper left hand point from the center point.
The responsibility for calculating and updating the outer circle center point falls to my controller class, the Animation class. I use a simple loop to update the theta angle, calculate the new outer circle center point, paint the outer circle, and wait a period of time.
Here's that code.
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
The Animation repaint method makes the call to the drawing JPanel repaint method inside another SwingUtilities invokeLater method. This method ensures that the drawing happens on the Event Dispatch Thread.
Finally, here's the complete, runnable, example. I used inner classes so I could post the code as one block, and you can copy and run this code as one block. Generally, classes should be in separate files and for a more complex GUI, separate packages.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotateCircle implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
}

How to calculate the X-Y coordinates and Radius of Circle shape after resize of JFrame?

I'm drawing Circles on JFrame using JComponent (AWT/SWING) and I want to make sure that when the user resizes the frame, that certain calculations are made and circles are drawn on screen dynamically (whether if it's bigger, smaller, moved to left or right etc.). I implemented the ComponentAdapter event and componentResized method however I'm struggling with coming up with something that is dynamic. Here's my code:
CircleViewer Class
import javax.swing.JFrame;
import java.awt.event.*;
public class CircleViewer
{
public static void main(String[] args)
{
final JFrame frame = new JFrame("Circle Shapes");
final CirclePanel panel = new CirclePanel();
// Class for Mouse Listener which implements the necessary interfaces
class MousePressListener implements MouseListener, MouseMotionListener
{
public void mouseClicked(MouseEvent event) { }
public void mouseEntered(MouseEvent event) { }
public void mouseExited(MouseEvent event) { }
public void mouseWheelMoved(MouseWheelEvent event) { }
public void mouseMoved(MouseEvent event) { }
public void mousePressed(MouseEvent event) { }
#Override
public void mouseDragged(MouseEvent event)
{
var x = event.getX();
var y = event.getY();
panel.moveTo(x, y);
}
#Override
public void mouseReleased(MouseEvent event)
{
panel.finalMove();
}
}
panel.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent event)
{
panel.frameResizeCalculation(frame.getWidth(), frame.getHeight());
}
});
MousePressListener listener = new MousePressListener();
panel.addMouseListener(listener);
panel.addMouseMotionListener(listener);
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setVisible(true);
}
public static final int FRAME_WIDTH = 700;
public static final int FRAME_HEIGHT = 500;
}
CirclePanel Class
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.util.ArrayList;
import java.awt.Color;
import java.lang.Math;
import java.awt.BasicStroke;
import java.awt.Stroke;
public class CirclePanel extends JComponent
{
private int mouseX;
private int mouseY;
private ArrayList<Circle> circleList;
private final BasicStroke dashLine = new BasicStroke(1,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL,
0, new float[]{6}, 0);
private Circle newCircle;
private final Color newCircleColor = Color.RED;
private final Color finalCircleColor = Color.BLUE;
public CirclePanel()
{
this.circleList = new ArrayList<Circle>();
this.mouseX = 0;
this.mouseY = 0;
}
public void moveTo(int x, int y)
{
mouseX = x;
mouseY = y;
if (newCircle == null)
{
newCircle = new Circle(x,y,0);
}
else
{
int dX = newCircle.get(0) - mouseX;
int dY = newCircle.get(1) - mouseY;
newCircle.set(2, (int)Math.sqrt(dX*dX + dY*dY));
}
repaint();
}
public void finalMove()
{
if (newCircle != null)
{
circleList.add(newCircle);
newCircle = null;
repaint();
}
}
// Do something here and change X-Y coordinates and radius of the circles and finally call repaint() method of Graphics2D
public void frameResizeCalculation(int width, int height)
{
var dX = CircleViewer.FRAME_WIDTH - width;
var dY = CircleViewer.FRAME_HEIGHT - height;
}
public void paintComponent(Graphics g)
{
g.setColor(finalCircleColor);
for (Circle circle : circleList)
{
drawCircle(g, circle);
}
Circle c = newCircle;
if (c != null)
{
g.setColor(newCircleColor);
drawCircle(g, c);
Graphics2D g2 = (Graphics2D)g.create();
g2.setStroke(dashLine);
g2.drawLine(c.get(0), c.get(1), mouseX, mouseY);
g2.dispose();
}
}
public void drawCircle(Graphics g, Circle c)
{
g.drawOval(c.get(0) - c.get(2), c.get(1) - c.get(2), c.get(2) * 2, c.get(2) * 2);
}
}
and lastly, the Circle
public class Circle
{
private int x;
private int y;
private int radius;
public Circle(int x, int y, int radius)
{
this.x = x;
this.y = y;
this.radius = radius;
}
public int get(int option)
{
switch (option)
{
case 0:
return this.x;
case 1:
return this.y;
case 2:
return this.radius;
}
return 0;
}
public void set(int option, int value)
{
switch (option)
{
case 0: //set x
this.x = value;
break;
case 1:
this.y = value;
break;
case 2:
this.radius = value;
break;
}
}
}
that certain calculations are made and circles are drawn on screen dynamically (whether if it's bigger, smaller, moved to left or right etc.).
Well you need to define what YOU want to happen when the frame is resized. We can't tell you what to do.
However, before you worry about that you need to restructure your classes to make it possible to have the dynamic painting.
I see the following issues with the basic code:
Forget about the frame size. That size is irrelevant to the custom painting that will be done in your CirclePanel. That is the size of the CirclePanel is NOT the same as the size of your frame, since the frame size includes the frame borders and title bar. Your logic should be based on the size of the panel, not the frame.
When doing custom painting it is also the responsibility of the component to override the getPreferredSize() method to return the preferred size of the component. Then in your code you add the panel to the frame and then invoke the pack() method on the frame.
You call your class CirclePanel, but you extend JComponent. Why? Give your class a proper name. If you want to extend JComponent then call your class CircleComponent. If you want to call your class CirclePanel, then extend JPanel. But you also need to understand the difference between extending JComponent and JPanel. All Swing components are responsible for clearing the background of the component BEFORE doing any painting. A JPanel does this for you automatically, you just invoke super.paintComponent(...) at the start. A JComponent does NOT clear the background so you must clear it by setting the color of the Graphics object and then invoke fillRect(0, 0, getWidth(), getHeight()) to paint the background.
The Circle object should contain all the information needed to paint itself. You already have the x, y, radius values. I would suggest you also need a Color property so each Circle can be a different color.
The Circle object should then know how to paint itself using its properties. Therefore your Circle class should have a method, lets say draw(Graphics grapics). You then use the properties of the class to draw the oval. So this means the paintComponent() method would invoke the draw(...) method of the Circle class and you would remove the drawOval(...) method you currently have.
A "getter" method does not take parameters. If you feel other classes need to know the x, y, radius properties then create getX(), getY() and getRadiout() methods. I would suggest you don't need the get()/set() methods.
I suggest you first need to implement the above suggestions before making the painting dynamic.
Next, you don't need a ComponentListener added to the panel. Instead you need to add logic to the paintComponent(...) method of your CirclePanel class. The paintComponent() method will be invoked automatically every time the size of the panel changes. The basic logic would be to determine a "multiplier" to be used when painting each Circle.
So you can use the getPreferredSize() method to get the preferred width and you can use the getWidth() method of the panel to get the current size. So your multiplier would be:
double multiplierX = getWidth() / getPreferredSize().x;
Now this information needs to be passed to the Circle objects draw(...) method, so the method signature would become draw(Graphics g, double multiplierX). When you invoke the drawOval(...) method you apply the multiplier to the "x" parameter. This should cause the Circles to shift in the horizontal direction as the frame is resized.
You would then repeat the above step for the multiplierY to have the Circles shift in a vertical direction.
You would then need to decide how you want to affect the radius?

Draw circles that are created as instances in Java GUI

So I've been stuck on this problem for a while now and I'm desperate for help. Please help me. I've got 3 classes:
Circle is just suppose to draw a circle in the frame created by Frame with random starting position (and defind the radius).
Frame is the mainclass with methods such as addCircle(), bounce(), start(), stop(), run() (moves the circles) and quit(). This class also creates the frame in which the circles are added to.
Interfa is just for now a inteface frame where I define the radius, number of circles and Frame size.
No matter what I try I cannot add more than two circle (one is colored and one is not):
The "recursive way":
private static void addCircle(int n){
Circle[] circles = new Circle[n+10];
if (n > 0){
circles[circleAdd] = new Circle();
frame.add(circles[circleAdd]);
circleAdd = circleAdd + 1;
addCircle(n-1);
}
}
Normal itterative way
private static void addCircles(int n){
ArrayList<Circle> circles = new ArrayList<Circle>();
for(int i = 0; i<=n;i++){
circles.add(new Circle());
frame.add(circles.get(i));
}
}
This is how I create my Frame:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public Class Frame{
private static JFrame frame;
private static int circleAdd = 0;
private static JPanel fra;
public static void mainFrame(){
frame = new JFrame();
frame.setSize(500,500);
frame.setVisible(true);
fra = new JPanel();
frame.add(fra);
...
//addCircle and addCircles
...
public static void main..
}
}
This is my circle:
import java.awt.*;
import javax.swing.*;
import java.util.Random;
public class Circle extends JPanel{
private Random random = new Random();
public void paint(Graphics g){
int randX = random.nextInt(250)+50;
int randY = random.nextInt(250)+50;
g.drawOval(randX,randY,50,50);
g.setColor(Color.ORANGE);
g.fillOval(100,100,50,50);
}
}
I would suggest that your general approach is wrong. Instead of using a JPanel as the element, you should have a JPanel capable of painting any number of "circles". The Graphics2D API is capable of drawing complex shapes (including ovals).
The main issues I can see are:
JFrame by default is using a BorderLayout, this only allows a single component to be placed in each of the five available positions
Layout managers rely on the preferred/minimum/maximumSize hints to make determinations about the size of the components. They are also responsible for deciding on where the component should be placed. In your current implementation, this would mean that it's possible for you to paint beyond the visible range of the component
Overriding paint is not recommend, and failing to call super.paint could cause a number of unexpected and difficult to diagnose issues
Painting can occur at any time, so using random values in the paint method will cause the UI to constantly change
Instead, you could define your own Circle class which takes the location and size you want and simply acts as a container
public class Circle {
private int x;
private int y;
private int radius;
private Ellipse2D shape;
public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.shape = new Ellipse2D.Double(x, y, radius * 2, radius * 2);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getRadius() {
return radius;
}
public Rectangle getBounds() {
return shape.getBounds();
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.ORANGE);
g2d.fill(shape);
}
}
This is simply a container class, it represents the information need to generate the desired outcome. It has a convince method which is capable of then painting the shape itself.
You would then need to create a List of these shapes and paint them to your component
public class TestPane extends JPanel {
private List<Circle> circles = new ArrayList<>(10);
private Dimension size;
public TestPane() {
Random random = new Random();
int maxX = 0;
int maxY = 0;
for (int index = 0; index < 10; index++) {
int randX = random.nextInt(250) + 50;
int randY = random.nextInt(250) + 50;
circles.add(new Circle(randX, randY, 25));
maxX = Math.max(maxX, randX + 50);
maxY = Math.max(maxY, randY + 50);
}
size = new Dimension(maxX, maxY);
}
#Override
public Dimension getPreferredSize() {
return size;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Circle circle : circles) {
Graphics2D g2d = (Graphics2D) g.create();
circle.paint(g2d);
g2d.dispose();
}
}
}
One of the things you seem to lack understanding in is how painting actually works in Swing.
Start by having a look at Performing Custom Painting and Painting in AWT and Swing for more details.
A deeper understanding of how layout managers and the component hierarchy work also wouldn't hurt

Pong Paddle bottom doesn't move up when UP key is pressed

I'm trying to make a pong game but my pong paddle isn't moving up properly for some reason.
When I hit the DOWN arrow key it moves down just fine. But when I hit the UP arrow key the whole paddle just get's longer upwards... If i resize the window the paddle returns to it's normal length at that position. If i press UP key it again continues to extend upwards.
I don't think its my code logic but something about clearing the previously drawn paddle... here's my code,
Code for Paddle:
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.JPanel;
public class Paddle extends JPanel {
int x;
int y;
int width;
int height;
Paddle(){
this.x = 0;
this.y = 0;
this.height = 40;
this.width = 10;
}
Paddle(int x, int y, int width, int height){
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(x, y, width, height);
}
public void moveDown(){
this.y += 3;
repaint();
System.out.println("h: " + height + " x: " + x + " y: " + y + " getW: " + getWidth() + " getH: " + getHeight());
}
public void moveUp(){
this.y -= 3;
repaint();
System.out.println("h: " + height + " x: " + x + " y: " + y + " getW: " + getWidth() + " getH: " + getHeight());
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
Code for Whole Game:
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Pong extends JFrame {
Pong() {
final Paddle p1Paddle = new Paddle();
Paddle p2Paddle = new Paddle();
p1Paddle.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
//super.keyPressed(arg0);
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN:
p1Paddle.moveDown();
break;
case KeyEvent.VK_UP:
p1Paddle.moveUp();
break;
default:
System.out.println("please press up or down");
}
}
});
setLayout(new BorderLayout());
add(p1Paddle, BorderLayout.CENTER);
//only focused components can recieve key events...
p1Paddle.setFocusable(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame frame = new Pong();
frame.setTitle("Pong");
frame.setSize(650, 300);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Any help on this issue or general code advice would be appreciated.
A little hard to tell from the code snippet, but, KeyListeners aren't very reliable. If the key stroke is been consumed (by the UI and underlying implementation) you may not be notified.
Try looking at InputMap and ActionMap instead.
InputMap im = getInputMap(JTable.WHEN_FOCUSED_COMPONENT);
ActionMap am = getActionMap();
KeyStroke downKey = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
KeyStroke upKey = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
im.put(downKey, "Action.down");
im.put(upKey, "Action.up");
am.put("Action.down", new DownAction());
am.put("Action.up", new UpAction());
And see where it takes you...
Update:
Ahh, it's so obvious now, you've overridden the x/y width/height methods of the panel expecting the layout manager to use them to layout out the component, but not really providing a layout manager who knows how to deal with it.
BorderLayout does not care about you "size" or "position" requirements, it will override them with what it thinks you component should be.
What you want to do is use an absolute layout manager instead (null). Also, you DON'T want to implement the X/Y, width/height management, as this is already taken care for you.
So.
In the Pong class. Change the layout manager from BorderLayout to null (also update the add(paddle) method to remove the BorderLayout reference, not required, but removes confusion).
In the Paddle class, remove all references to the x/y, width/height, you don't need them. Instead use setBounds/setLocation.
public class Paddle extends JPanel {
Paddle(){
this(0, 0, 20, 40);
}
Paddle(int x, int y, int width, int height){
setBounds(x, y, width, height);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
// The graphics context has already been translated to x/y for use,
// so we don't need to care about it
g.fillRect(0, 0, getWidth(), getHeight());
}
public void moveDown(){
setLocation(getX(), getY() + 3);
}
public void moveUp(){
setLocation(getX(), getY() - 3);
}
}
And viola, it works.

Categories