I have a problem that I haven't been able to completely understand and thus I am struggling to fix it.
Basically I am busy writing a small game engine for Java Swing, and one of the key components of this engine is the ability to separate design resolution from screen resolution. Meaning if I design a game on a resolution of 400 (w) x 300 (h), and I position an object at the center of the design resolution, then a user can specify the actual resolution they want to play the game at for example 800 (w) x 600 (h) and the object will still be placed correctly at the center of the screen in the current resolution.
This is where I am having trouble, when the design resolution and the current resolution are the same i.e. design resolution 400 x 300 and current resolution is 400 x 300, the object seems to be placed correctly at the center of the screen on start up and the bullet correctly at the center of the player regardless of the players position when moved:
However when the design resolution and current screen resolution are not the same i.e. design resolution 400 x 300 and current resolution is 800 x 600 the object is no longer correctly placed at center of the screen and neither is the bullet centered for the player:
I have a method to generate the center spawn point for all visible objects (the red reference dot, the sprite/player and the bullet) this method is a simple convenience method to help generate a center based coordinate for a Sprite within a container or another Sprite:
public static Point2D getCenterSpawnPoint(int parentWidth, int parentHeight, int childWidth, int childHeight, double childXOffset, double childYOffset) {
double spawnX = ((parentWidth - childWidth) / 2) + childXOffset;
double spawnY = ((parentHeight - childHeight) / 2) + childYOffset;
return new Point2D.Double((int) spawnX, (int) spawnY);
}
The Sprite and bullet render using screen coordinates:
public int getScreenX() {
//return (int) (imageScaler.getWidthScaleFactor() * this.getX());
return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
}
public int getScreenY() {
//return (int) (imageScaler.getHeightScaleFactor() * this.getY());
return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
}
I am unsure of where I am going wrong, but essentially what Id want to see is the same behavior in my first GIF regardless of the current screen size the game is in, the red reference dot seems to position correctly and it is simply drawn to the JPanel and bypasses the getScreen... calls:
// lets draw a centered dot based on the panels dimensions for a reference
int dotSize = 10;
g2d.setColor(Color.red);
Point2D centeredReferencePoint = getCenterSpawnPoint(getWidth(), getHeight(), dotSize, dotSize, 0, 0);
g2d.fillOval((int) centeredReferencePoint.getX(), (int) centeredReferencePoint.getY(), dotSize, dotSize);
Here is the minaml reproducible example:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ResolutionIndependentLocationIssue {
/**
* uncommenting this and commenting the line below will result in the bullet
* spawning correctly at the center of the sprite/player
*/
private static final Dimension CURRENT_SCREEN_SIZE = new Dimension(800, 600);
//private static final Dimension CURRENT_SCREEN_SIZE = new Dimension(400, 300);
private static final Dimension DESIGN_SCREEN_SIZE = new Dimension(400, 300);
private Scene scene;
private Sprite player;
public ResolutionIndependentLocationIssue() {
try {
createAndShowUI();
} catch (IOException ex) {
Logger.getLogger(ResolutionIndependentLocationIssue.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(ResolutionIndependentLocationIssue::new);
}
private void createAndShowUI() throws MalformedURLException, IOException {
JFrame frame = new JFrame("Resolution Issue");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage bulletImage = resize(ImageIO.read(new URL("https://i.stack.imgur.com/JlSEL.png")), 20, 20);
BufferedImage playerImage = resize(ImageIO.read(new URL("https://icons.iconarchive.com/icons/icons8/windows-8/512/Programming-Java-Duke-Logo-icon.png")), 100, 100);
player = new Sprite(playerImage);
player.setBulletImage(bulletImage);
System.out.println();
// center player according to our design resolution
Point2D spawnPoint = getCenterSpawnPoint(DESIGN_SCREEN_SIZE.width, DESIGN_SCREEN_SIZE.height, playerImage.getWidth(), playerImage.getHeight(), 0, 0);
player.setPosition((int) spawnPoint.getX(), (int) spawnPoint.getY());
System.out.println("ResolutionScalingIssue#createAndShowUI() - Player spawn point (always expressed in design resolution co-ordinates): X: " + spawnPoint.getX() + " Y: " + spawnPoint.getY());
System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Design Resolution X: " + player.getX() + " Y: " + player.getY());
System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Screen X: " + player.getScreenX() + " Screen Y: " + player.getScreenY());
System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Width: " + playerImage.getWidth() + " Height: " + playerImage.getHeight());
System.out.println();
this.scene = new Scene();
this.scene.add(player);
this.addKeyBindings();
frame.add(this.scene);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Thread gameLoop = new Thread(() -> {
while (true) {
this.scene.update();
this.scene.repaint();
try {
Thread.sleep(15);
} catch (InterruptedException ex) {
}
}
});
gameLoop.start();
}
private void addKeyBindings() {
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, false), "A pressed");
this.scene.getActionMap().put("A pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0, true), "A released");
this.scene.getActionMap().put("A released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.LEFT = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, false), "D pressed");
this.scene.getActionMap().put("D pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0, true), "D released");
this.scene.getActionMap().put("D released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.RIGHT = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "W pressed");
this.scene.getActionMap().put("W pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.UP = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "W released");
this.scene.getActionMap().put("W released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.UP = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "S pressed");
this.scene.getActionMap().put("S pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.DOWN = true;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "S released");
this.scene.getActionMap().put("S released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.DOWN = false;
}
});
this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "Space pressed");
this.scene.getActionMap().put("Space pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
player.shoot();
}
});
}
public static BufferedImage resize(BufferedImage image, int width, int height) {
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TRANSLUCENT);
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(image, 0, 0, width, height, null);
g2d.dispose();
return bi;
}
/**
* Used to calculate the center based spawning point, to ensure calculations
* are the same for the player spawning on the screen and bullet spawning
* from the player
*
* #return
*/
public static Point2D getCenterSpawnPoint(int parentWidth, int parentHeight, int childWidth, int childHeight, double childXOffset, double childYOffset) {
double spawnX = ((parentWidth - childWidth) / 2) + childXOffset;
double spawnY = ((parentHeight - childHeight) / 2) + childYOffset;
return new Point2D.Double((int) spawnX, (int) spawnY);
}
public class Scene extends JPanel {
private final ArrayList<Sprite> sprites;
public Scene() {
this.sprites = new ArrayList<>();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
sprites.forEach((sprite) -> {
sprite.render(g2d);
});
// lets draw a centered dot based on the panels dimensions for a reference
int dotSize = 10;
g2d.setColor(Color.red);
Point2D centeredReferencePoint = getCenterSpawnPoint(getWidth(), getHeight(), dotSize, dotSize, 0, 0);
g2d.fillOval((int) centeredReferencePoint.getX(), (int) centeredReferencePoint.getY(), dotSize, dotSize);
}
#Override
public Dimension getPreferredSize() {
return CURRENT_SCREEN_SIZE;
}
#Override
public boolean getIgnoreRepaint() {
return true;
}
public void add(Sprite sprite) {
sprite.setScence(this);
this.sprites.add(sprite);
}
private void update() {
sprites.forEach((sprite) -> {
sprite.update();
});
}
}
public class Sprite {
protected int x;
protected int y;
protected int speed = 5;
protected final BufferedImage image;
public boolean UP, DOWN, LEFT, RIGHT;
private boolean isFlippedX = false;
private Scene scene;
private BufferedImage bulletImage;
public Sprite(BufferedImage image) {
this.image = image;
}
public void render(Graphics2D g2d) {
// sprite is drawn based on the position of the current screen relative to our design screen size
g2d.setColor(Color.red);
g2d.drawRect(this.getScreenX(), this.getScreenY(), this.getWidth(), this.getHeight());
if (this.isFlippedX) {
// flip horizontally
g2d.drawImage(this.image, this.getScreenX() + this.image.getWidth(), this.getScreenY(), -this.getWidth(), this.getHeight(), null);
} else {
g2d.drawImage(this.image, this.getScreenX(), this.getScreenY(), null);
}
}
public void update() {
if (LEFT) {
setFlippedX(true);
this.x -= this.speed;
}
if (RIGHT) {
setFlippedX(false);
this.x += this.speed;
}
if (UP) {
this.y -= this.speed;
}
if (DOWN) {
this.y += this.speed;
}
}
public void setFlippedX(boolean isFlippedX) {
this.isFlippedX = isFlippedX;
}
/**
*
* #return The current screen x co-ordindate of the sprite relative to
* the design resolution
*/
public int getScreenX() {
//return (int) (imageScaler.getWidthScaleFactor() * this.getX());
return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
}
/**
*
* #return The current screen y co-ordindate of the sprite relative to
* the design resolution
*/
public int getScreenY() {
//return (int) (imageScaler.getHeightScaleFactor() * this.getY());
return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
}
/**
*
* #return The design resolution x co-ordindate
*/
public int getX() {
return this.x;
}
/**
*
* #return The design resolution y co-ordindate
*/
public int getY() {
return this.y;
}
public int getWidth() {
return this.image.getWidth();
}
public int getHeight() {
return this.image.getHeight();
}
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
public void setBulletImage(BufferedImage bulletImage) {
this.bulletImage = bulletImage;
}
public void shoot() {
System.out.println("Sprite#shoot() - Player Design Resolution X: " + this.getX() + " Y: " + this.getY());
System.out.println("Sprite#shoot() - Player Width: " + this.getWidth() + " Height: " + this.getHeight());
/**
* center the bullet according to the players design x and y
* co-ordinates, this is necessary as x and y should the design
* co-ordinates and render method will call getScreenX and
* getScreenY to calculate the current screen resolution
* co-ordinates
*
*/
Point2D spawnPoint = getCenterSpawnPoint(this.getWidth(), this.getHeight(), bulletImage.getWidth(), bulletImage.getHeight(), this.getX(), this.getY());
Bullet bullet = new Bullet((int) spawnPoint.getX(), (int) spawnPoint.getY(), this.bulletImage);
System.out.println("Sprite#shoot() - Bullet spawn point (always expressed in design resolution co-ordinates): X: " + spawnPoint.getX() + " Y: " + spawnPoint.getY());
System.out.println("Sprite#shoot() - Bullet spawn: X: " + bullet.getX() + " Y: " + bullet.getY());
System.out.println("Sprite#shoot() - Bullet spawn: Screen X: " + bullet.getScreenX() + " Screen Y: " + bullet.getScreenY());
System.out.println();
//bullet.LEFT = this.isFlippedX;
//bullet.RIGHT = !this.isFlippedX;
this.scene.add(bullet);
}
public void setScence(Scene scene) {
this.scene = scene;
}
}
public class Bullet extends Sprite {
public Bullet(int x, int y, BufferedImage image) {
super(image);
this.x = x;
this.y = y;
this.speed = 10;
}
}
}
Any help would be greatly appreciated!
UPDATE:
When using the solution by #akuzminykh all seems to work fine, however, now when I set the players position to something like player.setPosition(0,0), expecting it to appear in the top left corner, I get this instead:
which makes sense as I assume we are now positioning via the coordinate being at the center of the sprite, but how would I fix his so both setPosition for the top left corner and center would work, I think I might need to fix the getCenterSpawnPoint?
In your methods getScreenX and getScreenY you are ignoring that getX and getY include the width and height of the sprite. E.g. getX doesn't give you the center position of the sprite in the x-axis, but the position minus half of the sprite's width. When you scale this like you do in getScreenX, then you also scale the offset in x for the sprite. To solve this, simply add the offset initially, do the scaling and subtract the offset finally.
/**
*
* #return The current screen x co-ordindate of the sprite relative to
* the design resolution
*/
public int getScreenX() {
//return (int) (imageScaler.getWidthScaleFactor() * this.getX());
//return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
double halfWidth = this.getWidth() / 2.0;
double xCenterDesign = this.getX() + halfWidth;
double xCenterCurrent = xCenterDesign / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width;
return (int) (xCenterCurrent - halfWidth);
}
/**
*
* #return The current screen y co-ordindate of the sprite relative to
* the design resolution
*/
public int getScreenY() {
//return (int) (imageScaler.getHeightScaleFactor() * this.getY());
//return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
double halfHeight = this.getHeight() / 2.0;
double yCenterDesign = this.getY() + halfHeight;
double yCenterCurrent = yCenterDesign / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height;
return (int) (yCenterCurrent - halfHeight);
}
Or more mathematically:
If we take your example with 400x300 in "design" resolution, 800x600 being the "current" resolution and the sprite being 100x100 big: The position of the sprite is (150, 100), which makes sense: (400 / 2 - 100 / 2, 300 / 2 - 100 / 2). Now the formula you've used to bring it in "current" resolution (only for x because I'm lazy): 150 / 400 * 800 = 300. Hm, but half of 800 is 400 and the position should be 400 - 100 / 2? Exactly, the offset 100 / 2 for the sprite got scaled as well, from 50 to 100, which results in .. 400 - 100 = 300.
Therefore, add the offset back initially, so you scale the center. Then it's: (150 + 50) / 400 * 800 = 400. Don't forget to finally subtract the offset: 400 - 50 = 350. Now you have the correct position in the x-axis.
Re: UPDATE:
When you want to put the sprite in the top left corner, you might expect player.setPosition(0, 0) to do the trick. This is not the case. The way you've written it, the coordinates given by getX and getY include the width and height of the sprite, remember? Methods like getScreenX and getScreenY, with my fix, consider that and are used to render the sprite at the correct position. That means the coordinates (0, 0) describe the position of the center to be at (0 + 50, 0 + 50), where 50 is just 100 / 2, the width and height of the sprite divided by two.
To place the sprite in the top left corner, you need to consider the sprite's width and height when setting its position using the method setPosition: In our example, where the sprite is 100x100 big, you need to pass (0 - 100 / 2, 0 - 100 / 2), so the call looks like this: player.setPosition(-50, -50). You can of course make it dynamic by using playerImage.getWidth() and so on and so on.
Suggestion:
I suggest you to let x and y of Sprite to be relative to the center of the corresponding sprite. This will make some changes to the code necessary but it will also simplify other things and make them more intuitive. E.g. the problem with player.setPosition(0, 0) won't exist, it will actually put the sprite at the top left corner, exactly what you'd intuitively expect. This will also simplify getScreenX and getScreenY. Consider the offsets caused by the sprite's width and height just in the render method. This should be enough.
I want to animate a rectangle in a specific pattern. I was told to use a SwingTimer for animation instead of a Thread.
My plan was to animate a rectangle moving forward until it hits the end of the frame. Then it moves down by one unit(The height of the rectangle, so in my case 30), and then moves backwards; and when it hits the end, there it moves down again and so on.
Now the problem with the SwingTimer is that the whole operation is a continuous loop so the rectangle doesn´t move the way I want it to. In order for this to work, I guess I´ve to start and stop the loop of some methods which is complicated and I don´t know how to do it properly .
So how can I animate the rectangle the way I want it to? Is SwingTimer really the proper way to do such things or are other methods better?
Here´s the code I´ve got so far. I´m aware that it´s not much and that ActionPerformed does a completely different animation.
import javax.swing.JFrame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Test extends javax.swing.JPanel implements ActionListener{
private int x = 0;
private int y = 0;
Timer tm = new Timer(50, this);
public Test() {
initComponents();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new java.awt.Color(102, 102, 102));
g.fillRect(x, y, 30, 30);
tm.start();
}
public void moveForward() {
x = x + 30;
repaint();
System.out.println("(" + x + "|" + y + ")");
}
public void moveBackwards() {
x = x - 30;
repaint();
System.out.println("(" + x + "|" + y + ")");
}
public void moveDown() {
y = y + 30;
repaint();
System.out.println("(" + x + "|" + y + ")");
}
public void moveUp() {
y = y - 30;
repaint();
System.out.println("(" + x + "|" + y + ")");
}
public void actionPerformed(ActionEvent e) {
moveForward();
if (x >= 270){
moveDown();
}
}
public static void main(String[] args) {
Test t = new Test();
JFrame f = new JFrame();
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(t);
f.setVisible(true);
}
What you have done so far looks pretty good. You only need your action method to work properly. For that i would use a class variable called direction:
private boolean direction = true;
Now in your action method you move the rectangle either forward or backwards, depending on direction. And if it hits the end you move the rectangle down and invert direction:
public void actionPerformed(ActionEvent e) {
if (direction){
moveForward();
}
else {
moveBackwards();
}
//Check if it is at the end
if(((x >= 270) && (direction)) || ((x <= 30) && (!direction))) {
moveDown();
direction = !direction;
}
}
The if clause is a little bit complicated, but you can split it up, if you want it to be more readable.
I am making a program which draws circles in the clicked place.
For the first click it draws circle which is not exactly in the same place I have clicked.
But furthermore, for other clicks it just draws circles one on the other. as i find out it is because the coordinates of click won't change.
my main:
public static void main(String[] args)
{
JFrame frame = new JFrame();
//set window size
frame.setSize(1000, 1000);
//set the title
frame.setTitle("Oval Draw");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//add panel to frame and make it visible
MouseComponent component = new MouseComponent();
frame.add(component);
frame.setVisible(true);
}
and the MouseComponent class:
public class MouseComponent extends JPanel implements MouseListener
{
boolean drawPoint = true;
boolean drawLine = false;
boolean drawOval = false;
public MouseComponent()
{
super();
pointX = 0;
pointY = 0;
lineX = 0;
lineY = 0;
addMouseListener(this);
}
int pointX, pointY, lineX, lineY;
int[] ArrayX,ArrayY;
#Override
public void paintComponent(Graphics g)
{
for(int i=0; i<10; i++)
{
super.paintComponent(g);
if(drawLine)
{
int red = (int) (Math.random() * 255);
int green = (int) (Math.random() * 255);
int blue = (int) (Math.random() * 255);
Color startrandomColor = new Color(red, green, blue);
red = (int) (Math.random() * 255);
green = (int) (Math.random() * 255);
blue = (int) (Math.random() * 255);
Color endrandomColor = new Color(red, green, blue);
Graphics2D g2d = (Graphics2D) g;
// this.addMouseListener(this);
GradientPaint gradient = new GradientPaint(70, 70, startrandomColor,
150, 150, endrandomColor);
g2d.setPaint(gradient);
g2d.translate( lineX, lineY);
g2d.fillOval(70, 70, 100, 100);
System.out.print(lineX);
System.out.print(" ");
System.out.print(lineY);
System.out.print(" ");
System.out.print(pointX);
System.out.print(" ");
System.out.print(pointY);
System.out.print(" ");
// repaint();
}
else if(drawPoint)
{
// g.drawOval(pointX-5,pointY-5,10,10);
}
}
}
public void mouseClicked(MouseEvent mouse)
{
if(!drawPoint)
{
pointX = mouse.getX();
pointY = mouse.getY();
drawPoint = true;
}
else if(!drawLine)
{
lineX = mouse.getX();
lineY = mouse.getY();
drawLine = true;
}
repaint();
}
public void mouseEntered(MouseEvent mouse){ }
public void mouseExited(MouseEvent mouse){ }
public void mousePressed(MouseEvent mouse){ }
public void mouseReleased(MouseEvent mouse){ }
}
This here:
if(!drawPoint)
{
pointX = mouse.getX();
pointY = mouse.getY();
drawPoint = true;
}
simply doesn't make too much sense. You init drawPoint to true ... so you will never enter the if block and collect the click coordinates.
And beyond that: when you look into your other method --- the code to draw points is commented out.
Suggestion: step back; don't try to do 5 things at the same time. Write the code it takes to
fetch mouse coordinates after a click
draw circles around that coordinate
and get that to work. Forget about drawing lines, shapes, whatever for now.
And when you have a class that does nothing but that click+draw circles; then create a new class, where you add more features. Your "real" problem right now is that you started working on various features; and that your attempts to "integrate" them into a single class left you with something that is confusing, and not surprising ... not working!
Long story short: forgot about that strange logic that you have in your code right now to "toggle" between drawing points and lines. Draw points. And then, when that works ... add some radio buttons; or a drop down menu and use that to control if you want to draw lines or points. Don't make that "implicit" by toggling booleans in your event handler code!
boolean drawPoint = true;
boolean drawLine = false; cause it to print the first time. After that both are set to true so it will not change coordinates.
Ok so I have been try to get this ball to bounce naturally for a few weeks now and can't seem to get it right. The program should allow the user to input a set amount of gravity and then have the ball bounce according to that amount. It works for the first few bounces but stopping the ball is the problem. I have to deliberately set its movement to 0 or else it will endlessly bounce in place but not move on the x-axis. This issue only happens when the gravity is set to 15, other amount and things really go bad. The ball will bounce naturally but then keep rolling on x-axis forever. I've never taken a physics class so the issue is probably in the physics code. Anyone know where the issue is?
Here is the code my code-
import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.util.Random;
import javax.swing.JOptionPane;
//key stuff
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
public class StartingPoint extends Applet implements Runnable{
//key press
private static boolean wPressed = false;
public static boolean isWPressed() {
synchronized (StartingPoint.class) {
return wPressed;
}
}
//for position of circle
int x = 0;
int y= 0;
//for position change
double dx = 2;
double dy = 2;
//for circle size
int rad = 11;
//image for update()
private Image i;
private Graphics gTwo;
//Physics
double grav = 15;
double engloss= .65;
double tc = .2;
double friction = .9;
#Override
public void init() {
// sets window size
setSize(800,600);
}
#Override
public void start() {
//"this" refers to the implemented run method
Thread threadOne = new Thread(this);
//this goes to run()
threadOne.start();
}
#Override
public void run() {
String input = JOptionPane.showInputDialog( "How much gravity?" );
grav= Double.parseDouble(input);
// sets frame rate
while (true){
//makes sure it doesn't go off screen x wise
//right side
if (x + dx > this.getWidth() - rad -1){
x= this.getWidth() -rad -1; //blocks it from moving past boundary
dx = -dx; //Reveres it
}
//left side
else if (x + dx < 1 + rad){
x= 1+rad; //ball bounces from the center so it adjusts for this by adding one and rad to pad out the radius of the ball plus one pixel.
dx = -dx; //Inverters its movement so it will bounce
}
//makes the ball move
else{
x += dx; // if its not hitting anything it keeps adding dx to x so it will move.
}
//friction
if(y == this.getHeight()-rad -1){
dx *= friction; //every time the ball hits the bottom dx is decreased by 10% by multiplying by .9
//Keeps it from micro bouncing for ever
if (Math.abs(dy) < 4){ // if the speed of y (dy) is less than .4 it is set to 0
dy= 0;
}
/**if (Math.abs(dx) < .00000000000000000001){ // if the speed of x (dx) is less than .00000000000000000001 it is set to 0, this value doesn't seem to matter
dx = 0;
}**/
}
//makes sure it doesn't go off screen y wise
//down
if (y > this.getHeight() - rad -0){ // TODO Check how getHieght is measured.
y= this.getHeight() -rad -0;
//makes ball loose speed.
dy *= engloss;
dy = -dy;
}
else {
//velocity
// tc makes grav smaller. Total of which is added to dy. To increase velocity as the ball goes down.
dy += grav *tc;
//gravity
//new dy is decreased by tc + .5 multiplied by gravity and tc squared. This makes the ball bounce lower every time based on its speed
y += dy*tc + .5*grav*tc*tc;
}
//frame rate
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//end frame rate
}
#Override
public void stop() {
}
#Override
public void destroy() {
}
#Override
public void update(Graphics g) {
//keeps it from flickering... don't know how though
if(i == null){
i = createImage(this.getSize().width, this.getSize().height);
gTwo = i.getGraphics();
}
gTwo.setColor(getBackground());
gTwo.fillRect(0, 0, this.getSize().width, this.getSize().height);
gTwo.setColor(getForeground());
paint(gTwo);
g.drawImage(i, 0, 0, this);
//do some thing with setDoubleBuffered
}
#Override
public void paint(Graphics g) {
g.setColor(Color.BLUE);
g.fillOval(x-rad, y-rad, rad*2, rad*2);
}
}
Oh boy, trigonometry is so hard! I kinda need some help, It's a simple program that is supposed to rotate a ball around the center of the screen... Here is my code:
import java.awt.*;
import javax.swing.*;
public class Window {
private int x;
private int y;
private int R = 30;
private double alpha = 0;
private final int SPEED = 1;
private final Color COLOR = Color.red;
public static void main(String[] args) {
new Window().buildWindow();
}
public void buildWindow() {
JFrame frame = new JFrame("Rotation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
frame.add(new DrawPanel());
while(true) {
try {
Thread.sleep(60);
alpha += SPEED;
frame.repaint();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#SuppressWarnings("serial")
class DrawPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
g.setColor(Color.blue);
Font font = new Font("Arial",Font.PLAIN,12);
g.setFont(font);
g.drawString(String.format("Angle: %.2f ", alpha), 0, 12);
g.setColor(Color.black);
g.drawLine(this.getWidth()/2,0, this.getWidth()/2, this.getHeight());
g.drawLine(0, this.getHeight()/2, this.getWidth(), this.getHeight()/2);
x = (int) ((this.getWidth() / 2 - R / 2 ) + Math.round((R + 20) * Math.sin(alpha)));
y = (int) ((this.getHeight() / 2 - R / 2 ) + Math.round((R + 20) * Math.cos(alpha)));
g.setColor(COLOR);
g.fillOval(x, y, R, R);
}
}
}
This code looks like it's working, but then I've printed Angle[alpha] information to the screen. And when I comment out the alpha+=SPEED and enter the angle manually it does not look like it's working.The angle on the screen doses not correspond to that angle alpha.
So I need suggestions. What should I change? Is my trigonometry wrong? etc...
Three things to note here:
I assume your alpha variable is in degrees since you are adding 20 in each step. However the Math.sin() and Math.cos() methods expect an angle in radians.
Normally 0 deg (or 0 rads) is represented at the "3 o'clock" position. For this you need to switch the sin and cos calls.
Reverse the sign in the y equation to account for the fact that y coordinates start at the top of the screen and increase downwards
With these modifications, your code will work as you expect:
double rads = (alpha * Math.PI) / 180F;
x = (int) ((this.getWidth() / 2 - R / 2 ) + Math.round((R + 20) * Math.cos(rads)));
y = (int) ((this.getHeight() / 2 - R / 2 ) - Math.round((R + 20) * Math.sin(rads)));