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.
Related
I am working on a donkey kong type game and like I said I want the player to only jump if he is on the floor.
I made a sort of a gravity mechanic and I check collision so he don't go through a floor and I came up with an idea that your can jump only when you are colliding. But here comes the main problem that I don't know how to tell that information to a method in another class.
Hope it makes some sense, I am a beginner and don't know if I described the problem well.
I tried to separate my code but it makes more problems
Here's a class with a player:
import java.awt.*;
import java.awt.event.KeyEvent;
public class Mario extends Rectangle {
int rychlost = 5;
int xZrychleni;
int gravitace= 5;
int yZrychleni;
int vyskok = 20;
Mario(int x, int y, int SIRKA_MARIA,int VYSKA_MARIA){
super(x, y, SIRKA_MARIA, VYSKA_MARIA);
}
public void nakresli(Graphics g) {
g.setColor(Color.white);
g.fillRect(x, y, width, height);
}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_A) {
nastavSmerX(-rychlost);
}
if(e.getKeyCode()== KeyEvent.VK_D) {
nastavSmerX(rychlost);
}
if(e.getKeyCode()==KeyEvent.VK_SPACE){
nastavSmerY(vyskok);
}
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_A) {
nastavSmerX(0);
}
if (e.getKeyCode() == KeyEvent.VK_D) {
nastavSmerX(0);
}
if(e.getKeyCode()==KeyEvent.VK_SPACE){
nastavSmerY(0);
}
}
public void nastavSmerX ( int SmerX){
xZrychleni = SmerX;
}
public void nastavSmerY ( int SmerY){
yZrychleni = SmerY;
}
public void move () {
x = x + xZrychleni;
y = y + gravitace;
y = y - yZrychleni;
}
}
Here is a panel class where I check collisions:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class HerniPanel extends JPanel implements Runnable{
static final int HERNI_SIRKA = 1200;
static final int HERNI_VYSKA = 700;
static final Dimension SCREEN_SIZE = new Dimension(HERNI_SIRKA, HERNI_VYSKA);
static final int SIRKA_PLOSINY = 1000;
static final int VYSKA_PLOSINY = 35;
static final int SIRKA_ZEBRIKU = 25;
static final int VYSKA_ZEBRIKU = 180;
static final int MARIO_VELIKOST_XY = 50;
Thread gameThread;
Image image;
Graphics graphics;
Plosina plosina0;
Plosina plosina1;
Plosina plosina2;
Plosina plosina3;
Mario mario;
Zebrik zebrik1;
Zebrik zebrik2;
Zebrik zebrik3;
HerniPanel(){
novaPlosina();
novyMario();
novyZebrik();
this.setFocusable(true);
this.addKeyListener(new AL());
this.setPreferredSize(SCREEN_SIZE);
gameThread = new Thread(this);
gameThread.start();
}
public void novaPlosina() {
plosina0 = new Plosina(0, (HERNI_VYSKA - VYSKA_PLOSINY), HERNI_SIRKA, VYSKA_PLOSINY, 1);
plosina1 = new Plosina((HERNI_SIRKA - SIRKA_PLOSINY),(HERNI_VYSKA - VYSKA_PLOSINY - 180), SIRKA_PLOSINY, VYSKA_PLOSINY, 2 );
plosina2 = new Plosina(0, (HERNI_VYSKA - VYSKA_PLOSINY - 360), SIRKA_PLOSINY, VYSKA_PLOSINY,3 );
plosina3 = new Plosina((HERNI_SIRKA - SIRKA_PLOSINY), (HERNI_VYSKA - VYSKA_PLOSINY - 540), HERNI_SIRKA, VYSKA_PLOSINY,4);
}
public void novyMario(){
mario= new Mario(1000, (HERNI_VYSKA - VYSKA_PLOSINY - MARIO_VELIKOST_XY), MARIO_VELIKOST_XY , MARIO_VELIKOST_XY );
}
public void novyZebrik(){
zebrik1 = new Zebrik(250, (HERNI_VYSKA-VYSKA_PLOSINY-VYSKA_ZEBRIKU),SIRKA_ZEBRIKU,VYSKA_ZEBRIKU);
zebrik2 = new Zebrik(800, (HERNI_VYSKA-VYSKA_PLOSINY-VYSKA_ZEBRIKU-180),SIRKA_ZEBRIKU,VYSKA_ZEBRIKU);
zebrik3 = new Zebrik(300, (HERNI_VYSKA-360-VYSKA_PLOSINY-VYSKA_ZEBRIKU),SIRKA_ZEBRIKU,VYSKA_ZEBRIKU);
}
public void paint(Graphics g) {
image = createImage(getWidth(),getHeight());
graphics = image.getGraphics();
nakresli(graphics);
g.drawImage(image,0,0,this);
}
public void nakresli(Graphics g) {
plosina0.nakresli(g);
plosina1.nakresli(g);
plosina2.nakresli(g);
plosina3.nakresli(g);
mario.nakresli(g);
zebrik1.nakresli(g);
zebrik2.nakresli(g);
zebrik3.nakresli(g);
Toolkit.getDefaultToolkit().sync();
}
public void move() {
mario.move();
}
public void zkontrolujKolizi() {
if(mario.x<=0)
mario.x=0;
if(mario.x >= (HERNI_SIRKA-MARIO_VELIKOST_XY))
mario.x = HERNI_SIRKA-MARIO_VELIKOST_XY;
if (mario.y >= (HERNI_VYSKA-VYSKA_PLOSINY - MARIO_VELIKOST_XY)) {
mario.y = (HERNI_VYSKA - VYSKA_PLOSINY - MARIO_VELIKOST_XY);
}
if(mario.intersects(plosina1)){
mario.y = (HERNI_VYSKA-VYSKA_PLOSINY-MARIO_VELIKOST_XY-180);
}
if(mario.intersects(plosina2)){
mario.y = (HERNI_VYSKA-VYSKA_PLOSINY-MARIO_VELIKOST_XY-360);
}
if(mario.intersects(plosina3)){
mario.y = (HERNI_VYSKA-VYSKA_PLOSINY-MARIO_VELIKOST_XY-540);
}
}
public void run() {
//game loop
long lastTime = System.nanoTime();
double amountOfTicks =60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
while(true) {
long now = System.nanoTime();
delta += (now -lastTime)/ns;
lastTime = now;
if(delta >=1) {
move();
zkontrolujKolizi();
repaint();
delta--;
}
}
}
public class AL extends KeyAdapter{
public void keyPressed(KeyEvent e) {
mario.keyPressed(e);
}
public void keyReleased(KeyEvent e) {
mario.keyReleased(e);
}
}
}
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.
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.
I'm creating a simulation of the Boid algorithm in Swing (Graphics2d library), so I want to animate many shapes (triangles). Right now I'm learning how to move them around and have run into a problem with fps and repainting.
The fps drop significantly when I put more than two triangles on the screen (say, 10), and I really don't know where the problem lies. Here's the code (I omitted the libraries):
public class Program {
/**
* #param args
*/
public static void main(String[] args) {
World world = World.getInstance();
// Initialise and show world
JFrame f = new JFrame();
f.setSize(world.getBoundsX(),world.getBoundsY());
f.setTitle("Boids Alive");
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(world);
f.setResizable(false);
f.setVisible(true);
for(int i = 0 ; i < 10; i++)
world.createBoid();
while(true){
//adjust all behaviours
for(Boid b : world.boids){
b.move(0, -2);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
world.refreshAll();
}
}
public final class World extends JPanel {
private static final long serialVersionUID = 1L;
private Random r = new Random();
private static final int boundsX = 800;
private static final int boundsY = 600;
public ArrayList<Boid> boids = new ArrayList<Boid>();
public ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
private static World instance = null;
private World() {
this.setBackground(Color.white);
}
public static World getInstance(){
if(instance == null)
instance = new World();
return instance;
}
public int getBoundsX() { return boundsX; }
public int getBoundsY() { return boundsY; }
public void createBoid(){
Boid b = new Boid() ;
b.setX(r.nextInt(790));
b.setY(r.nextInt(590));
boids.add(b);
b.start() ;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
public void refreshAll() {
for(Boid b : boids) {
b.graphicEle.refreshLocation();
}
}
}
public class Boid extends WorldObject{
public Boid() {
graphicEle = new GraphicElement(this);
World world = World.getInstance() ;
world.add(graphicEle) ;
world.repaint() ;
world.validate() ;
}
public void move(int x, int y) {
this.x += x;
this.y += y;
checkBounds();
try {
sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void checkBounds() {
World world = World.getInstance() ;
if(this.x < 0) x = world.getBoundsX();
if(this.x > world.getBoundsX()) x = 0;
if(this.y < -20) y = world.getBoundsY(); //TODO: get the actual size
if(this.y > world.getBoundsY()) y = 0;
}
}
public class GraphicElement extends JPanel {
private static final long serialVersionUID = 1L;
private WorldObject owner;
public GraphicElement(WorldObject obj) {
owner = obj ;
setPreferredSize(new Dimension(20, 20));
setOpaque(false);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create(); //TODO Move to boid class
g2d.setColor(Color.orange);
Path2D.Double triangle = new Path2D.Double();
triangle.moveTo(0, 20);
triangle.lineTo(10, 0);
triangle.lineTo(20, 20);
triangle.closePath();
g2d.fill(triangle);
g2d.dispose();
}
protected void refreshLocation() {
setLocation(owner.getX(), owner.getY());
}
}
Any ideas what I am doing wrong? Should I use translate() instead of setLocation()?
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.