First Java Game Not Rendering - java

I decided I wanted to experiment with making a game and I like Java, so I started following a tutorial here. I did deviate from the video a few times when I felt it was cleaner while being synonymous with the tutorial's code, but not in any way I thought would affect how the code worked. For example, it made sense to me that there should only ever be one instance of the Renderer, and Object Registry so I made them Singletons.
The code I have so far is supposed to create a window with a black background, and a blue square in the middle of the window representing the player, that much is working. However, it should also be sliding around in response to the wasd keys, and even more slowly drifting in one direction regardless. Instead it's doing nothing.
I spent no less than an hour trying to figure out why it wasn't working. It seems to be ticking just fine and the data looks like it's updating properly, but even though my render methods are also being called, the screen just isn't changing.
This is all the code I have so far, since I'm out of ideas as to the problem. I apologize for listing a whole project.
public class Game implements Runnable {
private final Thread thread;
private boolean running = false;
Game() {
thread = new Thread(this);
}
public static void main(String[] args) {
Game game = new Game();
game.start();
new Player(600,450,0));
}
private void start() {
thread.start();
running = true;
}
#Override
public void run() {
double tps = 10.0;
double nsPerTick = 1000000000 / tps;
double delta = 0;
int frames = 0;
long timer = System.currentTimeMillis();
long lTime = System.nanoTime();
long now;
while (running) {
now = System.nanoTime();
delta += (now - lTime) / nsPerTick;
lTime = now;
while (delta >= 1) {
Registry.getInstance().tick();
delta--;
}
if (running) Renderer.getInstance().run();
frames++;
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS: " + frames);
frames = 0;
}
}
stop();
}
private void stop() {
try {
thread.join();
} catch (Exception e) {
e.printStackTrace();
}
running = false;
}
}
public class Renderer extends Canvas {
private static final Renderer renderer = new Renderer();
private final Window window;
private final BufferStrategy bs;
private final Graphics g;
boolean black = true;
private Renderer() {
window = new Window(1200, 900, "First Game", this);
this.createBufferStrategy(2);
bs = this.getBufferStrategy();
g = bs.getDrawGraphics();
addKeyListener(Controller.getInstance());
}
public static Renderer getInstance() {return renderer;}
public void run() {
g.setColor(Color.BLACK);
//this was to see if even the background would update, it wouldn't
//g.setColor(black ? Color.BLACK : Color.WHITE);
//black = !black;
g.fillRect(0,0,1200, 900);
Registry.getInstance().render();
g.dispose();
bs.show();
}
public Graphics getGraphics() {return g;}
private static class Window extends Canvas {
private Window(int width, int height, String title, Renderer renderer) {
JFrame frame = new JFrame(title);
frame.setPreferredSize(new Dimension(width, height));
frame.setMinimumSize(new Dimension(width, height));
frame.setMaximumSize(new Dimension(width, height));
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(renderer);
frame.setVisible(true);
}
}
}
public class Registry {
private static final Registry reg = new Registry();
private final LinkedList<GameObject> objects = new LinkedList<>();
private Registry() {
}
public static Registry getInstance() { return reg; }
public void tick() { //System.out.println("tick");
objects.forEach(GameObject::tick); }
public void render() { objects.forEach(GameObject::render); }
public void add(GameObject gameObject) { objects.add(gameObject); }
public void remove(GameObject gameObject) { objects.remove(gameObject);}
}
public class Controller extends KeyAdapter {
private static final Controller controller = new Controller();
private final HashMap<Character,Boolean> keyStates = new HashMap<>();
private Controller() {
}
public static Controller getInstance() {
return controller;
}
public void keyPressed(KeyEvent e) {
if (!keyStates.getOrDefault(e.getKeyChar(), true)) System.out.println(e.getKeyChar() + " down");
keyStates.put(e.getKeyChar(),true);
}
public void keyReleased(KeyEvent e) {
keyStates.put(e.getKeyChar(),false);
System.out.println(e.getKeyChar() + " up " + keyStates.size());
}
public boolean isKeyDown(char c) {return keyStates.getOrDefault(c,false);}
}
public abstract class GameObject {
protected Graphics graphics = Renderer.getInstance().getGraphics();
protected final ObjectType type;
protected float x,y,r;
protected GameObject(ObjectType objectType, float x, float y, float r) {
this.type = objectType;
this.x = x;
this.y = y;
this.r = r;
Registry.getInstance().add(this);
}
public abstract void tick();
public abstract void render();
public void destroy() { Registry.getInstance().remove(this); }
public float getX() { return x; }
public void setX(float x) { this.x = x; }
public float getY() { return y; }
public void setY(float y) { this.y = y; }
public float getR() { return r; }
public void setR(float r) { this.r = r; }
}
public class Player extends GameObject {
private final Controller controller;
public Player(float x, float y, float r) {
super(ObjectType.PLAYER, x, y, r);
controller = Controller.getInstance();
}
#Override
public void tick() {
this.x += 1;
if (controller.isKeyDown('w')) x += 2;
if (controller.isKeyDown('a')) y -= 2;
if (controller.isKeyDown('s')) x -= 2;
if (controller.isKeyDown('d')) y += 2;
}
#Override
public void render() {
graphics.setColor(Color.BLUE);
graphics.fillRect((int) (this.x-12),(int) (this.y-12), 24,24);
}
}

The problem lies in your handling of Graphics. There's only ONE active Graphics object you can effectively address.
Refactor your code, so that you pass the current Graphics object via parameter through the target methods (like here: Player.render() should become Player.render(Gpahics g). And get rid of the Gameobject.graphics member variable. That is the culprit.
Adding to that, the best way to do simple rendering is to override the paint(Graphics g) method or the paintComponents(Graphics g), and from outside call repaint() on the JPanel/Canvas. This way the UI instigates drawing itself, takes care of the actual frequency, and draws default components/design too if there is some.

Related

Uniformly Varied Rectilinear Motion (UVRM) (Animation)

I'm making an UVRM calculator and also looking to make an animation of an image. I use the Timer class, for the animation, but it happens that I have problems in making the image stop when the seconds have passed or it has reached the entered distance, and of course it is done correctly.
Timer, it receives the milliseconds, and it occurred to me to pass the seconds * 1000, but I do not know if it is correct. Also, I need the image not to span the width of the window (JFrame)
I don't know much about animations and I don't know what to do really.
Car class
public class Car {
private final ImageIcon imagen;
private double posx;
private int width;
public Car() {
imagen = new ImageIcon(getClass().getResource("/images/carred.png"));
posx = 10;
width = 400;
}
public double getPosx() {
return posx;
}
public int getWidth() {
return width;
}
public void setPosx(double posx) {
this.posx = posx;
}
public void draw(Graphics g){
g.drawImage(imagen.getImage(), (int) posx , 50, width, 300, null);
}
public void move(double x){
posx+=x;
}
}
JPanel
private void btnCalcActionPerformed(java.awt.event.ActionEvent evt) {
String text_vf = txfvf.getText();
String text_v0 = txfv0.getText();
String text_a = txfa.getText();
String text_d = txfd.getText();
String text_t = txft.getText();
String res = "message.. res";
// Calculations...
//text_d = distance
moveCarImage(t, Integer.parseInt(text_v0), a, Integer.parseInt(text_d));
}
private void moveCarImage(double timeMilis, double v0, double acceleration, int fDistance) {
timer = new Timer(timeMilis, (e) -> {
car.move(acceleration);
repaint();
if (car.getPosx() >= fDistance+car.getWidth() ) {
timer.stop();
}
});
timer.start();
if (!timer.isRunning()) {
System.out.println("Car Stopped.........");
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
car.draw(g);
//g.drawRect(10, 50, 300, 300);
}
Can you help me please.. thanks so much.
EDIT:
I changed the milliseconds a timeMilis

How can I optimize my 2d java game rendering?

My problem is, I have a game with a random generated map and it has only 30-40 fps because of the number of blocks.(You can imagine my game like a 2d minecraft).
First I search for the first tile what is in screen. Then start a loop render tile next tile... until I reach the last tile what you can see.
(I don't use any of the Java classes like graphics/graphics2d I use my own code what is an int[] with the rows of teh screen in it and when I render a tile I change the int[x+y*width] position of the screen to the correct pixel of the block)
I think logically this is the best way to render my map and i don't understend why is the low fps. I am wrong or I need to search for some other problem in my code? Or there is any better rendering method?
If I skip the rendering of the world, there is stabile 120 fps what is capped there. What can be the problem?
I know you dont use the Gaphics functions and choose to manipulate a pixel-array instead.
Try to change your game to use the Graphics object since there is no (easy and efficient) way around it. If you still choose not to do so, try to add
System.setProperty("sun.java2d.opengl", "true");
at the very begining of your code just after
public static void main(String[] args) {
I tried to do it the same way as you back when i first made simple games but you later come to realize that the built-in Graphics functions are vastly superior in performance and ease of use.
EDIT:
A short explanaition on how a basic game might use the Graphics object:
Suppose you created a JFrame. If you then add a class that extends from Canvas and add that to it. If you want to use a menu, you might even create a JPanel first and add the Canvas into the Jpanel so you can hide it more easily.
Here you have an example how we create a usable canvas:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 1920;
public static final int HEIGHT = WIDTH * 9 / 16;
public static final String TITLE = "YOUR GAMES NAME";
public static final int TICKSPERS = 120;
public static final boolean ISFRAMECAPPED = false;
public static JFrame frame;
private Thread thread;
private boolean running = false;
public int frames;
public int lastFrames;
public int ticks;
public Game(){
Dimension size = new Dimension(WIDTH, HEIGHT);
setPreferredSize(size);
setMaximumSize(size);
setMinimumSize(size);
}
public void render(){
frames++;
BufferStrategy bs = getBufferStrategy();
if (bs == null){
createBufferStrategy(2);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(new Color(79,194,232));
g.fillRect(0, 0, getWidth(), getHeight());
//Call your render funtions from here
g.setColor(Color.BLACK);
g.fillRect(120,70,35,90);
g.dispose();
bs.show();
}
public void tick(){
}
public synchronized void start(){
if(running) return;
running = true;
thread = new Thread(this, "Thread");
thread.start();
}
public synchronized void stop(){
if(!running) return;
running = false;
try {
System.exit(1);
frame.dispose();
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void init(){
}
public void run() {
init();
//Tick counter variable
long lastTime = System.nanoTime();
//Nanoseconds per Tick
double nsPerTick = 1000000000D/TICKSPERS;
frames = 0;
ticks = 0;
long fpsTimer = System.currentTimeMillis();
double delta = 0;
boolean shouldRender;
while(running){
shouldRender = !ISFRAMECAPPED;
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
//if it should tick it does this
while(delta >= 1 ){
ticks++;
tick();
delta -= 1;
shouldRender = true;
}
if (shouldRender){
render();
}
if (fpsTimer < System.currentTimeMillis() - 1000){
System.out.println(ticks +" ticks, "+ frames+ " frames");
ticks = 0;
lastFrames = frames;
frames = 0;
fpsTimer = System.currentTimeMillis();
}
}
}
public static void main(String[] args){
Game game = new Game();
frame = new JFrame(TITLE);
frame.add(game);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.start();
}
}
This class can be your base. Its tick method is called 120 times a second and its render method can render stuff onto the screen.
If you are not familiar with the functions of the Graphics Object i suggest reading a little about them.
You can't reach the Graphics object from the outside. You need to call the render functions from inside the games render function before the Graphics object gets disposed. Try to seperate your game logic from the render functions.
I use a slightly different architecture than what's stated above. I actually create a separate Renderer object which is basically a deferred renderer.
It's structured like this
public class App {
JFrame window;
Renderer renderer;
Engine engine; //implementation is a nested class within App
Dimension window_dimension; //stored for later use
public App() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
window = new JFrame("MyGame");
boolean full_screen = true;
window_dimension = initializeWindow(window, full_screen);
renderer = new Renderer(window_dimension);
window.add(renderer);
engine = new Engine(renderer);
engine.start();
}
});
}
}
Renderer.java :
import java.awt.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class Renderer extends JPanel {
Dimension dim;
private CopyOnWriteArrayList<Drawable> drawables = new CopyOnWriteArrayList<Drawable>();
Renderer(Dimension dim) {
this.dim = dim;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.clearRect(0, 0, getWidth(), getHeight());
for (Drawable drawable : drawables) {
drawable.paint(g2d);
}
g2d.dispose();
drawables.clear();
}
public synchronized void render() {
repaint();
}
public synchronized void submit(Drawable drawable) {
drawables.add(drawable);
}
public synchronized void submitBackground(Drawable drawable) {
drawables.add(0,drawable);
}
}
Drawable.java :
import java.awt.*;
abstract class Drawable {
protected Stroke stroke;
protected Color color, stroke_color;
public Dimension size;
public float sub_pixel_x;
public float sub_pixel_y;
public Drawable(Color color) {
setColor(color);
setStrokeColor(new Color(0));
sub_pixel_x = 0.0f;
sub_pixel_y = 0.0f;
size = new Dimension(10, 10);
}
public void setStroke(float width) {
stroke = new BasicStroke(width);
}
public void noStroke() {
stroke = null;
}
public void setColor(Color color) {
this.color = color;
}
public void setStrokeColor(Color color) {
this.stroke_color = color;
}
public void setLocation(float x, float y) {
sub_pixel_x = x;
sub_pixel_y = y;
}
protected abstract void paint(Graphics2D g2d);
}
AbstractEngine.java :
import java.awt.*;
abstract class AbstractEngine implements Runnable {
Renderer renderer;
Dimension dimension;
boolean running;
Thread thread;
public AbstractEngine(Renderer renderer) {
this.renderer = renderer;
dimension = renderer.dim;
}
public void start() {
if (running) return;
running = true;
thread = new Thread(this, "Tread");
thread.start();
}
public void stop() {
if(!running) return;
running = false;
try {
System.exit(1);
thread.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public abstract void handleInput();
public abstract void update();
public abstract void render();
#Override
public void run() {
final int UPS = 120;
final int FPS = 60;
long initialTime = System.nanoTime();
final double timeU = 1000000000 / UPS;
final double timeF = 1000000000 / FPS;
double deltaU = 0, deltaF = 0;
int frames = 0, ticks = 0;
long timer = System.currentTimeMillis();
while (running) {
long currentTime = System.nanoTime();
deltaU += (currentTime - initialTime) / timeU;
deltaF += (currentTime - initialTime) / timeF;
initialTime = currentTime;
if (deltaU >= 1) {
handleInput();
update();
ticks++;
deltaU--;
}
if (deltaF >= 1) {
render();
renderer.render();
frames++;
deltaF--;
}
if (System.currentTimeMillis() - timer > 1000) {
frames = 0;
ticks = 0;
timer += 1000;
}
}
}
}
Input is handled in the extended Engine class. I have a MouseState object that holds mouse positions and button state. I then create a MouseAdapter that updates the date in MouseState and attach it to the renderer. It's a bit weird, I know, but works nicely.

Graphics Stuttering in Java (Space Invaders)

I am creating a Space Invaders Game with just one Invader. The images are stuttering and are only visible ~10% of the time. What is wrong with my code?
package spaceinvader;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class spaceInvaders extends JApplet implements KeyListener, ActionListener
{
//Declare components and variables
JPanel mainPanel = new JPanel();
ImageIcon carImage = new ImageIcon("ship.png");
ImageIcon invaderImage = new ImageIcon("invader.png");
int intPosX = 240;
int intPosY = 330;
int intXAmount = 15;
boolean shipMoveLeft = false;
boolean shipMoveRight = false;
Timer shipTimer = new Timer(100,this);
int intBulletX = -50;
int intBulletY = -50;
boolean bulletMove = false;
boolean bulletActive = false;
Timer bulletTimer = new Timer(50,this);
int intInvaderX = 0;
int intInvaderY = 0;
int invaderXAmount = 10;
boolean invaderMove = true;
Timer invaderTimer= new Timer(1000,this);
public void init()
{
addKeyListener(this);
setFocusable(true);
resize(600,400);
setContentPane(mainPanel);
shipTimer.start();
bulletTimer.start();
invaderTimer.start();
}
public void actionPerformed(ActionEvent e)
{
requestFocus();
if(shipMoveLeft)
intPosX += intXAmount;
else if(shipMoveRight)
intPosX -= intXAmount;
if(bulletMove && bulletActive){
intBulletY -= 15;
if(intBulletY <= -50){
bulletMove = false;
bulletActive = false;
}
}
if(invaderMove){
intInvaderX += invaderXAmount;
if(intInvaderX > getWidth() - 60 || intInvaderX < 0){
intInvaderY += 40;
invaderXAmount *= -1;
}
}
repaint();
}
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if (key == 37){
shipMoveRight = true;
}
else if (key == 39){
shipMoveLeft = true;
}
else if (key == 32){
if(bulletActive == false){
intBulletX = intPosX;
intBulletY = intPosY;
}
bulletMove = true;
bulletActive = true;
}
}
public void keyReleased(KeyEvent e)
{
shipMoveLeft = false;
shipMoveRight = false;
}
public void keyTyped(KeyEvent e)
{
}
public void paint(Graphics gr)
{
super.paint(gr);
gr.setColor(Color.red);
gr.fillOval(intBulletX, intBulletY, 10, 25);
carImage.paintIcon(this,gr, intPosX, intPosY); //Draw image in new spot
invaderImage.paintIcon(this,gr, intInvaderX, intInvaderY);
}
}
So there are a number of issues which jump out immediately...
Applets are a dead end, most browsers will actively block them and/or have dropped support for the plugin
You're adding a JPanel to the applet, but overriding the applet's paint method, because of the way painting can work, the panel can be painted independently of the applet, causing it to paint over whatever you might have painted
You don't need multiple timers, you just need to have better delta values (smaller been faster)
KeyListener is a poor choice for detecting keyboard input, it's rather low level and has focus related issues which are easily overcome by using the Key Bindings API
I'd start by having a look at Performing Custom Painting, Painting in AWT and Swing and How to Use Key Bindings for more details.
So how would you start fixing it? Start by using a JPanel as you basic container, override it's paintComponent and place all your paint logic here.
Use a single Timer to manage the updates
There are any number of approaches you can take, but I'd start with defining some contracts that define what can go on in the game
public interface GameSpace extends ImageObserver {
public Dimension getGameSpace();
public boolean hasInput(Input input);
}
public interface Entity {
public void paint(GameSpace gameSpace, Graphics2D g2d);
public boolean update(GameSpace gameSpace);
public Rectangle getBounds();
}
public abstract class AbstractEntity implements Entity {
private int x;
private int y;
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
protected abstract int getWidth();
protected abstract int getHeight();
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
}
public abstract class AbstractImageEntity extends AbstractEntity {
protected abstract BufferedImage getImage();
#Override
protected int getWidth() {
return getImage().getWidth();
}
#Override
protected int getHeight() {
return getImage().getHeight();
}
}
This separates some of management of key elements, allowing for a more flexible design. You might have a lot more entities, one's which could move, ones which could not, some which are painted, some which are not, but all which provide support for the core engine to get work done.
Once you have that you can start defining some core entities you need
public class ShipEntity extends AbstractImageEntity {
private BufferedImage ship;
public ShipEntity(GameSpace gameSpace) throws IOException {
ship = ImageIO.read(getClass().getResource("/resources/ship.png"));
setY(gameSpace.getGameSpace().height - getBounds().height);
setX((gameSpace.getGameSpace().width - getBounds().width) / 2);
}
#Override
public BufferedImage getImage() {
return ship;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(ship, getX(), getY(), gameSpace);
}
#Override
public boolean update(GameSpace gameSpace) {
int x = getX();
if (gameSpace.hasInput(Input.LEFT)) {
x -= 2;
}
if (gameSpace.hasInput(Input.RIGHT)) {
x += 2;
}
if (x < 0) {
x = 0;
} else if (x + getWidth() > gameSpace.getGameSpace().width) {
x = gameSpace.getGameSpace().width - getWidth();
}
setX(x);
return true;
}
}
public class InvaderEntity extends AbstractImageEntity {
private BufferedImage invader;
public InvaderEntity() throws IOException {
invader = ImageIO.read(getClass().getResource("/resources/Invader.png"));
}
#Override
protected BufferedImage getImage() {
return invader;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(invader, getX(), getY(), gameSpace);
}
#Override
public boolean update(GameSpace gameSpace) {
return true;
}
}
public class ProjectileEntity extends AbstractEntity {
private int delta;
public ProjectileEntity(int delta) {
this.delta = delta;
}
#Override
protected int getWidth() {
return 10;
}
#Override
protected int getHeight() {
return 10;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.setColor(Color.RED);
int width = getWidth();
int height = getHeight();
g2d.fillOval(getX() - width / 2, getY() - height / 2, width, height);
}
#Override
public boolean update(GameSpace gameSpace) {
int y = getY() + delta;
setY(getY() + delta);
return y + getHeight() >= 0 && y + getHeight() <= gameSpace.getGameSpace().height;
}
}
Basically, they contain the logic required for getting their jobs done.
Finally, you need to setup the actual game UI
public class GamePane extends JPanel implements GameSpace {
private Set<Input> inputs;
private Entity playerEntity;
private List<Entity> projectileEntities;
private List<Entity> invaderEntities;
private long timeOfLastProjectile = -1;
public GamePane() throws IOException {
setBackground(Color.BLACK);
inputs = new HashSet<>(2);
playerEntity = new ShipEntity(this);
projectileEntities = new ArrayList<>(25);
invaderEntities = new ArrayList<>(25);
InvaderEntity invader = new InvaderEntity();
invader.setX((getGameSpace().width - invader.getBounds().width) / 2);
invader.setY((getGameSpace().height - invader.getBounds().height) / 2);
invaderEntities.add(invader);
addKeyBinding(Input.LEFT, "left", KeyEvent.VK_LEFT);
addKeyBinding(Input.RIGHT, "right", KeyEvent.VK_RIGHT);
addKeyBinding(Input.SPACE, "space", KeyEvent.VK_SPACE);
Timer timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
processCollisions();
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
protected void updateState() {
playerEntity.update(this);
if (hasInput(Input.SPACE)) {
long time = System.currentTimeMillis() - timeOfLastProjectile;
if (time < 0 || time > 1000) {
timeOfLastProjectile = System.currentTimeMillis();
Rectangle bounds = playerEntity.getBounds();
ProjectileEntity projectile = new ProjectileEntity(-1);
int x = bounds.x + ((bounds.width - projectile.getWidth()) / 2);
int y = bounds.y - projectile.getHeight();
projectile.setX(x);
projectile.setY(y);
projectileEntities.add(projectile);
}
}
for (Entity entity : invaderEntities) {
entity.update(this);
}
List<Entity> outOfBounds = new ArrayList<>(25);
for (Entity entity : projectileEntities) {
if (!entity.update(this)) {
outOfBounds.add(entity);
}
}
projectileEntities.removeAll(outOfBounds);
}
protected void processCollisions() {
Set<Entity> hitInvaders = new HashSet<>(25);
Set<Entity> hitProjectiles = new HashSet<>(25);
for (Entity invader : invaderEntities) {
for (Entity projectile : projectileEntities) {
if (projectile.getBounds().intersects(invader.getBounds())) {
// Maybe lots of cool explosiions
hitInvaders.add(invader);
hitProjectiles.add(projectile);
}
}
}
invaderEntities.removeAll(hitInvaders);
projectileEntities.removeAll(hitProjectiles);
}
protected void addKeyBinding(Input input, String name, int virtualKey) {
ActionMap am = getActionMap();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), name + ".pressed");
im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), name + ".released");
am.put(name + ".pressed", new KeyAction(inputs, input, true));
am.put(name + ".released", new KeyAction(inputs, input, false));
}
#Override
public Dimension getGameSpace() {
return getPreferredSize();
}
#Override
public boolean hasInput(Input input) {
return inputs.contains(input);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
playerEntity.paint(this, g2d);
g2d.dispose();
for (Entity entity : invaderEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
for (Entity entity : projectileEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
}
}
public class KeyAction extends AbstractAction {
private Input input;
private Set<Input> inputs;
private boolean pressed;
public KeyAction(Set<Input> inputs, Input input, boolean pressed) {
this.input = input;
this.inputs = inputs;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
}
Now you could go a little further and generate an "engine" class which controls the entities and performs all the required updating, but I'm lazy ;)
That's a really rough idea of some the basic concepts you need to develop to be able to move forward, hope it helps

Java animation does not refresh properly and uses too much CPU

I have a problem with a simple Java game I am creating right now. I want a dot (a car) to be movable across the game screen, but instead of this all I can see on the screen is the long "snake" created by the dot moved by me:
Other problem is that activity manager on my Mac shows that the game uses huge amount of CPU power - my laptop gets very hot very fast. I suspect that there is something wrong with my game loop, but since now I haven't found any solution:
BoardPanel.java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class BoardPanel extends JPanel implements KeyListener, Runnable {
public static final int WIDTH = 600;
public static final int HEIGHT = 600;
private Thread thread;
private boolean running;
private BufferedImage image;
private Graphics2D g;
private int FPS = 30;
private int targetTime = 1000/FPS;
private Map map;
private Car car;
public BoardPanel() {
super();
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setFocusable(true);
requestFocus();
}
public void addNotify() {
super.addNotify();
if(thread == null) {
thread = new Thread(this);
thread.start();
}
addKeyListener(this);
}
public void run() {
init();
long startTime;
long reTime;
long waitTime;
while (running) {
startTime = System.nanoTime();
update();
render();
draw();
reTime = System.nanoTime() - startTime;
waitTime = targetTime - reTime;
try {
Thread.sleep(waitTime);
}
catch(Exception e) {
}
}
}
private void init() {
running = true;
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
map = new Map();
car = new Car(map);
car.setxpos(50);
car.setypos(50);
}
private void update() {
map.update();
car.update();
}
private void render() {
map.draw(g);
car.draw(g);
}
private void draw() {
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
public void keyTyped(KeyEvent key) {
}
public void keyPressed(KeyEvent key) {
int code = key.getKeyCode();
if(code == KeyEvent.VK_LEFT) {
car.setLeft(true);
}
if(code == KeyEvent.VK_RIGHT) {
car.setRight(true);
}
if(code == KeyEvent.VK_UP) {
car.setUp(true);
}
if(code == KeyEvent.VK_DOWN) {
car.setDown(true);
}
}
public void keyReleased(KeyEvent key) {
int code = key.getKeyCode();
if(code == KeyEvent.VK_LEFT) {
car.setLeft(false);
}
if(code == KeyEvent.VK_RIGHT) {
car.setRight(false);
}
if(code == KeyEvent.VK_UP) {
car.setUp(false);
}
if(code == KeyEvent.VK_DOWN) {
car.setDown(false);
}
}
}
Car.java
import java.awt.*;
public class Car {
private double xpos;
private double ypos;
//private int xsize;
//private int ysize;
private boolean left;
private boolean right;
private boolean up;
private boolean down;
private Map map;
public Car(Map m) {
map = m;
}
public void setxpos(int i) {
xpos = i;
}
public void setypos(int i) {
ypos = i;
}
public void setLeft (boolean b) {
left = b;
}
public void setRight (boolean b) {
right = b;
}
public void setUp (boolean b) {
up = b;
}
public void setDown (boolean b) {
down = b;
}
public void update() {
if(left) {
xpos--;
}
if(right) {
xpos++;
}
if(up) {
ypos--;
}
if(down) {
ypos++;
}
}
public void draw(Graphics2D g) {
int mx = map.getx();
int my = map.gety();
g.setColor(Color.BLUE);
g.fillOval((int)(mx+xpos-20/2), (int)(my+ypos-20/2), 20, 20);
}
}
Map.java (I haven't created map yet, right now only want the dot to move properly)
import java.awt.*;
public class Map {
public int x;
public int y;
public int getx() {
return x;
}
public int gety() {
return y;
}
public void setx(int i) {
x = i;
}
public void sety(int i ) {
y = i;
}
public void update() {
}
public void draw(Graphics2D g) {
}
}
RacerMain.java
import javax.swing.JFrame;
public class RacerMain {
public static void main (String[]args) {
//MainFrame mf = new MainFrame();
JFrame mf = new JFrame("Racer");
mf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mf.setContentPane(new BoardPanel());;
mf.pack();
mf.setVisible(true);
}
}
Many thanks for your help!!!
In addition to what camickr said: Be careful with your usage of the Graphics. A rule of thumb:
Never call getGraphics on a Component!
Additionally, you are never disposing the Graphics that you are fetching from the BufferedImage. You are instead disposing the Graphics that you obtained from the Component, which may be even worse than fetching it in the first place!
I wonder why you are overriding the addNotify method. You should not implement any functionality based on averriding this method....
So you should change the respecive parts of your BoardPanel class roughly as follows:
public class BoardPanel extends JPanel implements KeyListener, Runnable {
...
// private Graphics2D g; // Don't store this here
public BoardPanel() {
...
// Create the thread here instead of in the "addNotify" method!
if(thread == null) {
thread = new Thread(this);
thread.start();
}
addKeyListener(this);
}
public void run() {
...
while (running) {
...
//draw(); // Don't call this method
repaint(); // Trigger a repaint instead!
}
}
private void render() {
Graphics2D g = image.createGraphics();
// Clear the background (see camickrs answer)
g.setColor(Color.BLACK);
g.fillRect(0,0,image.getWidth(),image.getHeight());
try
{
map.draw(g);
car.draw(g);
}
finally
{
g.dispose(); // Dispose the Graphics after it has been used
}
}
/** Don't call "getGraphics" on a component!
private void draw() {
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
*/
// Override the paintComponent method instead:
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image,0,0,null);
}
In your draw() method you need to clear the BufferedImages background before your invoke the fillOval method. Something like:
g.setColor( Color.BLACK );
g.fillRect(...);
g.setColor( Color.BLUE );
g.fillOval(...);
Print out your "waitTime" to make sure you are waiting a reasonable time.

Graphics will not cover entire canvas

What I know: Canvas is displayed properly, due to the yellow. However, graphics will not cover the canvas completely, nor will my law class recognized it past the end of the black area...
So what is causing this to happen? And how could I draw on my currently undrawable section of canvas(yellow part), or should I just implement my graphics another way?
EDIT: The UI class creates a canvas and a buffer, then the graphics class takes over and starts drawing on them, however for some reason it cannot in the yellow section, nor will the Law Class which handles collision with the red cube and walls of the app, regogize the yellow area as a valid place to go. Even through the same variables for dimensions, were used everywhere.
Main Class
package app;
public class Main {
static final int X = 1024;
static final int Y = 680;
static final int sanic = 10;
int fps = 0;
int frames = 0;
long totalTime = 0;
long curTime = System.currentTimeMillis();
long lastTime = curTime;
static int[] pos;
Graphics graphics;
Law physics;
static int status;
boolean holdState;
public Main() {
pos = new int[5];
pos[1] = X;
pos[2] = Y;
}
public void launch() {
// Audio sound = new Audio();
graphics = new Graphics();
physics = new Law();
graphics.draw();
// sound.play();
handle();
}
public void update() {
graphics.cache();
physics.check();
graphics.render();
try {
Thread.sleep(10);
} catch (Exception e) {
} finally {
graphics.dump();
}
}
public void handle() {
while (!isOver()) {
if (!isPaused()) {
update();
}
}
}
boolean isOver() {
if (status == 1) {
status = 0;
return true;
}
return false;
}
boolean isPaused() {
if (status == 2) {
status = 0;
if (!holdState) {
holdState = true;
pos[3] = pos[1];
pos[4] = pos[2];
} else if (holdState) {
holdState = false;
pos[1] = pos[3];
pos[2] = pos[4];
}
}
return holdState;
}
public static void main(String[] args) {
Main game = new Main();
game.launch();
}
}
UI Class
package app;
import java.awt.*;
import java.awt.image.*;
import java.net.URL;
import javax.swing.*;
public class UI extends Main {
JFrame mainWin;
Canvas wall;
URL pic;
Toolkit kit;
Image img;
BufferStrategy Buffer;
Graphics2D shell;
Graphics2D ball;
public UI() {
mainWin = new JFrame("Game");
wall = new Canvas();
wall.addKeyListener(new Input());
}
void draw() {
frame();
icon();
canvas();
show();
prep();
}
void frame() {
mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mainWin.setBackground(Color.BLUE);
mainWin.setIgnoreRepaint(true);
}
void icon() {
pic = ClassLoader.getSystemResource("res/app.png");
kit = Toolkit.getDefaultToolkit();
img = kit.createImage(pic);
mainWin.setIconImage(img);
}
void canvas() {
wall.setBackground(Color.YELLOW);
wall.setIgnoreRepaint(true);
wall.setBounds(0, 0, X, Y);
}
void show() {
mainWin.add(wall);
mainWin.pack();
mainWin.setResizable(false);
mainWin.setLocationRelativeTo(null);
mainWin.setVisible(true);
}
void prep() {
wall.createBufferStrategy(2);
Buffer = wall.getBufferStrategy();
}
}
Graphics Class
package app;
import java.awt.*;
public class Graphics extends UI {
public void render() {
mask();
player();
fps();
Buffer.show();
}
void cache() {
shell = (Graphics2D) Buffer.getDrawGraphics();
ball = (Graphics2D) Buffer.getDrawGraphics();
}
void dump() {
shell.dispose();
ball.dispose();
}
void mask() {
shell.setColor(Color.black);
shell.fillRect(0, 0, X, Y);
}
void fps() {
lastTime = curTime;
curTime = System.currentTimeMillis();
totalTime += curTime - lastTime;
if (totalTime > 1000) {
totalTime -= 1000;
fps = frames;
frames = 0;
}
frames++;
shell.setColor(Color.GREEN);
shell.setFont(new Font("Courier New", Font.PLAIN, 12));
shell.drawString(String.format("FPS: %s", fps), 20, 20);
}
void player() {
ball.setColor(Color.RED);
ball.fillRect(pos[1], pos[2], 32, 32);
}
}
Law Class
package app;
public class Law extends Main {
public void check() {
out();
}
void out() {
if (pos[1] < 0) {
pos[1] = 0;
}
if (pos[2] < 0) {
pos[2] = 0;
}
if (pos[1] > X - 32) {
pos[1] = X - 32;
}
if (pos[2] > Y - 32) {
pos[2] = Y - 32;
}
}
}
This is a bug with Frame#setResizable. Don't ask me why, but it wants to add about 10 pixels to the width and height of the frame.
The best solution I know is to call setResizable BEFORE pack
void show() {
mainWin.add(wall);
mainWin.setResizable(false); // Call me first
mainWin.pack();
mainWin.setLocationRelativeTo(null);
mainWin.setVisible(true);
}
You're not interacting with Swing properly, and you're doing unsafe things with a default Graphics object you should not be touching. Here's the bare bones of what needs to happen, in order.
JFrame mainFrame = new JFrame()
MyCanvas myCanvas = new MyCanvas()
mainFrame.getContentPane().add(myCanvas, BorderLayout.CENTER)
mainFrame.setVisible(true);
new Thread(myThread).start()
Main.main() returns.
MyCanvas needs to look something like this:
public class MyCanvas extends Canvas {
public void paint(Graphics g) {
// drawing code goes here
} // g is now no longer valid. Don't hold onto it or dispose it or anything.
}
Here's the physics update thread:
public class MyThread implements Runnable {
public void run() {
while (keepRunning()) {
physics.update();
myCanvas.repaint(); // myCanvas.paint() will eventually be called
sleep(10);
}
System.exit(0);
}
}
You probably want to add constructors to these objects and pass in references to your other objects they need to reference.

Categories