For some reason, every time I jump in my the game I'm making, the jump gets shorter than the previous jump. Jumps start long and majestic (my game is set in space) and after about 10 jumps, my character is literally twitching against the ground because the jump is practically less than one pixel in height. I honestly cannot find out what is wrong with it, but I feel like it has something to do with the way I find deltaTime. Please help. I'm usually able to solve my own problems with a bit of troubleshooting and/or a bit of Googling, but I honestly don't know what's wrong and it all looks logical to me.
Sorry for the lack of comments. As you can probably tell from my nasty styles of implementing, this is kinda just a quick-write project so I don't really care much to look at the code later. I'm making this mainly to learn and practice Java.
I know there are a lot of out of class references so if you need to see one (I believe I included the important ones), just let me know.
Player Class (which extends Entity):
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import main.Main;
import scenes.Scene;
import threads.Time;
public class Player extends Entity {
private Scene scene;
private int HorizontalAxis = 0;
public float speed = 0.5f;
public float gravity = 0.001f;
public float jumpSpeed = 1f;
private float moveVelX = 0, moveVelY = 0;
public Player(String name, String tag, int x, int y, int w, int h, String spritePath, Scene scene) {
super(name, tag, x, y, w, h, spritePath);
this.scene = scene;
}
public void Update() {
//System.out.println(isGrounded());
if (Main.keyInput.getKeyState("MoveLeft")) {
HorizontalAxis = -1;
}
if (Main.keyInput.getKeyState("MoveRight")) {
HorizontalAxis = 1;
}
if (Main.keyInput.getKeyState("MoveLeft") == Main.keyInput.getKeyState("MoveRight")) {
HorizontalAxis = 0;
}
moveVelX = (HorizontalAxis * speed);
if (isGrounded()) {
moveVelY = 0;
if (Main.keyInput.getKeyState("Jump") || Main.keyInput.getKeyState("JumpAlt")) {
moveVelY = -jumpSpeed;
}
} else {
moveVelY += gravity * Time.deltaTime.getSeconds();
}
setTrueX(getTrueX() + moveVelX);
setTrueY(getTrueY() + moveVelY);
System.out.println(moveVelY);
}
public void render(Graphics2D g) {
g.drawImage(getSprite(), getX(), getY(), getWidth(), getHeight(), Main.display);
}
public boolean isGrounded() {
ArrayList<Entity> groundEntities = scene.FindEntitiesWithTag("Ground");
if (groundEntities.size() > 0) {
for (int i = 0; i < groundEntities.size(); i++) {
if (this.hitbox.intersects(groundEntities.get(i).hitbox)) {
return true;
}
}
return false;
} else {
System.err.println("There is no ground in the scene!");
return false;
}
}
}
Entity Class:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import main.Main;
public class Entity {
public static enum AutoDrawTypes { NONE, RECTANGLE, RECTANGLE_ROUND, OVAL };
public static AutoDrawTypes autoDrawType = Entity.AutoDrawTypes.NONE;
public String name;
public String tag;
protected float x, y;
protected int arcWidth, arcHeight;
protected Rectangle hitbox = new Rectangle();
protected Image sprite;
protected Color color;
public Entity(String tag, String name, int x, int y, int w, int h, String spritePath) {
this.name = name;
this.tag = tag;
this.x = x;
this.y = y;
hitbox.setBounds((int)(getTrueX() - Camera.getX()), (int)(getTrueY() - Camera.getY()), w, h);
setSprite(spritePath);
this.autoDrawType = Entity.AutoDrawTypes.NONE;
}
public Entity(String tag, String name, int x, int y, int w, int h) {
this.name = name;
this.tag = tag;
this.x = x;
this.y = y;
hitbox.setBounds((int)(getTrueX() - Camera.getX()), (int)(getTrueY() - Camera.getY()), w, h);
this.autoDrawType = Entity.AutoDrawTypes.NONE;
}
public Entity(String tag, String name, int x, int y, int w, int h, Entity.AutoDrawTypes autoDrawType, Color color) {
this.name = name;
this.tag = tag;
this.x = x;
this.y = y;
hitbox.setBounds((int)(getTrueX() - Camera.getX()), (int)(getTrueY() - Camera.getY()), w, h);
this.autoDrawType = autoDrawType;
this.color = color;
}
public Entity(String tag, String name, int x, int y, int w, int h, Entity.AutoDrawTypes autoDrawType, Color color, int arcWidth, int arcHeight) {
this.name = name;
this.tag = tag;
this.x = x;
this.y = y;
hitbox.setBounds((int)(getTrueX() - Camera.getX()), (int)(getTrueY() - Camera.getY()), w, h);
this.autoDrawType = autoDrawType;
this.color = color;
this.arcWidth = arcWidth;
this.arcHeight = arcHeight;
}
public void UpdatePositionRelativeToCamera() {
hitbox.setBounds((int)(getTrueX() - Camera.getX()), (int)(getTrueY() - Camera.getY()), getWidth(), getHeight());
}
public Entity() {
}
public void Update() {
}
public void render(Graphics2D g) {
g.setColor(color);
if (autoDrawType == Entity.AutoDrawTypes.RECTANGLE) {
g.fillRect(getX(), getY(), getWidth(), getHeight());
}
if (autoDrawType == Entity.AutoDrawTypes.RECTANGLE_ROUND) {
g.fillRoundRect(getX(), getY(), getWidth(), getHeight(), arcWidth, arcHeight);
}
if (autoDrawType == Entity.AutoDrawTypes.OVAL) {
g.fillOval(getX(), getY(), getWidth(), getHeight());
}
}
public void setTrueX(float x) {this.x = x;}
public void setTrueY(float y) {this.y = y;}
public void setX(int x) {hitbox.x = x;}
public void setY(int y) {hitbox.y = y;}
public void setWidth(int width) {hitbox.width = width;}
public void setHeight(int height) {hitbox.height = height;}
public void setSprite(String path) {
Toolkit tk = Toolkit.getDefaultToolkit();
if (tk == null) {
System.err.println("Default Toolkit could not be fetched.");
return;
}
sprite = tk.getImage(Main.class.getResource(path));
if (sprite == null) {
System.err.println("Image not found at + '" + path + "'. Check path in resources folder.");
return;
}
}
public float getTrueX() {return this.x;}
public float getTrueY() {return this.y;}
public int getX() {return hitbox.x;}
public int getY() {return hitbox.y;}
public int getWidth() {return hitbox.width;}
public int getHeight() {return hitbox.height;}
public Image getSprite() {return sprite;}
}
Timing Class (which is run as a thread at the start of the application):
import java.time.Duration;
import java.time.Instant;
public class Time implements Runnable {
public static boolean running = false;
public static Duration deltaTime = Duration.ZERO;
public static Instant beginTime = Instant.now();
public void run() {
while (running) {
deltaTime = Duration.between(beginTime, Instant.now());
}
}
}
Nevermind, I fixed it. It was the way I calculated deltaTime. I decided just to remove deltaTime and reduce the gravity (even though it's already insanely small lol). That fixed the weird "smaller jumps over time" bug thing.
Take a look at this code for causing the player to fall down:
moveVelY += gravity * Time.deltaTime.getSeconds();
Notice that this causes the player's y velocity to increase by an amount that increases as a function of time. That is, it's as if gravity is constantly increasing as time passes. As a result, if you jump early on, it's like jumping on the moon, and if you jump later it's like jumping on Jupiter or the surface of a neutron star.
To fix this, remove the dependency on the current time. Just increase the y velocity by the gravity term, which should probably just be a fixed constant unless you're doing something cool with the world physics.
Related
I'm making custom components for my game and I tried everything I found in Stack Overflow and no matter what I still can't place text in the center of a rectangle. I even read all the documentation of working with java text API.
Can anyone explain to me how to align text to the center (Center of rect or frame or anything) in java swing once and for all? This is not a duplicate question because in all the other questions on Stack Overflow I did not get a solution that works.
So far I have used FontMetrics and I measured the width using stringWidth() method and the height using ascent (None of them are accurate).
package com.isi.uicomponents;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import com.isi.core.Game;
import com.isi.states.GameState;
public class Button extends UIComponent {
private Font font;
private String text;
public Button(Game game, GameState state, int x, int y, int width, int height, String text) {
super(game, state, x, y, width, height);
font = new Font("Arial", Font.BOLD, 20);
this.text = text;
}
public Button(Game game, GameState state, int x, int y, int width, int height) {
super(game, state, x, y, width, height);
text = null;
}
public String getText() {
return text;
}
#Override
public void tick() {
}
#Override
public void draw(Graphics2D g) {
g.setColor(fillColor);
g.fillRect(x, y, width, height);
g.setColor(boundsColor);
g.draw(bounds);
if (text != null) {
FontMetrics fm = g.getFontMetrics();
int textX = x + (width / 2) - (fm.stringWidth(text) / 2);
int textY = y + ((height - fm.getHeight()) / 2) + fm.getAscent();
g.setFont(font);
g.setColor(Color.white);
g.drawString(text, textX, textY);
}
}
}
// =============================================
package com.isi.uicomponents;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import com.isi.core.Game;
import com.isi.states.GameState;
public abstract class UIComponent {
public final static Color DEFAULT_BOUNDS_COLOR = Color.white;
public final static Color DEFAULT_FILL_COLOR = Color.gray;
protected Game game;
protected GameState state;
protected int x;
protected int y;
protected int width;
protected int height;
protected Rectangle bounds;
protected Color boundsColor;
protected Color fillColor;
public UIComponent(Game game, GameState state, int x, int y, int width, int height) {
this.game = game;
this.state = state;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
bounds = new Rectangle(x, y, width, height);
boundsColor = DEFAULT_BOUNDS_COLOR;
fillColor = DEFAULT_FILL_COLOR;
}
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;
}
public Rectangle getBounds() {
return bounds;
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public Color getBoundsColor() {
return boundsColor;
}
public void setBoundsColor(Color boundsColor) {
this.boundsColor = boundsColor;
}
public Color getFillColor() {
return fillColor;
}
public void setFillColor(Color fillColor) {
this.fillColor = fillColor;
}
public abstract void tick();
public abstract void draw(Graphics2D g);
}
// =============================================
package com.isi.states;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import com.isi.core.Game;
import com.isi.tools.ImageLoader;
import com.isi.uicomponents.Button;
import com.isi.uicomponents.UIComponent;
public class MainMenuState extends GameState {
private static BufferedImage bg = ImageLoader.load("Main Menu Background.jpg");
// Background coordinates for animation
private int x;
private int y;
// MainMenu components array
private ArrayList<UIComponent> components;
public MainMenuState(Game game) {
super(game);
x = 0;
y = 0;
components = new ArrayList<UIComponent>();
components.add(new Button(game, this, game.getWidth() / 2 - 80 / 2, game.getHeight() / 2 - 50 / 2, 80, 50, "Play"));
}
public ArrayList<UIComponent> getComponents() {
return components;
}
public void tick() {
y = y >= game.getHeight() ? 0 : y + 2;
}
public void draw(Graphics2D g) {
g.drawImage(bg, 0, -game.getHeight() + y, game.getWidth(), game.getHeight(), null);
g.drawImage(bg, x, y, game.getWidth(), game.getHeight(), null);
for (int i = 0; i < components.size(); i++) {
components.get(i).draw(g);
}
}
}
This is close, you need to increase x by half the width and then reduce by half the string width. You also need to set the font before you get the font metrics otherwise you're getting the metrics of the existing Graphics font.
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
int textX = x + (width / 2) - (fm.stringWidth(text) / 2);
int textY = y + ((height - fm.getHeight()) / 2) + fm.getAscent();
g.setColor(Color.white);
g.drawString(text, textX, textY);
In my program I'm adding a JPannel that I'm using as canvas to a JFrame. The problem is that the size of the JPannel#setSize() is not what it is displayed.
I first had a problem more or less like this one and I asked here
Java questions about coordinates with Graphics
They toldme to add the JPannel and use the pack() method and it worked but I can't get it to work now because the JFrame is larger than the JPannel where I am drawing.
I have look at some threads to see if I coud fiand an answer but I wasn't succesfull. Here are the threads I looked:
Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Why the JProgressBar doesn't respect the setSize()?
Component setSize method in FlowLayout object
setSize() not working?
setSize not influencing size of button
Why does the JFrame setSize() method not set the size correctly?
java.AWT - setSize() method
Java JPannel not Visible
I create a JFrame with setSize(600, 400); and I add the pane. Here is the code
import java.awt.Dimension;
import javax.swing.JFrame;
import me.nemo_64.particlessimulation.util.Canvas;
public class Frame extends JFrame {
private Frame(String title) {
super(title);
setResizable(false);
setSize(600, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setLayout(null);
init();
}
private void init() {
canvas = new Canvas();
canvas.setSize(400, 400);
canvas.setLocation(0, 0);
canvas.setPreferredSize(new Dimension(400, 400));
canvas.startDrawing();
add(canvas);
}
private Canvas canvas;
public static Frame getInstance() {
if (instance == null)
throw new IllegalAccessError("instanceFrame must be callen at least ones before this method");
return instance;
}
public static Frame instanceFrame(String title) {
if (instance != null)
return getInstance();
instance = new Frame(title);
return getInstance();
}
private static Frame instance;
private static final long serialVersionUID = 1L;
}
And here is the canvas class
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
public class Canvas extends ACanvas {
Vector p, dir;
float v;
public Canvas() {
p = new Vector(0, getHeight() / 2);
dir = new Vector(50f, 50f);
v = 50;
}
#Override
public void update(float delta) {
if (p.x <= 0 && dir.x < 0)
dir.x *= -1;
if (p.y <= 0 && dir.y < 0)
dir.y *= -1;
if (p.x + 100 >= getWidth() && dir.x > 0)
dir.x *= -1;
if (p.y + 100 >= getWidth() && dir.y > 0)
dir.y *= -1;
Vector a = dir.clone().multiply(delta);
p.add(a);
}
#Override
public void mouseClicked(MouseEvent e) {
System.out.println(e.getX() + " " + e.getY());
}
#Override
public void draw(Graphics2D g) {
System.out.println(g);
fill(255, 0, 0);
fillRectangle(p.x, p.y, 100, 100);
}
}
And here is the ACanvas class
package me.nemo_64.particlessimulation.util;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;
public abstract class ACanvas extends JPanel
implements KeyListener, MouseInputListener, MouseWheelListener, Drawable, Updateable, FigureDrawer {
private Graphics2D g;
private Thread drawThread;
private Color backgroundColor;
private Color actualColor;
private long lastTime = 0;
private float delta = 0;
public ACanvas() {
setFocusable(true);
addKeyListener(this);
addMouseListener(this);
addMouseWheelListener(this);
lastTime = System.currentTimeMillis();
drawThread = new Thread(() -> {
while (true)
repaint();
}, "Drawing thread");
}
#Override
/**
* Used to update all the components to be drawn
*/
public abstract void update(float delta);
/**
* Draws all the comsponents
*/
public void draw(Graphics2D g) {}
#Override
public Graphics2D getGraphics2D() {
return this.g;
}
#Override
/**
* Draws all the comsponents
*/
public void draw(Graphics g) {
this.g = (Graphics2D) g;
draw(this.g);
}
#Override
public void paint(Graphics g) {
super.paint(g);
delta = (System.currentTimeMillis() - lastTime) * 0.001f;
lastTime = System.currentTimeMillis();
clearBackground(g);
update(delta);
g.setColor(actualColor);
draw(g);
}
public void clearBackground(Graphics g) {
g.setColor(backgroundColor);
g.fillRect(0, 0, getWidth(), getHeight());
}
public void startDrawing() {
if (!drawThread.isAlive())
drawThread.start();
}
public void stopDrawing() {
if (drawThread.isAlive())
drawThread.interrupt();
}
public void background(Color c) {
backgroundColor = c;
}
public void background(int c) {
backgroundColor = new Color(c);
}
public void background(int r, int g, int b) {
backgroundColor = new Color(r, g, b);
}
public void background(float r, float g, float b) {
backgroundColor = new Color(r, g, b);
}
public void fill(Color c) {
actualColor = c;
g.setColor(c);
}
public void fill(int c) {
fill(new Color(c));
}
public void fill(float r, float g, float b) {
fill(new Color(r, g, b));
}
public void fill(int r, int g, int b) {
fill(new Color(r, g, b));
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseDragged(MouseEvent e) {}
#Override
public void mouseMoved(MouseEvent e) {}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {}
#Override
public void keyPressed(KeyEvent e) {}
#Override
public void keyReleased(KeyEvent e) {}
#Override
public void keyTyped(KeyEvent e) {}
}
It doen't seem to work in the height. In the canvas class I have a mouse click event that displays the position of the click. The width I click and the output is a coordenade like (0, ?) for the left and (400, ?) for the right. The problem is the height, at the top I get (?, 0) but at the bottom the highest value I could get is (?, 370) and not (?, 400). My question is: why the bottom doesn't go to (?, 400)?
Thanks for the help, if something is not clear just ask it
EDIT:
Other clases of the program are:
A vector class:
public class Vector {
public float x, y, z;
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public Vector(float x, float y) {
this(x, y, 0);
}
public Vector() {
this(0f, 0f, 0f);
}
public Vector multiply(float... values) {
for (float f : values) {
x *= f;
y *= f;
z *= f;
}
return this;
}
public Vector divide(float... values) {
for (float f : values) {
x /= f;
y /= f;
z /= f;
}
return this;
}
public Vector add(Vector... vectors) {
for (Vector v : vectors)
add(v.x, v.y, v.z);
return this;
}
public Vector add(float x, float y, float z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public Vector add(float x, float y) {
return add(x, y, 0);
}
public Vector remove(Vector... vectors) {
for (Vector v : vectors)
remove(v.x, v.y, v.z);
return this;
}
public Vector remove(float x, float y, float z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public Vector remove(float x, float y) {
return remove(x, y, 0);
}
public Vector normalize() {
double mod = module();
this.x /= mod;
this.y /= mod;
this.z /= mod;
return this;
}
public double module() {
return Math.sqrt(moduleSquared());
}
public double moduleSquared() {
return (double) (x * x + y * y + z * z);
}
public Vector clone() {
return new Vector(x, y, z);
}
#Override
public String toString() {
return "[ " + x + ", " + y + ", " + z + "]";
}
}
A drawing interface:
package me.nemo_64.particlessimulation.util;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
public interface FigureDrawer {
default public void fillRectangle(float x, float y, float width, float height) {
getGraphics2D().fill(new Rectangle2D.Float(x, y, width, height));
}
default public void drawRectangle(float x, float y, float width, float height) {
getGraphics2D().draw(new Rectangle2D.Float(x, y, width, height));
}
default public void fillCircle(float x, float y, float r) {
fillCircle(x, y, r, r);
}
default public void drawCircle(float x, float y, float r) {
drawCircle(x, y, r, r);
}
default public void fillCircle(float x, float y, float r1, float r2) {
getGraphics2D().fill(new Ellipse2D.Float(x, y, r1, r2));
}
default public void drawCircle(float x, float y, float r1, float r2) {
getGraphics2D().draw(new Ellipse2D.Float(x, y, r1, r2));
}
public Graphics2D getGraphics2D();
}
And the Drawable and Updateable interfaces:
package me.nemo_64.particlessimulation.util;
public interface Updateable {
/**
* Called when the component must be updated
* #param delta The seconds that past between calls
*/
public void update(float delta);
}
package me.nemo_64.particlessimulation.util;
import java.awt.Graphics;
public interface Drawable {
/**
* Called when the component must be drawn
*/
public void draw(Graphics g);
}
I see a lot of comments here (all valid) but the question you're asking is why the canvas is showing something like ~ 370 instead of 400 on the right hand side (if I read this correctly).
Your canvas is probably x,y(400,400) but the parent container is only 400px wide based on your initial call. There's a good chance your window border and other operating system specific elements are adding quite a few pixels (on Windows Vista/7 I seem to remember this was ~30px) so there's a really good chance that's the problem.
As a side note (having written some particle simulations in Java/Processing) I can say you might want to look at the OpenGL style of positioning using floats from -1.0f/1.0f representing your canvas rather than trying to do everything in integers. First, you're going to have a lot of choppy "bounces" working with integers as you're going to want to represent values between pixels. Secondly, this makes resizing your simulation to an arbitrary x/y width very easy.
https://learnopengl.com/Getting-started/Coordinate-Systems
Good luck with it, I stopped writing GUIs in Java five or six years ago so I'm a bit rusty. Null layouts can be problematic, I would recommend picking a layout manager and using setPreferredSize (GridBag was my favorite).
I have been working on a simple animation using a Timer on a JComponent. However, I experience incredibly choppy motion when I view the animation. What steps should I take to optimize this code?
MyAnimationFrame
import javax.swing.*;
public class MyAnimationFrame extends JFrame {
public MyAnimationFrame() {
super("My animation frame!");
setSize(300,300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new AnimationComponent(0,0,50,50));
setVisible(true);
}
public static void main(String[] args) {
MyAnimationFrame f = new MyAnimationFrame();
}
}
AnimationComponent
import javax.swing.*;
import java.awt.*;
public class AnimationComponent extends JComponent implements ActionListener {
private Timer animTimer;
private int x;
private int y;
private int xVel;
private int yVel;
private int width;
private int height;
private int oldX;
private int oldY;
public AnimationComponent(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
animTimer = new Timer(25, this);
xVel = 5;
yVel = 5;
animTimer.start();
}
#Override
public void paintComponent(Graphics g) {
g.fillOval(x,y,width,height);
}
#Override
public void actionPerformed(ActionEvent e) {
oldX = x;
oldY = y;
if(x + width > getParent().getWidth() || x < 0) {
xVel *= -1;
}
if(y + height > getParent().getHeight() || y < 0) {
yVel *= -1;
}
x += xVel;
y += yVel;
repaint();
}
}
Not sure if this matters, but I am using OpenJDK version 1.8.0_121.
Any help is appreciated.
After a wonderful discussion with Yago it occurred to me that the issue revolves around number of areas, alot comes down to the ability for Java to sync the updates with the OS and the hardware, some things you can control, some you can't.
Inspired by Yago's example and my "memory" of how the Timing Framework works, I tested you code by increasing the framerate (to 5 milliseconds, ~= 200fps) and decreasing the change delta, which gave the same results as using the Timing Framework, but which leaves you with the flexibility of your original design.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test");
frame.add(new AnimationComponent(0, 0, 50, 50));
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class AnimationComponent extends JComponent implements ActionListener {
private Timer animTimer;
private int x;
private int y;
private int xVel;
private int yVel;
private int width;
private int height;
private int oldX;
private int oldY;
public AnimationComponent(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
animTimer = new Timer(5, this);
xVel = 1;
yVel = 1;
animTimer.start();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
);
g2d.setRenderingHints(hints);
g2d.fillOval(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent e) {
oldX = x;
oldY = y;
if (x + width > getParent().getWidth() || x < 0) {
xVel *= -1;
}
if (y + height > getParent().getHeight() || y < 0) {
yVel *= -1;
}
x += xVel;
y += yVel;
repaint();
}
}
}
If you need to slow down the speed more, then decrease the change delta more, this will mean you have to use doubles instead, which will lead into the Shape's API which supports double values
Which should you use? That's up to you. The Timing Framework is really great for linear animations over a period of time, where you know you want to go from one state to another. It's not so good for things like games, where the state of the object can change from my cycle to another. I'm sure you could do it, but it'd be a lot easier with a simple "main loop" concept - IMHO
Timing Framework offers a way to provide animations highly optimized which may help in this case.
MyAnimationFrame
import javax.swing.*;
public class MyAnimationFrame extends JFrame {
public MyAnimationFrame() {
super("My animation frame!");
setSize(300,300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(new AnimationComponent(0,0,50,50));
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
MyAnimationFrame f = new MyAnimationFrame();
}
});
}
}
AnimationComponent
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.TimeUnit;
import org.jdesktop.core.animation.rendering.*;
import org.jdesktop.core.animation.timing.*;
import org.jdesktop.core.animation.timing.interpolators.*;
import org.jdesktop.swing.animation.rendering.*;
import org.jdesktop.swing.animation.timing.sources.*;
#SuppressWarnings("serial")
public class AnimationComponent extends JRendererPanel {
protected int x;
protected int y;
protected int width;
protected int height;
protected Animator xAnimator;
protected Animator yAnimator;
public AnimationComponent(int x, int y, int width, int height) {
setOpaque(true);
this.x = x;
this.y = y;
this.height = height;
this.width = width;
JRendererFactory.getDefaultRenderer(this,
new JRendererTarget<GraphicsConfiguration, Graphics2D>() {
#Override
public void renderSetup(GraphicsConfiguration gc) {
// Nothing to do
}
#Override
public void renderUpdate() {
// Nothing to do
}
#Override
public void render(Graphics2D g, int w, int h) {
Color c = g.getColor();
g.setColor(g.getBackground());
g.fillRect(0, 0, w, h);
g.setColor(c);
g.fillOval(AnimationComponent.this.x, AnimationComponent.this.y,
AnimationComponent.this.width, AnimationComponent.this.height);
}
#Override
public void renderShutdown() {
// Nothing to do
}
}, false);
this.xAnimator = new Animator.Builder(new SwingTimerTimingSource())
.addTargets(new TimingTargetAdapter() {
#Override
public void timingEvent(Animator source, double fraction) {
AnimationComponent.this.x = (int) ((getWidth() - AnimationComponent.this.width) * fraction);
}})
.setRepeatCount(Animator.INFINITE)
.setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
.setInterpolator(LinearInterpolator.getInstance()).build();
this.yAnimator = new Animator.Builder(new SwingTimerTimingSource())
.addTargets(new TimingTargetAdapter() {
#Override
public void timingEvent(Animator source, double fraction) {
AnimationComponent.this.y = (int) ((getHeight() - AnimationComponent.this.height) * fraction);
}})
.setRepeatCount(Animator.INFINITE)
.setRepeatBehavior(Animator.RepeatBehavior.REVERSE)
.setInterpolator(LinearInterpolator.getInstance()).build();
addComponentListener(new ComponentAdapter() {
private int oldWidth = 0;
private int oldHeight = 0;
#Override
public void componentResized(ComponentEvent event) {
Component c = event.getComponent();
int w = c.getWidth();
int h = c.getHeight();
if (w != this.oldWidth) {
AnimationComponent.this.xAnimator.stop();
AnimationComponent.this.xAnimator = new Animator.Builder()
.copy(AnimationComponent.this.xAnimator)
.setDuration(w * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
.build();
AnimationComponent.this.xAnimator.start();
}
if (h != this.oldHeight) {
AnimationComponent.this.yAnimator.stop();
AnimationComponent.this.yAnimator = new Animator.Builder()
.copy(AnimationComponent.this.yAnimator)
.setDuration(h * 5, TimeUnit.MILLISECONDS) // Original speed was 200 px/s
.build();
AnimationComponent.this.yAnimator.start();
}
this.oldWidth = w;
this.oldHeight = h;
}
});
}
}
I'm getting good results but has one issue: any item you resize, the animation is reset.
here is the entire code for the classes Ship,Asteroids,BaseShapeClass. Ship Class inherits from the BaseShapeClass for its shape. Asteroid class is the main source code which declares the Graphics2D object,AffineTransform(for identity creation),declares double image buffer...
Code for BaseShapeClass..
package baseshapeclass;
import java.awt.Shape;
public class BaseShapeClass {
private Shape shape;
private double x, y;
private double velX, velY;
private double moveAngle, faceAngle;
private boolean alive;
//accessors and mutators
public Shape getShape(){return shape;}
public void setShape(Shape shape){ this.shape = shape; }
public double getX() { return x; }
public void setX(double x) { this.x = x; }
public void incX(double ix) { this.x += ix; }
public double getY() { return y; }
public void setY(double y) { this.y = y; }
public void incY(double iy) { this.y += iy; }
public double getVelX() { return velX; }
public void setVelX(double velX) { this.velX = velX; }
public void incVelX(double ivX) { this.velX += ivX; }
public double getVelY() { return velY; }
public void setVelY(double velY) { this.velY = velY; }
public void incVelY(double ivY) { this.velY += ivY; }
//MoveAngle refers to the objects angular movement
public double getMoveAngle() { return moveAngle; }
public void setMoveAngle(double mAngle) { this.moveAngle = mAngle; }
public void incMoveAngle(double imAngle) { this.moveAngle += imAngle; }
//FaceAngle refers to the objects face/heads angular movement
public double getFaceAngle() { return faceAngle; }
public void setFaceAngle(double fAngle) { this.faceAngle = fAngle; }
public void incFaceAngle(double ifAngle) { this.faceAngle += ifAngle; }
public boolean isAlive() { return alive; }
public void setAlive(boolean alive) { this.alive = alive; }
//default constructor everything will be set to original state
//when update is called everything will start to move
BaseShapeClass(){
setShape(null);
setAlive(false);
//all of them are set to '0' representing their initial position,
//which will be called during the update() Event of the graphics objects
setX(0.0);
setY(0.0);
setVelX(0.0);
setVelY(0.0);
setMoveAngle(0.0);
setFaceAngle(0.0);
}
}
Code for Ship class...
package baseshapeclass;
import java.awt.Rectangle;
import java.awt.Polygon;
public class Ship extends BaseShapeClass {
//ships shape along the x and y cordinates
private final int[] shipx = {-6,3,0,3,6,0};
private final int[] shipy = {6,7,7,7,6,-7};
public Rectangle getBounds(){
Rectangle r = new Rectangle((int)getX()-6, (int)getY()-6, 12, 12);
return r;
}
Ship(){
setShape(new Polygon(shipx, shipy, shipx.length));
setAlive(true);
}
}
Code for Asteroid(Main source code)...
package baseshapeclass;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;
public abstract class Asteroid extends Applet implements Runnable, KeyListener {
BufferedImage backbuffer;
Graphics2D g2d;
Ship ship = new Ship();
boolean showBounds= true;
AffineTransform identity = new AffineTransform();
#Override public void init(){
backbuffer = new BufferedImage(640,480,BufferedImage.TYPE_INT_RGB);
g2d = backbuffer.createGraphics();
ship.setX(320);
ship.setY(240);
addKeyListener(this);
}
#Override public void update(Graphics g){
g2d.setTransform(identity);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getSize().width, getSize().height);
g2d.setColor(Color.WHITE);
g2d.drawString("Ship: "+Math.round(ship.getX())+" , "+Math.round(ship.getY()),2, 150);
g2d.drawString("Face Angle: "+Math.toRadians(ship.getFaceAngle()),5, 30);
g2d.drawString("Move Angle: "+Math.toRadians(ship.getMoveAngle())+90,5,50);
drawShip();
paint(g);
}
public void drawShip(){
g2d.setTransform(identity);
g2d.translate(ship.getX(),ship.getY());
g2d.rotate(Math.toRadians(ship.getFaceAngle()));
g2d.setColor(Color.ORANGE);
g2d.fill(ship.getShape());
}
}
I hope you guys get a better idea with all the code in place. Just wanted to know on the part of Ship class why are the ships x and y cordinates such as under:
public class ship extends BaseShapeClass{
private int[] shipx = {-6,3,0,3,6,0};
private int[] shipy = {6,7,7,7,6,-7};
}
I cant follow on how those values will make upto a Polygon??
Ship(){
setShape(new Polygon(shipx,shipy,shipx.length));
setAlive(true);
}
You can see that the two arrays you are confused about go into the initialization of a Polygon. These two arrays, taken as a pair, give the x and y coordinates of each point in the Polygon.
This post is in answer to your comment in Kronion's answer; I was going to post it as a comment, but there is too much to say and I wanted to show you some code, which is not as legible in the comments.
As Kronion said, the Polygon class does indeed accept an array of X coordinates, and an array of Y coordinates. The reason for this is that the X and Y coordinate are stored at the same position in both arrays. So if int index = 0, then that X,Y coordinate pair would be xArray[index] and yArray[index].
If that doesn't make any sense, examine the Polygon class source code. For example, you'll see this happening in the contains method, here:
for (int i = 0; i < npoints; lastx = curx, lasty = cury, i++) {
curx = xpoints[i];
cury = ypoints[i];
// remainder of loop
}
So in short, they are assigned in this manner because the X and Y are paired by their index positions.
Hope that helps.
I am having a problem with a project.
So basically what I have is a class Shape with some sub-class ( ShapeRectangle, ShapeTriangle, etc ).
In each sub-class, I got an outputShape method:
g.fillRect(getX(), getY(), getWidth(), getHeight());
In another class, showShapes, I got an array that contains the sub-classes.
I would like to run the method via the array.
Is there any way to do it?
EDIT:
The array in showShapes is a Shape[] array.
Here are the code of ShapeRect ( sorry, bits are in french ):
import java.awt.Color;
import java.awt.Graphics;
public class FormeRectangulaire extends Forme {
public FormeRectangulaire(int x, int y, int width, int height){
setX(x);
setY(y);
setWidth(width);
setHeight(height);
setColor(Color.RED);
}
public void afficherForme(Graphics g){
g.setColor(getColor());
g.fillRect(getX(), getY(), getWidth(), getHeight());
}
}
Here is the shape:
import java.awt.Color;
import java.awt.Graphics;
public class Forme {
private int x;
private int y;
private int width;
private int height;
private Color color;
/*public void afficherForme(Graphics g){
afficherForme(g);
}*/
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 Color getColor(){
return color;
}
public void setColor(Color color){
this.color = color;
}
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;
}
}
And here is how I put each class inside the array:
public void creerArrayFormes(){
String[][] donnes = getDatas();
if(donnes[getDatasElement()][0].equals("CARRE") || donnes[getDatasElement()][0].equals("RECTANGLE")){
FormeRectangulaire rect = new FormeRectangulaire(Integer.parseInt(donnes[getDatasElement()][1]),Integer.parseInt(donnes[getDatasElement()][2]),Integer.parseInt(donnes[getDatasElement()][3]),Integer.parseInt(donnes[getDatasElement()][4]));
setFormes(rect, getDatasElement());
}
}
You must create an array object or a List of SuperClass :
List<Form> shapes = new ArrayList<Form>();
//List<Form> shapes = new ArrayList<>(); in JDK 7
shapes.add(new ShapeRectangle());
shapes.add(new ShapeTriangle());
//....
Create a loop to get the objects :
for(int i = 0; i<shapes.size();i++){
Object obj = shapes.get(i);
if(objinstanceof ShapeRectangle){
((ShapeRectangle)obj).fillRect(....);
}
else if(list.get(i)
}
I strongly recommend adding outputShape to the Shape class. If Shape is naturally abstract (no expectation of actually creating a new Shape()) it can be abstract.
If you do that, you can just iterate over your Shape[] and call outputShape for an element. That will call the version of outputShape for the actual class of the element:
for(Shape s: shapes) {
s.outputShape();
}