I'm creating a galaga clone in java. I am trying to understand the basics of a game loop and I have a couple problems/ questions. I have an update method that changes the positions of all the game objects and I have a draw method that draws all the updated positions of said objects. Should I ever call the paint() method? How do I repaint the objects (clear the screen then draw everything again)? Also, what is the best way to call my update() method at a fixed rate? Thanks for any input.
public Galaga() throws IOException
{
//set the background
setBackground(Color.BLACK);
//create a controller
c = new Controller();
addKeyListener(c);
setFocusable(true);
//create a player
player = new Ship(40,50);
running = true;
}
public void paint(Graphics g)
{
super.paint(g);
}
//game loop
public void run()
{
while(running)
{
draw(getGraphics());
if(c.right())
player.move(1, 0);
}
}
//update logic
public void update()
{
}
//renders all objects
public void draw(Graphics g)
{
player.draw(g);
}
I have an update method that changes the positions of all the game objects
Yep, good
and I have a draw method that draws all the updated positions of said objects
Ah...not so good
Should I ever call the paint() method?
No, you should never have to do this, in fact, you shouldn't be call getGraphics either, this is not how painting is done.
You would normally call repaint to trigger a new paint cycle.
Have a look at Painting in AWT and Swing and Performing Custom Painting for how painting works in Swing - I'm assuming you're using Swing based on the methods you're using
Also, beware, Swing is single thread - see Concurrency in Swing for more details and How to use Swing Timers for more a simple solution.
If you still want to use a Thread, beware, that painting can occur at any time for any number of reasons, which are beyond your control, so you need to be aware of the risk of dirty paints when update states
If you want more control over the paint/update cycle, then you should have a look at BufferStrategy and BufferCapabilities
There is really good code for a game-loop found here. Thought I would just share this link to save people time!
Use the loop with the fixed time-step. The code can be greatly simplified, as the code creates a bouncing ball example to show the interpolation of frames for smoothness and the benefits of that type of loop.
I have created a pastebin with my simplified version of the code from the website above. I separated the game loop into separate classes, and added more comments in the code to show what is happening. This makes it easier to create your own game without going through the whole game loop to see what's happening and how it works.
Let me know any suggestions to my pastebin version, and I will update it :)
Folder layout:
src
|
|--app
|
|--game
| |
| |--logic
| | .--Game.java
| |--loop
| .--GameLoop.java
| .--GamePanel.java
|
.--App.java
.--Window.java
Related
I have a java App and I am Loading 3 Gif files into BufferedImage Arrays and want to display them.
I have a simple Thread that is executed every e.g. 16ms (~ 60fps) using Thread.sleep()
In this method, I am calling the paintcomponent method to draw on the GUI component.
But I don't want the Thread (Runnable) to wait for the Method to end rendering because the Delay is more than 16ms afterward - even if it is not measurable.
Here's a snippet inside my Runnable JLabel (Currently the fast/efficient method)
#Override
public void run() {
while (active) {
repaint(); //Takes 0ms
i++; // Counter used inside paintComponent()
Thread.sleep(speed * multiplier));
if (i + 1 == frames.length) {
i = 0;
}
}
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(frames[i], 0, 0, x, y, observerFrame);
g.dispose();
}
I tested it and the repaint methods take 0ms. Probably not measurable.
But isn't it still unprecise? Or maybe inefficient?
So the (simple) math is done inside the Thread. But how do I ensure that the timing is precise? I mean it is still precise enough but it still depends on the speed of the pc
After the Answer of #Reto Höhener I did some research and tried another method.
But it is still inefficient and lags when rendering the images (BufferedImages btw):
This is one big attempt I did where I thought that it would be better:
Timer timer = new Timer(16, this);
Executor executor = Executors.newCachedThreadPool();
//called by executor
#Override
public void run() {
i++; // Counter used inside paintComponent()
Thread.sleep(speed * multiplier));
if (i + 1 == frames.length) {
i = 0;
}
}
//called by timer & runs on EDT
#Override
public void actionPerformed(ActionEvent e) {
repaint();
executor.execute(this); //Costs no time and has enought time to complete
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(frames[i], 0, 0, x, y, observerFrame);
g.dispose();
}
So this way I ensure that
The repaint is done in the EDT
The math is done by another Thread using executors
Even if I was proud after compiling and running it the results made me cry...
With the first method, the JVM uses ~10% of the CPU. Everything is fine and running on the fps for all 3 Frames.
The second try uses less CPU but is bad performing.
I don't know why but the GUI is responsive but the rendering is slow almost 3x slower.
The goal is to render images in 60 frames per second without lag.
The point here is that I tried an attempt where I thought that it is conceptually better but it did even worse.
So my question is if there is a better and more precise way to achieve this.
Otherwise, I will stay at the Simple Thread based solution.
regards
Nur1
First it would probably be helpful to learn just a little bit about the Java Event Dispatching Thread (or AWT Event Queue) concept.
My suggestion would be to use one thread that is only responsible to call a .repaint()-method on your component. Instead of rolling your own thread, consider using javax.swing.Timer for this, it already uses a thread behind the scenes, and is able to coalesce multiple events if they arrive too quickly.
Then use another thread that is only responsible for calculating the information required to paint. One approach is to render into a buffer image. The .paint()- or .paintComponent()-method of your display component then only has to paint that image.
The problem:
I've been creating a Snake game in Java and everything is working smoothly. The only problem I've having is displaying the score. Each time the snake "eats" an apple, the score increases by 10. Instead of displaying the current score, it simply writes over the present score without actually erasing it. When I attempt to erase the content before re-displaying, I get an error and nothing appears, other than my background colour.
I have other Graphics components, including a paint() that I do NOT want affected.
My attempt (theoretical):
I display the score using a Graphics object that calls
drawString("Current score: " + currScore, 0, (Constants.TOTAL_HEIGHT + 15));
I figured I have to call a clearRect() method, which makes sense to me. Unfortunately, I get a NullPointerException at the line specifying clearRect().
I'm not using Graphics2D just so I can first be familiar with Graphics. If resolving this issue involves Graphics2D, I have no problem using it.
My attempt (the method in question):
public void displayScore(Graphics g) {
//clearScore.clearRect(0, getY(), getWidth(), getHeight());
//g.clearRect(0, getY(), getWidth(), getHeight());
g.drawString("Current score: " + currHighScore, 0, (Constants.TOTAL_HEIGHT + 15));
}
In the code, clearScore is a Graphics object declared in the class outside of any methods. My reason for using this was to avoid affecting the other graphics. I attempted each of the clearRect() lines and both produced the same NullPointerException, which is why I have them commented out.
(Constants.TOTAL_HEIGHT + 15) is responsible for assigning the Y-coordinate.
The Error Message:
Exception in thread "Thread-3" java.lang.NullPointerException
at com.nmw.view.CanvasFrame.displayScore(CanvasFrame.java:149)
at com.nmw.view.CanvasFrame.drawAll(CanvasFrame.java:43)
at com.nmw.view.CanvasFrame.run(CanvasFrame.java:64)
at java.lang.Thread.run(Thread.java:722)
drawAll is where I call all of my methods that take in Graphics to draw a segment of the game.
Apologies for the lengthy question =/
Your problem is that you are likely trying to draw with a null Graphics object. This usually occurs if you are not drawing within the paint(...) method if AWT or paintComponent(...) if Swing. The solution,
draw within one of those two methods (depending on the library you're using).
Always call the super's method within your painting method.
Or use a Label (AWT) or JLabel (Swing) to display the data.
To be blunt, your statement: "I have other Graphics components, including a paint() that I do NOT want affected." -- is an unrealistic requirement if this is an AWT project and if you don't want to use a Label.
And this statement:
In the code, clearScore is a Graphics object declared in the class outside of any methods. My reason for using this was to avoid affecting the other graphics.
Is not how you should do drawing, ever. You're finding out now why. If you happen to get a Graphics context during the running of the program, use it to assign Graphics to your class field, and then try to use that class field, you'll often get the NPE like you're seeing because a Graphics object thus obtained is not long-lasting.
If you need more in-depth help, please post your actual assignment requirements and more code. In particular your drawing methods. Best would be to create and post an sscce.
Edit
You state in comment:
My Snake game is an independent project I've been doing to learn more about Graphics. I've only been using the AWT library, however, I'll try using a Label. Thank you for your response and explanation of why I got the NullPointerException
If this is for your own project and not a school assignment, then I think that you will want to avoid using AWT and instead use Swing. It is much more powerful and flexible than AWT. There really is no reason why you'd want to use AWT now adays.
This question already has answers here:
How do i get a graphics object in Java?
(2 answers)
Closed 9 years ago.
Im making a basic space invaders game. I got all the resources from the LWJGL .zip file (Im not using LWJGL librarys to create my game, just got the pictures, etc. from it.) Anyway, whenever i press "space" on my keyboard, my KeyListener creates a new bullet that my ship fires. However, i dont know how to draw the bullets image, since my KeyListener doesnt pass a graphics object, and you need one to draw a image. The code thats causing the problem is the "drawImage" method in the "Shot" constructor. Heres my code:
public class KeyTyped{
public void keyESC(){
Screen.isRunning = false;
}
public void keyLEFT() {
Screen.shipPosX -= 10;
}
public void keyRIGHT() {
Screen.shipPosX += 10;
}
//This is automatically called from my KeyListener whenever i
//press the spacebar
public void keySPACE(){
if(!spacePressed){
ShotHandler.scheduleNewShot();
}else{
return;
}
}
}
public class ShotHandler {
public static int shotX = Screen.shipPosX;
public static int shotY = Screen.shipPosY + 25;
public static void scheduleNewShot() {
//All this does is set a boolean to 'false', not allowing you to fire any more shots until a second has passed.
new ShotScheduler(1);
new Shot(25);
}
}
public class Shot extends ShotHandler{
public Shot(int par1){
//This is my own method to draw a image. The first parameter is the graphics object that i need to draw.
GUI.drawImage(*????????*, "res/spaceinvaders/shot.gif", ShotHandler.shotX, ShotHandler.shotY);
}
//Dont worry about this, i was just testing something
for(int i = 0; i <= par1; i++){
ShotHandler.shotY++;
}
}
}
Thanks guys! Any help will be appreciated!
(This is a re-post because the last time i posted this, i didnt get a sufficient answer, at least for my skill level)
Start by taking a look at
Painting in AWT and Swing
Performing Custom Painting
You may also find Concurrency in Swing of some usefulness.
The basic gist is you should have some kind of model which describes the state of the game play at any moment in time. This model is updated outside the content of the Event Dispatching Thread, normally in some kind of background thread, where the time it takes to perform the updates doesn't effect the ability for Swing to continue running and remain responsive to the user.
You will need to override the JPanel's paintComponent method. In here, you would paint the model state to the screen.
There are a bunch of other techniques which could be discussed, but lets get this started
Examples...
Java Bouncing Ball
Drawing 2 Balls to move in different direction on Java but one disappeared
Multiple bouncing balls thread issue
I am trying to make ball gradually move
Java ball object doesn't bounce off of drawn rectangles like it's supposed to.
https://stackoverflow.com/questions/15858623/how-to-use-a-swing-timer-to-animate/15859932#15859932
the images are not loading
Swing animation running extremely slow
How to make line animation smoother?
I'm planning to write a simple spaceshooter. I have read that the repaint() method is only a request, and it doesn't execute every time it's called. I believe I'm noticing the effects of this, as my spaceship tends to lag ever so slightly when I'm moving it. Currently I'm simply drawing my ship in a a JPanel's paintComponent() method, and keep calling repaint() on regular intervals (my panel's also Runnable). Seeing as repaint() may potentially screw me over, I'm trying to find a way to work arround it, however I've ran out of ideas. The code I have so far:
private void renderGraphics() {
if (MyImage == null) {
MyImage = new BufferedImage(getPreferredSize().width,
getPreferredSize().height, BufferedImage.TYPE_INT_RGB);
}
MyGraphics = MyImage.getGraphics();
MyGraphics.setColor(Color.BLACK);
MyGraphics.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
MyGraphics.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);
}
The idea was to create my own graphics, and then make the JPanel draw it, and keep calling this instead of repaint() in my run() method, however I have no idea how to do that. I'd appriciate any input on the matter.
There are multiple ways to approach this.
The best is probably to use BufferStrategy and draw to that, of which I have included a code snippet that should work for you.
You can take this one step further and abandon Swing altogether, just using Frame/BufferStrategy. There is a fully working example (from which the code snippet was taken and adapted) in my question here:
AWT custom rendering - capture smooth resizes and eliminate resize flicker
Anyway, here is an implementation BufferStrategy that you should be able to just drop in:
// you should be extending JFrame
public void addNotify() {
super.addNotify();
createBufferStrategy(2);
}
private synchronized void render() {
BufferStrategy strategy = getBufferStrategy();
if (strategy==null) return;
sizeChanged = false;
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
MyGraphics draw = strategy.getDrawGraphics();
draw.setColor(Color.BLACK);
draw.fillRect(0, 0, getPreferredSize().width, getPreferredSize().height);
draw.drawImage(ship.getImage(), ship.getCurrentX(), ship.getCurrentY(), null);
draw.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
any drawing will still be performed in the Swing Thread, so no matter what you try work around, it wont help.
Make sure you are not doing any lengthy calculations in the swing thread, this may be stopping repaint from being executed as soon as it needs to be executed
Separate all the logic into 2 parts. Static and Dynamic. (e.g. sea and moving ship. Ship changes shape/location on a static image of sea)
Draw static content in an image once and use the image in your paintComponent(). Call dynamic parts painting after the static image.
Use setClip() to restrict repainting areas.
Calling repaint without any arguments means that the whole panel is repainted.
If you need to repaint parts of the screen (the spaceship has moved to a different location) you should make shure that only those parts of the screen are repainted. The areas that stay the same should not be touched.
Repaint takes coordinates of a rectangle that should be repainted. When moving the ship you should know the old coordinates of the ship and the coordinates the ship should move to.
repaint( oldShipCoordinateX, oldShipCoordinateY, shipWidth, shipLength );
repaint( newShipCoordinateX, newShipCoordinateY, shipWidth, shipLength );
This is usually much faster than calling repaint() without arguments. However you have extra effort to remember the last position of the ship and must be able to calculate the new position of the ship.
See also: http://download.oracle.com/javase/tutorial/uiswing/painting/index.html - especially step 3
Just for code that you post here:
1/ if you want to display Image/ImageIcon, then the best and easiest way is to Use Labels
2/ as you mentioned Runnable{...}.start(); Swing is simple threaded and all output to GUI must be done on EDT; you have to look at Concurrency in Swing, result is that all output from BackGround Task(s) must be wrapped into invokeLater(), and if is there problem with perfomancie then into invokeAndWait()
3/ if you be switch (between JComponents)/add/delete/change Layout then you have to call revalidate() + repaint() as last lines in concrete code block
EDIT:
dirty hack would be paintImmediately()
I have read that the repaint() method is only a request, and it doesn't execute every time it's called
It consolidates multiple repaint() requests into one to be more efficient.
I believe I'm noticing the effects of this, as my spaceship tends to lag ever so slightly when I'm moving it.
Then post your SSCCE that demonstrates this problem. I suspect the problem is your code.
Regarding the solution you accepted, take a look at Charles last posting: Swing/JFrame vs AWT/Frame for rendering outside the EDT comparing Swing vs AWT solutions.
We can override the paintComponent(Graphics g) method of JComponent.
It is called automatically to refresh the screen.
What can I do that is called regularly, for example every 100 ms.
Is this possible?
You can request that the component repaint itself with a call to repaint(). You most certainly do not want to call repaint every 1 ms though; no monitor in the world could display at 1000 Hz, and no human eye would distinguish that either. Furthermore the repaint calls would be coalesced into a few separate ones and you would not achieve that number of repaints.
What are you trying to achieve? I assume it's some sort of animation; if so you should start googling for "java 2d swing animation" or some variant of that. Try this 2d java tutorial for starters.
Maybe your purpose is refreshing the component every certain ms?
If so, there's a few options available.The use of Timer is a first option. Example here.
The option most people use is Thread animation. Here is an example.
Also there's a timing framework available to download. I can't find an available link, but just search for it on the internet.
Hope this helps.