I am currently making a tower shooter type game that has a turret at the bottom of the screen. It shoots multiple bullets (or is suppose too, I haven't made it work properly yet) ever few second or so. I have a enemy guy come from the top of the screen and make his way to the bottom unless the bullets collide with the enemy. If it does collide, the y value is set back to to the top of the screen and the enemy comes down again from another x value position. What I am trying to do is make multiple enemies come down from the top, one after the other in different x value positions, but I can't figure out how to make multiple version of the enemy with the collision checking it too.
private Graphics2D graphics;
private Animation mobAnimation = new Animation();
private int mobX = (int) ((Math.random() * 1252) + 28);
private int mobY = -55;
private int bulletX = ((width / 2) - (22 / 2));
private int bulletY = (height - 100);
private Timer mobs = new Timer(200, new MobListener());
private Timer bullet = new Timer(100, new BulletListener());
private BufferedImage MobAnimation;
// Draws the bullets.
public void drawBullets() {
// bulletCollision = new Ellipse2D.Double(bulletX, bulletY, 22, 22);
BufferedImage Bullets = bullets.getSprite(0, 0, 13, 13);
float centerX = (getWidth() / 2);
float centerY = (getHeight() - (75 / 2));
double angle = Math.atan2((centerY - mouse.getMouseY()), (centerX - mouse.getMouseX())) - (Math.PI / 2);
graphics.rotate(angle, (getWidth() / 2), (getHeight() - (75 / 2)));
graphics.drawImage(Bullets, bulletX, bulletY, 22, 22, null);
// graphics.setColor(Color.WHITE);
// graphics.draw(bulletCollision);
graphics.rotate(-angle, (getWidth() / 2), (getHeight() - (75 / 2)));
}
// Part of the bullets.
private class BulletListener implements ActionListener {
public void actionPerformed(ActionEvent bae) {
bulletY -= 20;
if (bulletY <= -350) {
bulletY = (getHeight() - 100);
}
if (bulletCollisionCheck()) {
mobY = -55;
bulletY = getHeight() - 100;
mobX = (int) ((Math.random() * 1252) + 28);
finalScore += 10;
}
}
}
// Draws the mobs.
public void drawMobs() {
// mobCollision = new Ellipse2D.Double(((mobX - (56 / 2)) - 3), (mobY - 56 / 2), 56, 56);
BufferedImage Mob1[] = new BufferedImage[4];
Mob1[0] = mob1.getSprite(0, 0, 32, 35);
Mob1[1] = mob1.getSprite(32, 0, 32, 35);
Mob1[2] = mob1.getSprite(64, 0, 32, 35);
Mob1[3] = mob1.getSprite(32, 0, 32, 35);
for (int i = 0; i < Mob1.length; i++) {
mobAnimation.addFrame(Mob1[i]);
}
graphics.drawImage(MobAnimation, (mobX - (56 / 2)), (mobY - (56 / 2)), 52, 55, null);
// graphics.setColor(Color.WHITE);
// graphics.draw(mobCollision);
}
// Part of the mobs.
private class MobListener implements ActionListener {
public void actionPerformed(ActionEvent mae) {
MobAnimation = mobAnimation.getNextFrame();
mobY += 10;
float dx = (mobX - (getWidth() / 2));
float dy = (mobY - (getHeight() - 2));
float radiusSum = ((56 / 2) + (256 / 2));
float distanceBetweenCircles = (float) Math.sqrt((dx * dx) + (dy * dy));
float distanceToMove = radiusSum - distanceBetweenCircles;
if (mobY >= (getHeight() + 55)) {
mobY = -55;
health -= 10;
}
if (health <= 15) {
gameState = GAME_OVER;
health = 300;
mobs.stop();
scenes.stop();
timer.stop();
mobY -= 55;
}
if (mobCollisionCheck()) {
if (mobX < (getWidth() / 2)) {
mobX -= distanceToMove;
} else if (mobX > (getWidth() / 2)) {
mobX += distanceToMove;
} else if (mobX == (getWidth() / 2)) {
mobX += distanceToMove;
}
}
}
}
This is where it checks for collision:
// Checks the collision of mobs.
public boolean mobCollisionCheck() {
float dx = (mobX - (getWidth() / 2));
float dy = (mobY - (getHeight() - 2));
float radiusSum = ((56 / 2) + (256 / 2));
float distance = (dx * dx) + (dy * dy);
return distance <= radiusSum * radiusSum;
}
// Checks the collision of bullets.
public boolean bulletCollisionCheck() {
float centerX = (getWidth() / 2);
float centerY = (getHeight() - (75 / 2));
double angle = Math.atan2((centerY - mouse.getMouseY()), (centerX - mouse.getMouseX())) - (Math.PI / 2);
float dxB = (bulletX - (getWidth() / 2));
float dyB = (bulletY - (getHeight() - (75 / 2)));
float distanceBetweenCircles = (float) Math.sqrt((dxB * dxB) + (dyB * dyB));
float bulletXB = (float) ((getWidth() / 2) + (distanceBetweenCircles * Math.sin(angle)));
float bulletYB = (float) ((getHeight() - (75 / 2)) - (distanceBetweenCircles * Math.cos(angle)));
float dx = (mobX - (bulletXB + 3));
float dy = (mobY - bulletYB);
float radiusSum = ((56 / 2) + (22 / 2));
float distance = (dx * dx) + (dy * dy);
return distance <= radiusSum * radiusSum;
}
If someone can please help. I have tried creating the mobs in a different class but then I have to change the x value to a random for each of the different enemies which causes a lot of errors that I can't seem to fix.
EDIT:
Forgot to add the animation class.
public class Animation {
private ArrayList<BufferedImage> Frames = new ArrayList<BufferedImage>();
private int currentFrame = 0;
public void addFrame(BufferedImage Frame) {
Frames.add(Frame);
}
public BufferedImage getNextFrame() {
if (currentFrame == Frames.size()) {
currentFrame = 0;
}
return Frames.get(currentFrame++);
}
}
You can create a class representing each enemy and a hitbox for each enemy, an area in which the enemy is in and if the turret projectile enters that area the enemy gets damaged. This could be e.g. a rectangle or a circle and the hitbox's coordinates should update every time the enemy moves.
What you're doing is great in order to get some knowledge on fundamental stuff but if you want to do something really good you should use existing frameworks.
Related
By the code below, a graph is made and is added to a JPanel in another class which contains a bar to show the long graph (which usually has 3000 width):
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;
public class Gf extends JPanel {
private static final long serialVersionUID = 1L;
private int padding = 0;
private int labelPadding = 0;
private Color pointColor = new Color(0, 10, 100);
private int pointWidth = 1;
private int numberYDivisions = 2;
private List<Double> scores;
public Gf(List<Double> scores) {
this.scores = scores;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
double xScale = ((double) getWidth() - (2 * padding) - labelPadding)
/ (scores.size() - 1);
double yScale = ((double) getHeight() - 2 * padding - labelPadding) / 60000;
List<Point> graphPoints = new ArrayList<>();
for (int i = 0; i < scores.size(); i++) {
int x1 = (int) (i * xScale + padding + labelPadding);
int y1 = (int) ((getMaxScore() - scores.get(i)) * yScale + padding);
graphPoints.add(new Point(x1, y1));
}
// draw white background
g2.setColor(Color.BLACK);
g2.fillRect(padding + labelPadding, padding,
getWidth() - (2 * padding) - labelPadding,
getHeight() - 2 * padding - labelPadding);
g2.setColor(Color.BLACK);
// create hatch marks and grid lines for y axis.
for (int i = 0; i < numberYDivisions + 1; i++) {
int x0 = padding + labelPadding;
int x1 = pointWidth + padding + labelPadding;
int y0 = getHeight()
- ((i * (getHeight() - padding * 2 - labelPadding)) / numberYDivisions
+ padding + labelPadding);
int y1 = y0;
if (scores.size() > 0) {
String yLabel = ((int) ((getMinScore()
+ (getMaxScore() - getMinScore())
* ((i * 1.0) / numberYDivisions))
* 100))
/ 100.0
+ "";
FontMetrics metrics = g2.getFontMetrics();
int labelWidth = metrics.stringWidth(yLabel);
g2.drawString(
yLabel, x0 - labelWidth - 5, y0 + (metrics.getHeight() / 2) - 3);
}
g2.drawLine(x0, y0, x1, y1);
}
// and for x axis
for (int i = 0; i < scores.size(); i++) {
if (scores.size() > 1) {
int x0 =
i * (getWidth() - padding * 2 - labelPadding) / (scores.size() - 1)
+ padding + labelPadding;
int x1 = x0;
int y0 = getHeight() - padding - labelPadding;
int y1 = y0 - pointWidth;
if ((i % ((int) ((scores.size() / 20.0)) + 1)) == 0) {
String xLabel = i + "";
FontMetrics metrics = g2.getFontMetrics();
int labelWidth = metrics.stringWidth(xLabel);
g2.drawString(
xLabel, x0 - labelWidth / 2, y0 + metrics.getHeight() + 3);
}
g2.drawLine(x0, y0, x1, y1);
}
}
// create x and y axes
g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding,
padding + labelPadding, padding);
g2.drawLine(padding + labelPadding, getHeight() - padding - labelPadding,
getWidth() - padding, getHeight() - padding - labelPadding);
Stroke oldStroke = g2.getStroke();
g2.setStroke(oldStroke);
g2.setColor(pointColor);
for (int i = 0; i < graphPoints.size(); i++) {
int x = graphPoints.get(i).x - pointWidth / 2;
int y = graphPoints.get(i).y - pointWidth / 2;
int ovalW = pointWidth;
int ovalH = pointWidth;
g2.fillOval(x, y, ovalW, ovalH);
}
}
// #Override
// public Dimension getPreferredSize() {
// return new Dimension(width, heigth);
// }
private double getMinScore() {
double minScore = -30000;
return minScore;
}
private double getMaxScore() {
double maxScore = 30000;
return maxScore;
}
public void setScores(List<Double> scores) {
this.scores = scores;
invalidate();
this.repaint();
}
public List<Double> getScores() {
return scores;
}
public static void createAndShowGui() {
List<Double> scores = new ArrayList<>();
short[] x = mainclass.Omega; // getting required data from main class
for (int i = 0; i < x.length; i++) {
scores.add((double) x[i]);
}
Gf mainPanel = new Gf(scores);
mainPanel.setPreferredSize(new Dimension(
mainclass.range, 276)); // range is calculated at main class and usually
// has more than 3k width
mainclass.p.add(mainPanel); // adding to jpanel at main class
mainclass.f.revalidate();
mainclass.f.repaint();
}
}
The problem is that sometimes gets way slow specially on slower devices when scrolling the bar to check the entire diagram.
So wanted to know if there is a way to save the graph as an image instead of showing at JPanel(so that it can be added to the JPanel later in some way that doesn't affect the performance).
Edit:
I think it's something to do with the way I setBounds()...
A similar project I have (that actually works), involves a moving box that changes color when clicked... A box - not a circle. I think this is a clue.
OG Post:
Subclassed Actor.
Put Actor in a Stage.
Added a ClickListener inside Actor.constructor.
Set Stage as inputProcessor.
But when I click on the Actor, nothing seems to happen.
There is no response - when I try to click the actor.
It's suppose to change color, increase speed and track input x/ys.
It's weird 'cause I built a very similar program that works. So why doesn't this?
The actor is represented by a circle drawn by the ShapeRenderer. I assume it's "click" boundary must be just a rectangle?
HelloGDXGame.java
#Override
public void create() {
SCREEN_WIDTH = Gdx.graphics.getWidth() / 2;
SCREEN_HEIGHT = Gdx.graphics.getHeight() / 2;
rowHeight = 80;
touchPos = new Vector3();
batch = new SpriteBatch();
font = new BitmapFont();
font.setColor(Color.PINK);
font.setScale(4.0f);
ball = new Ball(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 180, 90, this);
stage = new Stage(new StretchViewport(SCREEN_WIDTH, SCREEN_HEIGHT));
stage.addActor(ball);
Gdx.input.setInputProcessor(stage);
}
#Override
public void render()
{
Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Translate inputs x/y
touchPos.x = Gdx.input.getX();
touchPos.y = (Gdx.input.getY() - Gdx.graphics.getHeight()) * -1;
stage.act();
stage.draw();
// UI, to check it all works (it doesn't)
batch.begin();
font.draw(batch, "Ball Class: Taps: " + ball.getTaps(), 64, SCREEN_HEIGHT - (rowHeight * 8));
font.draw(batch, "Main Class: Touch X: " + touchPos.x, 64, SCREEN_HEIGHT - (rowHeight * 7));
font.draw(batch, "Main Class: Touch Y: " + touchPos.y, 64, SCREEN_HEIGHT - (rowHeight * 6));
font.draw(batch, "Ball Class: Touch X: " + ball.getScreenX(), 64, SCREEN_HEIGHT - (rowHeight * 5));
font.draw(batch, "Ball Class: Touch Y: " + ball.getScreenY(), 64, SCREEN_HEIGHT - (rowHeight * 4));
batch.end();
}
Ball.java
public Ball(float xPos, float yPos, float xSpeed, float ySpeed, HelloGdxGame game) {
super();
this.game = game;
renderer = new ShapeRenderer();
taps = 0;
radius = 196;
super.setBounds(xPos - radius, yPos - radius, radius * 2, radius * 2);
// just to check it's working
screenXY = new Vector3(0, 0, 0);
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.setTouchable(Touchable.enabled);
this.addListener(new ClickListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
doStuff(x, y);
return false;
}
});
changeColour(); // sets random color
}
private void doStuff(float screenX, float screenY) {
screenXY.x = screenX;
screenXY.y = screenY;
changeColour();
taps++;
// Increase speed
if (xSpeed > 0) xSpeed++;
else xSpeed--;
if (ySpeed > 0) ySpeed++;
else ySpeed--;
}
#Override
public void act(float delta) {
super.act(delta);
// move
super.setX(super.getX() + (xSpeed * delta));
super.setY(super.getY() + (ySpeed * delta));
// Bounce horizontally
if (super.getX() < 0 || super.getX() + (radius / 2) > Gdx.graphics.getWidth()) {
super.setX(super.getX() - (xSpeed * delta));
xSpeed *= -1;
}
// Bounce vertically
if (super.getY() < 0 || super.getY() + (radius / 2) > Gdx.graphics.getHeight()) {
super.setY(super.getY() - (ySpeed * delta));
ySpeed *= -1;
}
}
#Override
public void draw(Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
renderer.begin(ShapeRenderer.ShapeType.Filled);
renderer.setColor(r, g, b, 1);
renderer.circle(super.getX(), super.getY(), radius);
renderer.end();
}
1) Take a changed Ball from here
2)
Change
SCREEN_WIDTH = Gdx.graphics.getWidth() / 2;
SCREEN_HEIGHT = Gdx.graphics.getHeight() / 2;
To
SCREEN_WIDTH = Gdx.graphics.getWidth();
SCREEN_HEIGHT = Gdx.graphics.getHeight();
This is illogical.
I've started to take interest with making animations(slideshows, backgrounds etc) in Java. I know that JavaFX is much better for doing this, but I'm just to stubborn to bother switching over.
Here is what I got so far.
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BlurredLightCells extends JPanel {
private static final long serialVersionUID = 4610174943257637060L;
private Random random = new Random();
private ArrayList<LightCell> lightcells;
private float[] blurData = new float[500];
public static void main(String[] args) {
JFrame frame = new JFrame("Swing animated bubbles");
frame.setSize(1000, 750);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new BlurredLightCells(60));
frame.setVisible(true);
}
public BlurredLightCells(int amtOfBCells) {
setSize(1000, 750);
/**
* Below we initiate all the cells that are going to be drawn on screen
*/
Arrays.fill(blurData, 1f / 20f);
lightcells = new ArrayList<LightCell>(amtOfBCells);
for (int i = 0; i < amtOfBCells; i++) {
/**
* Below we generate all the values for each cell(SHOULD be random for each one)
*/
int baseSpeed = random(0, 3);
int xSpeed = (int) Math.floor((Math.random() * (baseSpeed - -baseSpeed + baseSpeed)) + -baseSpeed);
int ySpeed = (int) Math.round((Math.random() * baseSpeed) + 0.5);
int radius = random(25, 100);
int x = (int) Math.floor(Math.random() * getWidth());
int y = (int) Math.floor(Math.random() * getHeight());
int blurrAmount = (int) (Math.floor(Math.random() * 10) + 5);
int alpha = (int) ((Math.random() * 15) + 3);
/**
* Now we draw a image, and apply transparency and a slight blur to it
*/
Kernel kernel = new Kernel(blurrAmount, blurrAmount, blurData);
BufferedImageOp op = new ConvolveOp(kernel);
BufferedImage circle = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
Graphics2D circlegfx = circle.createGraphics();
circlegfx.setColor(new Color(255, 255, 255, alpha));
circlegfx.fillOval(20, 20, radius, radius);
circle = op.filter(circle, null);
LightCell bubble = new LightCell(x, y, xSpeed, ySpeed, radius, getDirection(random.nextInt(3)), circle);
lightcells.add(bubble);
}
}
public int random(int min, int max) {
final int n = Math.abs(max - min);
return Math.min(min, max) + (n == 0 ? 0 : random.nextInt(n));
}
#Override
public void paint(Graphics g) {
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
long start = System.currentTimeMillis();
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
System.out.println("Took " + (System.currentTimeMillis() - start) + " milliseconds to draw ALL cells.");
repaint();
}
public String getDirection(int i) {
switch (i) {
case 0:
return "right";
case 1:
return "left";
case 2:
return "up";
case 3:
return "down";
}
return "";
}
private class LightCell {
private int x, y, xSpeed, ySpeed, radius;
private String direction;
private BufferedImage image;
public LightCell(int x, int y, int xSpeed, int ySpeed, int radius, String direction, BufferedImage image) {
this.x = x;
this.y = y;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.radius = radius;
this.direction = direction;
this.image = image;
}
public void process(Graphics g) {
switch (direction) {
case "right":
moveRight();
break;
case "left":
moveLeft();
break;
case "up":
moveUp();
break;
case "down":
moveDown();
break;
}
g.drawImage(image, x, y, null);
}
private void moveUp() {
x += xSpeed;
y -= ySpeed;
if (y + (radius / 2) < 0) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = radius + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveDown() {
x += xSpeed;
y += ySpeed;
if (y - (radius / 2) > getHeight()) {
y = 0 - (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
if ((x + radius / 2) < 0 || (x - radius / 2) > getWidth()) {
y = getHeight() + (radius / 2);
x = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveRight() {
x += ySpeed;
y += xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x - radius / 2) > getWidth()) {
x = 0 - (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
private void moveLeft() {
x -= ySpeed;
y -= xSpeed;
if (y - (radius / 2) > getHeight() || y + (radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getHeight());
}
if ((x + radius / 2) < 0) {
x = getWidth() + (radius / 2);
y = (int) Math.floor(Math.random() * getWidth());
}
}
}
}
If you run that, you will see the cells move at a very high speed, and if you look through the code, you see I call repaint() in the paint method in which I override. I know thats not good to do. But my question is, is their any other way in which I could draw each cell outside of the repaint() loop I have right now, because that causes other components to flash/flicker when I use this in a JFrame with other components.
FYI: Aventually I'd like to achieve something similar to this: Click Here
Thanks!
The issue of flicker is to do with the fact that top level containers are not double buffered. Instead of extending from JFrame (or other top level containers), you should consider using something more like JPanel and override it's paintComponent.
nb- Had it in my head that the OP was extending from JFrame...
Two issues could be causing the flickering. The first is overriding paint, the second is not calling super.paint(g) and the time between the updates. A better solution would be to override paintComponent and make sure you are calling super.paintComponent. Also using something like a javax.swing.Timer to schedule updates and regular intervals would also help...
Only call repaint when you want to encourage the RepaintManager to update you component. Don't call repaint from within any paintXxx method, this will cause a never ending loop of paint requests to schedule onto the event queue, eventually consuiming your CPU
I would avoid doing anything in your paintXxx methods that might take time to perform, this will slow down the rendering process. Instead, I would use a javax.swing.Timer for simple updates or for more complicated processing, a Thread which could be used to update the model before it is rendered to the screen.
This is an example of some simple optimisation process I did to take animation of 500 objects to 4500 with only a slight degration in the overall performance.
Updated
I changed you code slight and it works fine...
I changed your paint method to paintComponent and added super.paintComponent(g)
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
final Graphics2D g2 = (Graphics2D) g;
GradientPaint gp = new GradientPaint(-w, -h, Color.LIGHT_GRAY, w, h, Color.DARK_GRAY);
g2.setPaint(gp);
g2.fillRect(0, 0, w, h);
for (int i = 0; i < lightcells.size(); i++) {
LightCell cell = lightcells.get(i);
cell.process(g2);
}
}
And at the end of your constructor I added...
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
});
timer.start();
To update the UI on a regular bases...
I'm having trouble drawing the smallest arc described by 3 points: the arc center, an "anchored" end point, and a second point that gives the other end of the arc by determining a radius. I used the law of cosines to determine the length of the arc and tried using atan for the starting degree, but the starting position for the arc is off.
I managed to get the arc to lock onto the anchor point (x1,y1) when it's in Quadrant 2, but that will only work when it is in Quadrant 2.
Solutions I can see all have a bunch of if-statements to determine the location of the 2 points relative to each other, but I'm curious if I'm overlooking something simple. Any help would be greatly appreciated.
SSCCE:
import javax.swing.JComponent;
import javax.swing.JFrame;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
class Canvas extends JComponent {
float circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
public Canvas() {
//Setup.
x1 = random.nextInt(250);
y1 = random.nextInt(250);
//Cant have x2 == circleX
while (x1 == 150 || y1 == 150)
{
x1 = random.nextInt(250);
y1 = random.nextInt(250);
}
circleX = 150; //circle center is always dead center.
circleY = 150;
//Radius between the 2 points must be equal.
dx = Math.abs(circleX-x1);
dy = Math.abs(circleY-y1);
//c^2 = a^2 + b^2 to solve for the radius
radius = (float) Math.sqrt((float)Math.pow(dx, 2) + (float)Math.pow(dy, 2));
//2nd random point
x2 = random.nextInt(250);
y2 = random.nextInt(250);
//I need to push it out to radius length, because the radius is equal for both points.
dx2 = Math.abs(circleX-x2);
dy2 = Math.abs(circleY-y2);
radius2 = (float) Math.sqrt((float)Math.pow(dx2, 2) + (float)Math.pow(dy2, 2));
dx2 *= radius/radius2;
dy2 *= radius/radius2;
y2 = circleY+dy2;
x2 = circleX+dx2;
//Radius now equal for both points.
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
Arc2D.Float centerPoint = new Arc2D.Float(150-2,150-2,4,4, 0, 360, Arc2D.OPEN);
Arc2D.Float point1 = new Arc2D.Float(x1-2, y1-2, 4, 4, 0, 360, Arc2D.OPEN);
Arc2D.Float point2 = new Arc2D.Float(x2-2, y2-2, 4, 4, 0, 360, Arc2D.OPEN);
//3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point1);
g.draw(point2);
float start = 0;
float distance;
//Form a right triangle to find the length of the hypotenuse.
distance = (float) Math.sqrt(Math.pow(Math.abs(x2-x1),2) + Math.pow(Math.abs(y2-y1), 2));
//Law of cosines to determine the internal angle between the 2 points.
distance = (float) (Math.acos(((radius*radius) + (radius*radius) - (distance*distance)) / (2*radius*radius)) * 180/Math.PI);
float deltaY = circleY - y1;
float deltaX = circleX - x1;
float deltaY2 = circleY - y2;
float deltaX2 = circleX - x2;
float angleInDegrees = (float) ((float) Math.atan((float) (deltaY / deltaX)) * 180 / Math.PI);
float angleInDegrees2 = (float) ((float) Math.atan((float) (deltaY2 / deltaX2)) * 180 / Math.PI);
start = angleInDegrees;
//Q2 works.
if (x1 < circleX)
{
if (y1 < circleY)
{
start*=-1;
start+=180;
} else if (y2 > circleX) {
start+=180;
start+=distance;
}
}
//System.out.println("Start: " + start);
//Arc drawn in blue
g.setColor(Color.BLUE);
Arc2D.Float arc = new Arc2D.Float(circleX-radius, //Center x
circleY-radius, //Center y Rotates around this point.
radius*2,
radius*2,
start, //start degree
distance, //distance to travel
Arc2D.OPEN); //Type of arc.
g.draw(arc);
}
}
public class Angle implements MouseListener {
Canvas view;
JFrame window;
public Angle() {
window = new JFrame();
view = new Canvas();
view.addMouseListener(this);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBounds(30, 30, 400, 400);
window.getContentPane().add(view);
window.setVisible(true);
}
public static void main(String[] a) {
new Angle();
}
#Override
public void mouseClicked(MouseEvent arg0) {
window.getContentPane().remove(view);
view = new Canvas();
window.getContentPane().add(view);
view.addMouseListener(this);
view.revalidate();
view.repaint();
}
#Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub
}
#Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
}
}
Perhaps this will help. It tests with click and drag to set the two points rather than random numbers. It's considerably simpler than what you were attempting and other solutions posted so far.
Notes:
Math.atan2() is a friend in problems like this.
Little helper functions make it easier to reason about your code.
It's best practice to use instance variables for independent values only and compute the dependent values in local variables.
My code fixes some Swing usage problems like calling Swing functions from the main thread.
Code follows:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
class TestCanvas extends JComponent {
float x0 = 150f, y0 = 150f; // Arc center. Subscript 0 used for center throughout.
float xa = 200f, ya = 150f; // Arc anchor point. Subscript a for anchor.
float xd = 150f, yd = 50f; // Point determining arc angle. Subscript d for determiner.
// Return the distance from any point to the arc center.
float dist0(float x, float y) {
return (float)Math.sqrt(sqr(x - x0) + sqr(y - y0));
}
// Return polar angle of any point relative to arc center.
float angle0(float x, float y) {
return (float)Math.toDegrees(Math.atan2(y0 - y, x - x0));
}
#Override
protected void paintComponent(Graphics g0) {
Graphics2D g = (Graphics2D) g0;
// Can always draw the center point.
dot(g, x0, y0);
// Get radii of anchor and det point.
float ra = dist0(xa, ya);
float rd = dist0(xd, yd);
// If either is zero there's nothing else to draw.
if (ra == 0 || rd == 0) { return; }
// Get the angles from center to points.
float aa = angle0(xa, ya);
float ad = angle0(xd, yd); // (xb, yb) would work fine, too.
// Draw the arc and other dots.
g.draw(new Arc2D.Float(x0 - ra, y0 - ra, // box upper left
2 * ra, 2 * ra, // box width and height
aa, angleDiff(aa, ad), // angle start, extent
Arc2D.OPEN));
dot(g, xa, ya);
// Use similar triangles to get the second dot location.
float xb = x0 + (xd - x0) * ra / rd;
float yb = y0 + (yd - y0) * ra / rd;
dot(g, xb, yb);
}
// Some helper functions.
// Draw a small dot with the current color.
static void dot(Graphics2D g, float x, float y) {
final int rad = 2;
g.fill(new Ellipse2D.Float(x - rad, y - rad, 2 * rad, 2 * rad));
}
// Return the square of a float.
static float sqr(float x) { return x * x; }
// Find the angular difference between a and b, -180 <= diff < 180.
static float angleDiff(float a, float b) {
float d = b - a;
while (d >= 180f) { d -= 360f; }
while (d < -180f) { d += 360f; }
return d;
}
// Construct a test canvas with mouse handling.
TestCanvas() {
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
}
// Listener changes arc parameters with click and drag.
MouseInputAdapter mouseListener = new MouseInputAdapter() {
boolean mouseDown = false; // Is left mouse button down?
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = true;
xa = xd = e.getX();
ya = yd = e.getY();
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
mouseDown = false;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (mouseDown) {
xd = e.getX();
yd = e.getY();
repaint();
}
}
};
}
public class Test extends JFrame {
public Test() {
setSize(400, 400);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().add(new TestCanvas());
}
public static void main(String[] args) {
// Swing code must run in the UI thread, so
// must invoke setVisible rather than just calling it.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test().setVisible(true);
}
});
}
}
package curve;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Main
{
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException
{
PointF pFrom = new PointF(-10f, 30.0f);
PointF pTo = new PointF(-100f, 0.0f);
List<PointF> points = generateCurve(pFrom, pTo, 100f, 7f, true, true);
System.out.println(points);
// Calculate the bounds of the curve
Rectangle2D.Float bounds = new Rectangle2D.Float(points.get(0).x, points.get(0).y, 0, 0);
for (int i = 1; i < points.size(); ++i) {
bounds.add(points.get(i).x, points.get(i).y);
}
bounds.add(pFrom.x, pFrom.y);
bounds.add(pTo.x, pTo.y);
BufferedImage img = new BufferedImage((int) (bounds.width - bounds.x + 50), (int) (bounds.height - bounds.y + 50), BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.translate(25.0f - bounds.getX(), 25.0f - bounds.getY());
g.setStroke(new BasicStroke(1.0f));
g.setColor(Color.DARK_GRAY);
g.drawLine(-1000, 0, 1000, 0);
g.drawLine(0, -1000, 0, 1000);
g.setColor(Color.RED);
for (int i = 0; i < points.size(); ++i) {
if (i > 0) {
Line2D.Float f = new Line2D.Float(points.get(i - 1).x, points.get(i - 1).y, points.get(i).x, points.get(i).y);
System.out.println("Dist : " + f.getP1().distance(f.getP2()));
// g.draw(f);
}
g.fill(new Ellipse2D.Float(points.get(i).x - 0.8f, points.get(i).y - 0.8f, 1.6f, 1.6f));
}
g.setColor(Color.BLUE);
g.fill(new Ellipse2D.Float(pFrom.x - 1, pFrom.y - 1, 3, 3));
g.fill(new Ellipse2D.Float(pTo.x - 1, pTo.y - 1, 3, 3));
g.dispose();
ImageIO.write(img, "PNG", new File("result.png"));
}
static class PointF
{
public float x, y;
public PointF(float x, float y)
{
this.x = x;
this.y = y;
}
#Override
public String toString()
{
return "(" + x + "," + y + ")";
}
}
private static List<PointF> generateCurve(PointF pFrom, PointF pTo, float pRadius, float pMinDistance, boolean shortest, boolean side)
{
List<PointF> pOutPut = new ArrayList<PointF>();
// Calculate the middle of the two given points.
PointF mPoint = new PointF(pFrom.x + pTo.x, pFrom.y + pTo.y);
mPoint.x /= 2.0f;
mPoint.y /= 2.0f;
System.out.println("Middle Between From and To = " + mPoint);
// Calculate the distance between the two points
float xDiff = pTo.x - pFrom.x;
float yDiff = pTo.y - pFrom.y;
float distance = (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
System.out.println("Distance between From and To = " + distance);
if (pRadius * 2.0f < distance) {
throw new IllegalArgumentException("The radius is too small! The given points wont fall on the circle.");
}
// Calculate the middle of the expected curve.
float factor = (float) Math.sqrt((pRadius * pRadius) / ((pTo.x - pFrom.x) * (pTo.x - pFrom.x) + (pTo.y - pFrom.y) * (pTo.y - pFrom.y)) - 0.25f);
PointF circleMiddlePoint = new PointF(0, 0);
if (side) {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) + factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) + factor * (pFrom.x - pTo.x);
} else {
circleMiddlePoint.x = 0.5f * (pFrom.x + pTo.x) - factor * (pTo.y - pFrom.y);
circleMiddlePoint.y = 0.5f * (pFrom.y + pTo.y) - factor * (pFrom.x - pTo.x);
}
System.out.println("Middle = " + circleMiddlePoint);
// Calculate the two reference angles
float angle1 = (float) Math.atan2(pFrom.y - circleMiddlePoint.y, pFrom.x - circleMiddlePoint.x);
float angle2 = (float) Math.atan2(pTo.y - circleMiddlePoint.y, pTo.x - circleMiddlePoint.x);
// Calculate the step.
float step = pMinDistance / pRadius;
System.out.println("Step = " + step);
// Swap them if needed
if (angle1 > angle2) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
}
boolean flipped = false;
if (!shortest) {
if (angle2 - angle1 < Math.PI) {
float temp = angle1;
angle1 = angle2;
angle2 = temp;
angle2 += Math.PI * 2.0f;
flipped = true;
}
}
for (float f = angle1; f < angle2; f += step) {
PointF p = new PointF((float) Math.cos(f) * pRadius + circleMiddlePoint.x, (float) Math.sin(f) * pRadius + circleMiddlePoint.y);
pOutPut.add(p);
}
if (flipped ^ side) {
pOutPut.add(pFrom);
} else {
pOutPut.add(pTo);
}
return pOutPut;
}
}
and the use the generateCurve method like this to have a curve between the from and to points..
generateCurve(pFrom, pTo, 100f, 7f, true, false);
Okay, here it is, testing and working. The problems were based on the fact that I don't use graphics much, so I have to remind myself that the coordinate systems are backward, and on the fact that the Javadoc description of the Arc2D constructor is atrocious.
In addition to these, I found that your point creation (for the two points to be connected) was extremely inefficient given the requirements. I had assumed you actually had to receive two arbitrary points and then calculate their angles, etc., but based on what you put on Pastebin, we can define the two points however we please. This benefits us.
Anyway, here's a working version, with none of that gobbledegook from before. Simplified code is simplified:
import javax.swing.JComponent;
import java.awt.geom.*;
import java.awt.*;
import java.util.*;
public class Canvas extends JComponent {
double circleX, circleY, x1, y1, x2, y2, dx, dy, dx2, dy2, radius, radius2;
Random random = new Random();
double distance;
private static double theta1;
private static double theta2;
private static double theta;
// private static double radius;
private Point2D point1;
private Point2D point2;
private Point2D center;
private static int direction;
private static final int CW = -1;
private static final int CCW = 1;
public Canvas() {
/*
* You want two random points on a circle, so let's start correctly,
* by setting a random *radius*, and then two random *angles*.
*
* This has the added benefit of giving us the angles without having to calculate them
*/
radius = random.nextInt(175); //your maximum radius is higher, but we only have 200 pixels in each cardinal direction
theta1 = random.nextInt(360); //angle to first point (absolute measurement)
theta2 = random.nextInt(360); //angle to second point
//build the points
center = new Point2D.Double(200, 200); //your frame is actually 400 pixels on a side
point1 = new Point2D.Double(radius * Math.cos(toRadians(theta1)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta1)));
point2 = new Point2D.Double(radius * Math.cos(toRadians(theta2)) + center.getX(), center.getY() - radius * Math.sin(toRadians(theta2)));
theta = Math.abs(theta1 - theta2) <= 180 ? Math.abs(theta1 - theta2) : 360 - (Math.abs(theta1 - theta2));
if ((theta1 + theta) % 360 == theta2) {
direction = CCW;
} else {
direction = CW;
}
System.out.println("theta1: " + theta1 + "; theta2: " + theta2 + "; theta: " + theta + "; direction: " + (direction == CCW ? "CCW" : "CW"));
System.out.println("point1: (" + (point1.getX() - center.getX()) + ", " + (center.getY() - point1.getY()) + ")");
System.out.println("point2: (" + (point2.getX() - center.getX()) + ", " + (center.getY() - point2.getY()) + ")");
// Radius now equal for both points.
}
public double toRadians(double angle) {
return angle * Math.PI / 180;
}
public double toDegrees(double angle) {
return angle * 180 / Math.PI;
}
public void paintComponent(Graphics g2) {
Graphics2D g = (Graphics2D) g2;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL));
//centerpoint should be based on the actual center point
Arc2D.Double centerPoint = new Arc2D.Double(center.getX() - 2, center.getY() - 2, 4, 4, 0,
360, Arc2D.OPEN);
//likewise these points
Arc2D.Double point11 = new Arc2D.Double(point1.getX() - 2, point1.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
Arc2D.Double point22 = new Arc2D.Double(point2.getX() - 2, point2.getY() - 2, 4, 4, 0, 360,
Arc2D.OPEN);
// 3 points drawn in black
g.setColor(Color.BLACK);
g.draw(centerPoint);
g.draw(point11);
g.draw(point22);
// Arc drawn in blue
g.setColor(Color.BLUE);
g.draw(new Arc2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius, theta1, theta * direction, Arc2D.OPEN));
}
}
The following code is an animation of a clock.
What changes would have to be made in the program to make it not flicker?
Why does the field initiation have to take place in the addNotify() function for the hands to be displayed?
You can copy all of it into one .java file and it should work (flicker ;) ).
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class ac extends Frame implements Runnable {
// for double buffering
Graphics og;
Image oi;
Thread t = null;
int width, height;
int timeH, timeM, timeS, radius = 50, lenH, lenM, lenS,
lenIn, cx, cy, x1, y1, x2, y2;
private boolean timeToQuit;
ac() {
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
stopRunning();
dispose();
}
});
setMinimumSize(new Dimension(300, 300));
EventQueue.invokeLater(new Runnable() {
public void run() {
setVisible(true);
}
});
}
public void addNotify() {
super.addNotify();
width = getBounds().width;
height = getBounds().height;
setSize(width, height);
lenH = 5 * radius / 10;
lenM = 6 * radius / 10;
lenS = 7 * radius / 10;
lenIn = 8 * radius / 10;
cx = width / 2;
cy = height / 2;
// for double buffering
oi = createImage(width, height);
og = oi.getGraphics();
}
public static void main(String [] args) {
ac clock = new ac();
clock.start();
}
public void start() {
if (t == null) {
t = new Thread(this);
t.start();
}
}
public void stopRunning() {
timeToQuit = true;
}
public void run() {
Thread.currentThread().setPriority(Thread.NORM_PRIORITY - 1);
do {
repaint();
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
}
}
while (!timeToQuit);
System.out.println("Quitting");
}
public void paint(Graphics g) {
og.setColor(new Color(170, 170, 170));
og.fillRect(0, 0, width, height);
Calendar cal = new GregorianCalendar();
timeH = cal.get(Calendar.HOUR);
timeM = cal.get(Calendar.MINUTE);
timeS = cal.get(Calendar.SECOND);
if (timeH >= 12)
timeH -= 12;
for (int i = 1 ; i < 13 ; i++) {
og.setColor(new Color(20, 20, 20));
x2 = (int) (cx + radius * Math.sin(i * 2 * 3.14159f / 12));
y2 = (int) (cy - radius * Math.cos(i * 2 * 3.14159f / 12));
if (i % 3 != 0) {
x1 = (int) (cx + 0.9f * radius * Math.sin(i * 2 * 3.14159f / 12));
y1 = (int) (cy - 0.9f * radius * Math.cos(i * 2 * 3.14159f / 12));
}
else {
x1 = (int) (cx + 0.8f * radius * Math.sin(i * 2 * 3.14159f / 12));
y1 = (int) (cy - 0.8f * radius * Math.cos(i * 2 * 3.14159f / 12));
}
og.drawLine(x1, y1, x2, y2);
og.setColor(new Color(230, 230, 230));
og.drawLine(x1 - 1, y1 - 1, x2 - 1, y2 - 1);
}
og.setColor(new Color(20, 20, 20));
x2 = (int) (cx + lenH * Math.sin((timeH + timeM / 60.0f + timeS / 3600.0f)
* 2 * 3.14159f / 12));
y2 = (int) (cy - lenH * Math.cos((timeH + timeM / 60.0f + timeS / 3600.0f)
* 2 * 3.14159f / 12));
og.drawLine(cx, cy, x2, y2);
og.setColor(Color.red);
og.drawLine(cx - 1, cy - 1, x2 - 1, y2 - 1);
og.setColor(new Color(20, 20, 20));
x2 = (int) (cx + lenM * Math.sin((timeM + timeS / 60.0f) * 2 * 3.14159f
/ 60));
y2 = (int) (cy - lenM * Math.cos((timeM + timeS / 60.0f) * 2 * 3.14159f
/ 60));
og.drawLine(cx, cy, x2, y2);
og.setColor(Color.green);
og.drawLine(cx - 1, cy - 1, x2 - 1, y2 - 1);
og.setColor(new Color(20, 20, 20));
x2 = (int) (cx + lenS * Math.sin(timeS * 2 * 3.14159f / 60));
y2 = (int) (cy - lenS * Math.cos(timeS * 2 * 3.14159f / 60));
og.drawLine(cx, cy, x2, y2);
og.setColor(Color.blue);
og.drawLine(cx - 1, cy - 1, x2 - 1, y2 - 1);
og.setColor(new Color(20, 20, 20));
og.drawOval((width - 2 * radius) / 2, (height - 2 * radius) / 2,
2 * radius, 2 * radius);
og.setColor(new Color(230, 230, 230));
og.drawOval((width - 2 * radius) / 2 - 1, (height - 2 * radius) / 2 - 1,
2 * radius, 2 * radius);
g.drawImage(oi, 0, 0, this);
}
}
This uses awt which is old and it paints on to the main Frame - not the best way to make a swing UI. You can try this :
make the timer - sleep amount to less or more, try 300 or 800
use a Panel to draw on, add the panel to the frame
use swing (use a JComponent, it double buffers automatically, and a JFrame
For 2 and 3 you can keep original class to make Frame/JFrame and a new class for the java.awt.Panel or javax.swing.JComponent which is added to the former.
Does not flicker on my mac system, so i think its an OS + times its refreshed issue. Meaning suggestion 1 might work - try that first. Talking about line of code :
Thread.sleep(800);
Believe it or not, the problem was solved by adding the function:
public void update(Graphics g)
{
paint(g);
}
But the question remains, why? And why aren't the hands displayed when you initiate the class fields outside the addNotify() function?