Java Update & Refresh BufferedImage on JPanel dynamically - java

Very new here. Trying to learn Java.
I figured I'd write a very simple "Game Of Life" auto-algorithm to see if I could grasp some of the basics - (The syntax and concepts are pretty straightforward from other languages etc.)
I've managed to muddle through and seem to be "getting the hang of it" but I've reached a pretty steep hill now.
The code described here will compile and is executable, but as you will see, despite the #override to DrawComponent and the repaint() call, the actual image displayed on-screen does not appear to change...
DIRECT QUESTION IS:
How can I ensure the display is updated every "loop"?
FLUFF & SAMPLE SOURCE:
I appreciate the procedural and non-distributed // rigid single encapsulated class here is not the ideal approach for Java, nor is this "game of life" a particularly appropriate use fothe language - BUT Iwanted to focus on getting core elements and understanding first before I 'advance' onto other things. Secondly, I appreciate this is not a very efficient solution either, but again, I wanted to learn by trying something straightforward and readable so I could easily SEE what was happening and ensure it was working properly as expected.
Third, my naming or formatting conventions may be unusual or non-standard, for this I apologise and again appreciate there are preferred ways. This was not necessarily a priority for me in learning how the language works at this time.
I welcome any advice and tips but please consider I am a complete newcomer to using Java so may not be familiar with concepts and terminologies etc.
/*
Project.java
By: PJ Chowdhury
Entire program encapsulated in single class
Created 29-Oct-2018
Updated: 07-Nov-2018
Added graphics library
*/
//Import the basic required graphics classes.
import java.awt.image.BufferedImage;
import java.awt.*;
//Import the basic applet classes.
import javax.swing.*;
//Core class
public class project
{
//Control value - determines when overpopualted or extinct
private static int supercount;
//Control value - how many surrounding cells must be alive for the central cell to survive
private static byte thrive=4;
//Define & declare effective constant size values
private static byte size=64;
private static byte cellsize=4;
//Declare effective singleton arrays of cells
private static boolean[][] cells;
private static boolean[][] prolif;
//Declare Window Frame
public static JFrame frame;
//Declare Graphics
public static JPanel panel;
//main entry-point. Execution must include parameter argument.
public static void main(String[] args)
{
initialise();
do
{
runtime();
defaultcells();
}
while (1>0); //Bad form of course. I wanted an infinite loop. The window can be closed at user request.
}
//Initialises window & graphics frame
public static void initialiseframe()
{
//Create Window
frame = new JFrame("Life Cells");
//Define window parameters
frame.setSize((int)cellsize*size,(int)cellsize*size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
//Create a window panel to accept graphics
panel = new JPanel()
{
//Overload PaintComponent method to redraw image when the frame panel is redrawn
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
}
};
//attach this panel as a gadget to the frame window
frame.add(panel);
//frame.pack();// Deprecated as this resizes frame window to a minimal size
frame.validate(); // required since panel was added after setVisible was called
frame.repaint(); // required since panel was added after setVisible was called
}
//Initialises & defaults cells
public static void initialisecells()
{
//Define array sizes
cells = new boolean[size][size];
prolif = new boolean[size][size];
// Populate with defaults
defaultcells();
}
//Sets randomised state for each cell
public static void defaultcells()
{
byte x;
byte y;
for (y=0;y<size;y++)
{
for (x=0;x<size;x++)
{
if (Math.random()>=0.5)
{
}
else
{
}
}
}
}
//Wraps initialisation routines
public static void initialise()
{
initialiseframe();
initialisecells();
}
//iterates cells (twice) to determine if they survive or decline and draw to image
public static void process()
{
//Prepare image for cell drawing
Graphics g=panel.getGraphics();
byte x;
byte y;
supercount=0;
//First pass - check if cell will thrive
for (y=0;y<size;y++)
{
for (x=0;x<size;x++)
{
checkcell(x,y);
}
}
//Second pass - apply thrive or wither
for (y=0;y<size;y++)
{
for (x=0;x<size;x++)
{
if (updatecell(x,y))
{
}
if (cells[x][y])
{
}
}
}
}
//sets prolif equivalent depending on status of surrounding cells. This is used in update to set these cells to thrive
public static void checkcell(byte x, byte y)
{
byte count=getsurrounding((int)x,(int)y);
if (count>thrive)
{
prolif[x][y]=true;
}
else
{
if (count<thrive)
{
prolif[x][y]=false;
}
else
{
prolif[x][y]=cells[x][y];
}
}
}
//updates cell with prolif equivalent and returns true if cahnged
public static boolean updatecell(byte x, byte y)
{
if (cells[x][y]!=prolif[x][y])
{
cells[x][y]=prolif[x][y];
return true;
}
return false;
}
//returns number of thriving "cells" surrounding cell at given coords
public static byte getsurrounding(int x, int y)
{
int u=(x-1);
int v=(y-1);
int ux;
int vy;
byte count=0;
for (v=(y-1);v<(y+2);v++)
{
//Use wraparound for edge cells
vy=(v+size) % size;
for (u=(x-1);u<(x+2);u++)
{
//Use wraparound for edge cells
ux=(u+size) % size;
//Only for surrounding cells, not this cell
if ((ux!=x) & (vy!=y))
{
}
}
}
return count;
}
//Draw cell at x,y : not the most efficient nor elegant method...
public static void drawcell(Graphics g, int x, int y, boolean live)
{
if (live)
{
// Draw this cell alive
//g.setColor(Color.GREEN);
}
else
{
// Draw this cell dead
g.setColor(Color.BLACK);
}
g.fillRect(x*cellsize, y*cellsize,cellsize,cellsize);
panel.repaint(x*cellsize, y*cellsize,cellsize,cellsize);
}
//Returns true if population is healthy. False if extinct or overcrowded
public static boolean populationanalysis()
{
return ((supercount<thrive)||(supercount>(int)(size*size)<<1));
}
//Main Loop method
public static void runtime()
{
int sanity=5000;
int loopcount=0;
do
{
process();
loopcount++;
if (populationanalysis())
{
break;
}
}
while (loopcount<sanity);
}
}

Related

Java Swing: Is there a way to check for MouseOver for a drawn shape without checking every time the mouse moves?

I have a JPanel that I draw upon. The shapes I draw are some objects stored in a list. I would like to register MouseOvers over these drawn objects. What I am currently doing is adding a MouseMotionListener that checks the list of objects for a hit every time the mouse moves. This is of course pretty inefficient once there are a lot of objects. Is there a better way to check for MouseOvers than just checking all the objects every time the mouse moves?
Here is a minimal example:
class Ui.java:
public class Ui {
private static JFrame frame;
private static DrawPanel drawPanel;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { createGui();
});
}
private static void createGui() {
frame = new JFrame("Test");
drawPanel = new DrawPanel();
frame.setContentPane(drawPanel);
frame.pack();
frame.setVisible(true);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
class SomeObject.java:
public class SomeObject {
//class that represents some object that will be drawn. note that this is
//just a minmal example and that in my actual application, there are
//other aspects and functions to this class that have nothing to do with drawing.
//This is just kept small for the sake being a minimal example
private String id;
private Point2D origin;
private int length;
private int height;
public SomeObject(String id, Point2D origin, int length, int height) {
this.id = id;
this.origin = origin;
this.length = length;
this.height = height;
}
public String getId() {
return id;
}
public Point2D getOrigin() {
return origin;
}
public int getLength() {
return length;
}
public int getHeight() {
return height;
}
}
class CustomMouseMotionListener.java:
public class CustomMouseMotionListener implements java.awt.event.MouseMotionListener {
public CustomMouseMotionListener() { }
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
for (SomeObject object: DrawPanel.objectsToDraw) {
Shape s = new Rectangle((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());
if (s.contains(e.getPoint())) {
System.out.println("hit: " + object.getId());
}
}
}
}
class DrawPanel.java:
public class DrawPanel extends JPanel {
public static List<SomeObject> objectsToDraw = new ArrayList<>();
public DrawPanel() {
objectsToDraw.add( new SomeObject("a", new Point2D.Float(20,1), 20,20));
objectsToDraw.add( new SomeObject("b", new Point2D.Float(20,45), 20,20));
addMouseMotionListener(new CustomMouseMotionListener());
setFocusable(true);
setVisible(true);
grabFocus();
}
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
for (SomeObject object: objectsToDraw) {
g.drawRect((int)object.getOrigin().getX(), (int)object.getOrigin().getY(), object.getLength(), object.getHeight());
}
}
}
Instead of using your SomeObject class, i recommend using Shape or Area. The whole purpose of SomeObject seems to be to be turned into a Shape anyway, right? Not only that, but with an ArrayList of Shapes, you can eliminate creating rectangles for your shapes in every mouseMove.
BTW, years ago I put together a package for treating Areas like 1st class Components. You can see this here: https://sourceforge.net/p/tus/code/HEAD/tree/tjacobs/ui/shape/
(Start with AreaManager and AreaModel). Area has some advantages and disadvantages: Advantages: Easy to manage as a group, fairly easy to test if they are overlapping. Disadvantage: You lose the information about how the Area was constructed (ex. Polygon points, circle radius, etc)
You've already taken this a long way, so kudos to you. This answer is (a) responding to your question about efficiency, but also pointing you in some ways you could choose to go
There is more to SomeObject than just being drawn, so I think I can't just replace it with Shape,
Then you keep a Rectangle instance as part of your SomeObject class, instead of your Point and length/height variables.
Then you modify your methods to return the values from the Rectangle.
This will prevent you from continually creating new Rectangle instances, make the process more memory efficient and reducing garbage collection.
But, you should also be able to extend the Rectangle class and add your extra functionality in the same way that you extend JPanel to add custom painting logic.

Animating a shape in a JPanel

first questing for me here. Been searching forever but cant seem to find the answer.
Im working on a school assignment. Got given an ui and are supposed to make the different panels in it do different things in separate threads. Anyway, Im trying to make a triangle rotate inside one of the JPanels. I have managed to draw it and somewhat rotate it, but when I try to make a loop to update it it just blinks and then disappears again. Heres the code Ive written.
StartAssignment, starts the application
public class StartAssignment1 {
public static void main(String[] args) {
Controller controller = new Controller();
}
Controller, recieves calls from the ui and executes various functions in other classes
public class Controller {
private GUIAssignment1 gui = new GUIAssignment1(this);
private RotateShape rotateShape;
private Thread t1;
public Controller() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
gui.Start();
}
});
}
public void startT1(JPanel panel) {
rotateShape = new RotateShape(panel);
t1 = new Thread(rotateShape);
t1.start();
}
public void t1Shutdown() {
rotateShape.shutdown();
}
RotateShape, where Im trying to rotate the damned thing
public class RotateShape implements Runnable {
JPanel panel;
private volatile boolean t1Running = true;
public RotateShape(JPanel panel) {
this.panel = panel;
}
private void draw() {
Graphics2D g2 = (Graphics2D) panel.getGraphics();
g2.rotate(Math.toRadians(10));
g2.drawPolygon(new int[] {50, 100, 150}, new int[] {150, 50, 150}, 3);
}
public void shutdown() {
t1Running = false;
System.out.println("Shutdown");
}
#Override
public void run() {
while (t1Running) {
try {
draw();
Thread.sleep(500);
System.out.println("loop working");
panel.repaint();
panel.revalidate();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
I'm not certain because I don't have access to your GUI code and therefore cannot test it myself, but here's an idea:
It looks like you're rotating your Graphics2D object by a fixed amount on every re-render (i.e. every invocation of draw()). It's possible that the internal JPanel code initiates the Graphics2D at a default rotation before every re-render, which may be why your rotation only causes the shape to "somewhat rotate."
Maybe try storing a variable for the radian offset (e.g. double offset;) as a member field of the RotateShapeclass, incrementing it on every re-render, then calling g2.rotate(Math.toRadians(offset));?
Addendum 1:
The reason that it may draw on top of the previous render is because the Graphics2D object is not clearing the canvas between re-renders. One way to fix this is to "clear" the canvas at the beginning of the draw() method by filling a rectangle that covers the whole canvas.
Addendum 2:
When you call panel.repaint(), it triggers the paintComponent() method of the JPanel object. So, by calling repaint() and your own draw() method, you are doing two separate renders, which may cause some graphical errors. If you were able to extends the JPanel class and use that, you should define an override to replace draw():
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// TODO: add your rendering operations here
}
This way, calls to repaint() should trigger this method.

Resizing a 2D JavaFX game

I'm currently making my first tile-based game in JavaFX, and quite early on I've run into an issue. I want to make the size of the object in the game (including the map) resize with the window, so that you won't be able to see a larger part of the game if you resize your window.
I'm using a Scale class which calculates the size of tiles. To make it responsible to rescaling of the window, it uses properties. The viewport variable is a rectangle with the width and height of the window:
public class Scale {
private static DoubleProperty widthProperty, heightProperty;
private static DoubleBinding tileWidthProperty, tileHeightProperty;
public static void initialize(Rectangle viewport){
widthProperty = viewport.widthProperty();
heightProperty = viewport.heightProperty();
tileWidthProperty = tileHeightProperty = new DoubleBinding() {
{
super.bind(widthProperty, heightProperty); // initial bind
}
#Override
protected double computeValue() {
return Math.round(Math.max(widthProperty.get(), heightProperty.get())/32);
}
};
}
public static DoubleBinding x(int n){
return tileWidthProperty.multiply(n);
}
public static DoubleBinding y(int n){
return tileHeightProperty.multiply(n);
}
}
All objects base their size and position on these tileWidthProperty and tileHeightProperty variables, like so:
Tree tree = new Tree();
tree.widthProperty.bind(Scale.x(2));
tree.heightProperty.bind(Scale.y(3));
tree.xProperty.bind(Scale.x(12));
tree.yProperty.bind(Scale.x(2));
All tiles are scaled and positioned in the same way.
When first starting the game, there is little to no lag. However, when the window is resized and all the properties change, the client freezes. My question is: Is there any way to optimize this resizing? Should I do it in a different way than I am with these properties? If so, what would be most efficient?

Java efficient way to have multiple identical ArrayList elements

I am writing an game in java with Libgdx and have a question about how to have multiple instances of the same object in an ArrayList without abusing the garbage collector.
this is the code of my main game state. I am still really new, so I assume my coding habits are downright awful:
package com.frog.game;
import java.util.ArrayList;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
import com.badlogic.gdx.math.MathUtils;
import com.frog.entities.Frog;
import com.frog.entities.Grass;
import com.frog.entities.LilyPad;
import com.frog.gamestates.GameState;
import com.frog.managers.GameStateManager;
import com.frog.managers.Jukebox;
import com.frog.managers.Save;
import com.frog.managers.SimpleDirectionGestureDetector;
import com.frog.managers.SpeedManager;
import com.frog.game.Game;
public class PlayState extends GameState {
private SpriteBatch sb;
private Frog frog;
private BitmapFont font;
private ArrayList<LilyPad> lilypads;
private Grass grass;
private float hopY;
public boolean tmp;
private SpeedManager speedManager;
private float speed;
private float[] placement;
public PlayState(GameStateManager gsm) {
super(gsm);
}
#SuppressWarnings("deprecation")
public void init() {
speedManager = new SpeedManager();
lilypads = new ArrayList<LilyPad>();
sb = new SpriteBatch();
frog = new Frog();
frog.init();
grass = new Grass();
grass.init();
hopY = Game.SIZE * 0.8f;
placement = new float[] {
0,
Game.SIZE,
Game.SIZE * 2
};
addPad();
FreeTypeFontGenerator gen = new FreeTypeFontGenerator(
Gdx.files.internal("PressStart2P-Regular.ttf")
);
font = gen.generateFont((Game.WIDTH / 10));
gen.dispose();
Gdx.input.setInputProcessor(new SimpleDirectionGestureDetector(new SimpleDirectionGestureDetector.DirectionListener() {
#Override
public void onUp() {
// TODO Auto-generated method stub
}
#Override
public void onRight() {
frog.moveRight();
}
#Override
public void onLeft() {
frog.moveLeft();
}
#Override
public void onDown() {
}
}));
}
This is the method I use to add another lilypad at the top of the screen. I make it appear in one of three places randomly. Since the main function of these lilypads is to scroll down the screen, instances of the lilypad are being added and removed from the array non stop. I know this kills because each time I add a new lilypad, I have to run init() for it, otherwise I will get a nullpointerexception. init() instantiates a bunch of objects in order to make that lilypad render, such as textures, the default Y value etc. Is there any way I could run the init() method once for the entire arraylist, even though I am constantly adding & removing them? I have considered wrapping the same lilypads around the screen when they hit the bottom, so I would only need like 7, and then I wouldn't have to add or remove anymore, but would have to rework a big chunk of code for that, and consider it a last resort. The game already runs smooth, it just has a few barely noticeable stutters here and there that I would like to avoid.
private void addPad() {
lilypads.add(new LilyPad(placement[MathUtils.random(0, 2)], 300, false));
lilypads.get(lilypads.size() - 1).init();
}
public void update(float dt) {
speed = speedManager.speed(dt);
speedManager.update(dt);
// update grass
if(!grass.shouldStop()) {
grass.update(dt, speed);
frog.introPlay(speed);
}
// update lily pads
for(int i = 0; i < lilypads.size(); i++) {
lilypads.get(i).update(dt, speed);
This is where I call the addPad() method. It basically says if the last added pad is fully visible on screen, add the next.
if(lilypads.get(i).getY() < (Game.HEIGHT - Game.SIZE) && lilypads.get(i).past()) {
addPad();
}
// hop frog
if(lilypads.get(i).getY() < hopY && lilypads.get(i).past2()) {
// play hop
if(lilypads.get(i).getX() > frog.getX() - Game.SIZE / 2 && lilypads.get(i).getX() < frog.getX() + Game.SIZE / 2){
frog.incrementScore();
Jukebox.play("hop");
lilypads.get(i).splash();
frog.play(speed);
} else {
Jukebox.stopAll();
Save.gd.setTempScore(frog.getScore());
gsm.setState(GameStateManager.GAMEOVER);
return;
}
return;
}
if(lilypads.get(i).getY() < (-Game.SIZE)){
lilypads.remove(i);
}
}
}
public void draw() {
// draw grass
if(!grass.shouldStop()) {
grass.draw(sb);
}
// draw pads
for(int i = 0; i < lilypads.size(); i++){
lilypads.get(i).draw(sb, true);
}
// draw frog
frog.draw(sb, speed, true);
sb.setColor(1, 1, 1, 1);
sb.begin();
font.draw(sb, Long.toString(frog.getScore()),
Game.WIDTH * 0.92f -font.getBounds(Long.toString(frog.getScore())).width, Game.HEIGHT * 0.958f);
sb.end();
}
public void handleInput() {
}
public void dispose() {
sb.dispose();
Jukebox.stopAll();
}
Thanks for the support.
Use libGDX's custom collections for that. You can find all of them here.
In your case you want to use an Array. They are optimized to not cause the garbage collector to kick in, for example by re-using the iterators.
With a standard ArrayList the following piece of code in your render method would create a new Iterator in every frame.
for(LilyPad lilyPad : lilipads){
lilypad.draw(sb, true);
}
In general try to avoid using new whenever you can. This wiki article might help you with it and it explains how pooling works.
Another common trick to avoid GC especially when working with Vector2 or Vector3 is keeping a private final Vector2 tmp = new Vector2() and always only using tmp.set(x, y) to change it. This principle could also be applied to any other custom class that you are creating yourself to hold some data.

How do I add images to an image array?

I am making an applet that displays a random collection of 10 cards based upon 10 random integers.
My idea was to make an array of the 52 displayable cards (not including jokers) and display each cards from the array based upon the random integer like this (sorry i don't know how to use code blocks):
for (int i = 0; i<cards.length; i++) { //cards being my image array
//code that displays each image
}
But I ran into trouble trying to add images into the array and I don't know how to display the images from the array either.
Should I be adding them like this:
Image[] cards = new Image[52];
cards[0] = c1; //name of the Ace of Clubs, I had used getImage() to already get it
The preceding statements throw up errors saying it's an illegal start.
I also need help with displaying the images once I incorporate the images since I don't think:
System.out.println(cards[x]);
Will work with images.
Thanks in advance and sorry for seeming so complicated, I've tried to water it down as much as possible!
So, here's my silly take on it...
public class RandomCards {
public static void main(String[] args) {
new RandomCards();
}
public RandomCards() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new RandomCardsPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (Exception exp) {
exp.printStackTrace();
}
}
});
}
public class RandomCardsPane extends JPanel {
// A list is a collection of Image objects...
private List<Image> cardList;
private Image card = null;
public RandomCardsPane() throws IOException {
// My cards are stored in the default execution location of the program
// and are named "Card_1.png" through "Card_51.png"...
// You image loading process will be different, replace it here..
// ArrayList is a dynamic list (meaning it can grow and shrink
// over the life time of the list) and is backed by an array
// which shouldn't concern you, the only thing you really need to
// know is that it has excellent random access...
cardList = new ArrayList<Image>(51);
for (int index = 0; index < 51; index++) {
cardList.add(ImageIO.read(new File("Card_" + index + ".png")));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
card = cardList.get(Math.min((int)Math.round(Math.random() * cardList.size()), 51));
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (card != null) {
int x = (getWidth() - card.getWidth(this)) / 2;
int y = (getHeight() - card.getHeight(this)) / 2;
g.drawImage(card, x, y, this);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(225, 315);
}
}
}
I would also prefer ImageIO over Toolkit.getImage or even ImageIcon, apart from the fact that it guarantees the image data is loaded BEFORE the method returns, it also supports a greater number of image formats and is extendable via plugins...

Categories

Resources