I am messing around with my custom built game engine, which uses Java2D APIs to draw on a Canvas (Active rendering).
The thing that I noticed is that a simple scene with a square moving around gets rendered more smoothly if I either actively keep pressing keys on my laptop's keyboard or move my mouse around (inside the frame boundaries). If I don't do anything the movement feels sluggish.
My game engine uses fixed timestep rendering where multiple updates can occur per single drawing.
My frame structure: JFrame has a child Canvas which employs a thread to perform updating / rendering.
Operating system is Linux, can it be a focus problem? FPS counter reports the same fps in both cases.
My loop code is this:
while(active) {
g = (Graphics2D) bs.getDrawGraphics();
now = System.currentTimeMillis();
double delta = now - prev;
while(delta >= 0) {
update(dt);
updateTicks++;
delta -= dt;
}
render(g);
bs.show();
if(System.currentTimeMillis() - fpsTimer > 1000) {
fpsTimer += 1000;
System.out.printf("FPS: %d\n", updateTicks);
updateTicks = 0;
}
prev = now;
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I don't think that's the issue though.
update: there's a slight fps drop: 5/7 updates per second if I don't provide input to the frame..Something is going on behind the scenes..
Check on which thread is your input logic processing.
Related
So I was messing with Swing for the first time in a while and I came across a strange issue. So I am adding "shapes" to a list every so often, and then in the paintComponent method of a JPanel I am looping through the list and drawing the shapes. I also draw a shape outside of the for loop for testing purposes.
What happens is the shapes in the for loop will jump around the screen randomly. This only happens when the shapes are drawn in this loop.
What I have tried already:
Updating graphics drivers for both the integrated GPU and discrete GPU
Using java.util.Timer instead of Swing Timer
Using Thread/Runnable
Using things other than ArrayList, such as LinkedList, Vector, and a normal Array.
Trimmed literally everything out of my code except the basics, which is what we're left with here. I was drawing more complex things before when I noticed it.
Changed the timing (PERIOD variable, in millis). It will get worse if I increase or decrease it.
Changed from using System time in milliseconds to the System time in nanoseconds, converted to milliseconds. I know this should be the same but I was running out of ideas.
Here is a gif of the problem (15 seconds long):
image
You'll notice that the small squares will jump around at random intervals. This should not occur. I'm just trying to "spawn" a square at random coords.
Here is the code in a pastebin:
code
I have included all 3 classes in this order: the JPanel class, the Main class (extends JFrame), and the shape class
If any of the links don't work, inform me and I will promptly post other links.
Thanks.
This setup ...
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (this.startTime == -1L) {
this.startTime = Main.timeMillis();
}
final long timeDiff = Main.timeMillis() - this.startTime;
if (this.circlesIndex <= 19 && timeDiff > 2000) {
final int randX = this.rand.nextInt(this.WIDTH);
final int randY = this.rand.nextInt(this.HEIGHT);
this.testShapes.add(new TestShape(randX, randY));
this.startTime = Main.timeMillis();
}
for (TestShape ts : this.testShapes) {
ts.draw(g);
}
g2.setColor(Color.gray);
g2.fill3DRect(350, 400, 100, 60, true);
}
#Override
public final void actionPerformed(final ActionEvent event) {
x++;
if (x > WIDTH) {
x = -50;
}
repaint();
}
is wrong.
Paint is for painting - you should not modify the state of the UI from inside any paint method, do this within your ActionListener. The problem is, your component can be painted for any number of reasons, many of which you don't control or know about
I've been trying to make a simple game in pure java and I've encountered a problem in drawing. I'm trying to keep a relatively high frame-rate but having issues with the fact that JFrame.repaint() cannot be 'forced' and is merely a request to have the frame redrawn at the next available opportunity. As a result, the below code's frame-rate is terrible. However, (and this is the strange part) it seems to only be terrible when my mouse isn't moving. If my mouse is moving and over-top of the window, the frame rate is fast and crisp.
I've tried various online suggestions and even compiled examples on how to do this and they all seem to have the same issue with the frame rate dropping dramatically when my mouse isn't moving over the window.
(I'm using Linux, if that matters)
Any and all help is much appreciated!
import java.awt.*;
import javax.swing.*;
public class Test extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(300, 300));
frame.setVisible(true);
frame.getContentPane().add(new Test());
for (int k = 0; k < 1_000_000; k++) {
frame.repaint();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
frame.dispose();
System.exit(0);
}
private int k = 0;
public Test() {
super();
}
#Override public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
int height = (int) (((k * 0.01) % 1) * getHeight());
g.drawLine(
0, height,
getWidth(), height
);
k++;
}
}
After far too much research, it turns out that java does not sync / flush the display buffer automatically on many Linux systems. All the repaints and such were correct, however the display buffer was not flushing, thus creating the strange lagging effect.
The Solution:
Toolkit toolkit = Toolkit.getDefaultToolkit(); /* get AWT toolkit */
/* initialize things */
...
while (your_loop) {
/* run your logic */
...
/* paint everything */
...
toolkit.sync(); /* force display buffer to flush */
}
Thank you all for your input
The question is not simple. The code below has not been tested, then just to give you the idea... In the next lines, AWT is the underlying of Swing.
First, you have to keep your paintComponent() very fast (indeed!). This is the first requirement. Basically, for 60 fps, you must draw in less than 15 milliseconds. Forget transaparency and other stuff (works badly on Windows, I don't know for Linux). Try to save calculations when possible.
Second, Execute everything else in a different thread. This is the way I use for my own program. Note every call to AWT (included Swing, of course) must be encapsulated in a call to EventQueue.invokeLater() to ensure you are running stuff in the AWT thread because setting a label MUST NOT be done outside the AWT thread.
Do not forget to create a thread when you receive an input from AWT that takes time!
Third, replace your loop by a timer like
new Timer("Drawer", true).scheduleAtFixedRate( new TimerTak(){
public void run(){
frame.repaint();
}
},
100, // Start in 100 ms
(int)(1000 / 60)); // 60 is the frame rate.
Everything should work smoothly. For the frame count k, use the following:
// You should initialize just before you create the timer...!
static private long startedAt = System.currentTimeMillis();
#Override public void paintComponent(Graphics g) {
super.paintComponent(g);
// Microseconds since the game started.
long k = (System.currentTimeMillis() - startedAt);
// Increment only one by frame (60 fps)
k = (int)((double)k * 60 / 1000.0)
// Draw the game...!
}
That's all. Note some frames can be dropped if the computer is not enough powerful (or CPU intensive is required, or garbage collector...). But, when possible, your game will run at a maximum of 60 fps.
Bonus: if you increment a value each time you go through the paintComponent(), you can find the number of frames dropped or the average number of frames per second really displayed since the start of the game.
I am making a game and all is going well except for the game loop. I am using a SurfaceView and drawing 2D Sprites (Bitmaps). Currently the game is a ship moving through an asteroid field. The ship stays in the center of the screen and the phone is tilted in either direction to move the the asteroids (asteroids change location instead of player). New asteroids spawn as old ones fall off the screen creating an infinite asteroid field feeling.
Running on my Nexus 5, I noticed that after about 3 seconds, the asteroids moving down the screen became choppy, despite my game loop set to run at 60fps. Here was the code:
#Override
public void run() {
Canvas canvas;
long startTime;
long timeTaken;
long sleepTime;
while (running) {
canvas = null;
//Update and Draw
try {
startTime = System.nanoTime();
canvas = gameView.getHolder().lockCanvas();
//Update
gameModel.update(gameController.getPlayerShots(), gameController.getCurrentTilt());
//Game Over?
if (gameModel.isGameOver()) { running = false; }
//Draw
synchronized (gameView.getHolder()) { gameModel.drawToCanvas(canvas); }
}
finally {
if (canvas != null) {
gameView.getHolder().unlockCanvasAndPost(canvas);
}
}
//Sleep if needed
timeTaken = System.nanoTime() - startTime;
sleepTime = FRAME_TIME - timeTaken;
try {
if (sleepTime > 0) {
sleep(Math.abs((int)sleepTime/1000000), Math.abs((int)sleepTime % 1000000));
}
}
catch (InterruptedException e) {}
}
//Load menu after game over
if (gameModel.isGameOver()) {
gameController.launchContinueMenu();
}
}
I thought the problem may be that my model was doing the drawing to canvas and that I wasn't using my surfaceview's onDraw() method. Refactored to use onDraw() and same result as before. I printed out the sleep time of each frame and my thread was consistently sleeping for 5-10ms (16.667ms in a frame), so my nexus was not short on computing power.
I read on stackoverflow that I shouldn't use synchronize() on the view holder. Still no luck.
Then I read again on stackoverflow that using Thread.sleep() may be causing the issue because it is not always accurate. I did a major refactor as shown below.
#SuppressLint("WrongCall")
#Override
public void run() {
Canvas canvas;
while (running) {
canvas = null;
//Update and Draw
try {
canvas = gameView.getHolder().lockCanvas();
//Calc and smooth time
long currentTimeMS = SystemClock.uptimeMillis();
double currentDeltaTimeMS;
if (lastTimeMS > 0) {
currentDeltaTimeMS = (currentTimeMS - lastTimeMS);
}
else {
currentDeltaTimeMS = smoothedDeltaTimeMS; // just the first time
}
avgDeltaTimeMS = (currentDeltaTimeMS + avgDeltaTimeMS * (avgPeriod - 1)) / avgPeriod;
// Calc a better aproximation for smooth stepTime
smoothedDeltaTimeMS = smoothedDeltaTimeMS + (avgDeltaTimeMS - smoothedDeltaTimeMS) * smoothFactor;
lastTimeMS = currentTimeMS;
//Update
gameModel.update(smoothedDeltaTimeMS / 1000.0d, gameController.getCurrentTilt());
//Game Over?
if (gameModel.isGameOver()) { running = false; }
//Draw
// synchronized (gameView.getHolder()) {
// gameModel.drawToCanvas(canvas);
gameView.onDraw(canvas);
// }
}
//Release canvas
finally {
if (canvas != null) {
gameView.getHolder().unlockCanvasAndPost(canvas);
}
}
}
//Load menu after game over
if (gameModel.isGameOver()) {
gameController.launchContinueMenu();
}
}
This new approach should just draw as fast as possible using delta time to draw next frame instead of a set increment as before. Still no luck.
I changed my bitmap locations from Rect's to a new positions class I created that uses doubles instead of ints to simulate sub-pixel movement. Still no luck.
Recap of things I've tried:
Use onDraw() instead of gameModel drawing to canvas
Don't use synchronize() on the view's holder
Using time since last update to update next frame instead of advancing one frame each update() call.
Remove Thread.sleep()
Sub-pixel movement
After all this, I noticed that the game run fine while the phone was charging. 3 seconds after I unplug the phone, the game becomes choppy again. Then I noticed that if I tap the screen when the game becomes choppy, everything smoothes out again for another 3 seconds. I'm guessing that there is some kind of battery saving feature that is affecting my SurfaceView performance? What is going on here, and how can I disable it? This is quite maddening...
I tested out my first game loop again out of curiosity (1st code block) and tapped the screen while playing and everything ran smoothly. Man... all that work adding complexity for nothing. I guess it's a good learning experience. Any ideas to how I can keep my SurfaceView running at full speed? Help is greatly appreciated.
:)
Your observation is correct. In short, the power management in the N5 aggressively throttles the power down. If your finger is in contact with the screen, the clock speeds are increased to ensure that interactions are smooth. (For more, see my answer to this question.)
The best way to handle this is to recognize that you will need to drop frames sometimes. The "Record GL app" activity in Grafika uses a simple trick with Choreographer to do this. See also this article for some additional thoughts about game loops.
I think you should try to make your game more battery saving,darker the game(using background layout #000) more power for game ,
also you can use Intent flags like clear top to kill previous activity for saving battery , clearing ram(avoid out of memory)
but for your situation you can use wakelock
The code below should make a shape flash twice, I triple checked the methods from root and I'm 99% sure it's that those methods are correct (I will post that code if needed though). What's the best way to make the current state of root pause on screen for a few seconds?
noLoop();
root.setVal(newVal);
root.highlight(0,255,0);
root.setopacity(200);
redraw();
try {Thread.sleep((long)1500);}
catch (InterruptedException ex) {println("Error!");}
root.setopacity(0);
redraw();
try {Thread.sleep((long)1500);}
catch (InterruptedException ex) {println("Error!");}
root.setopacity(200);
root.clearHL();//just to make sure I repeated these methods
root.highlight(0,255,0);
redraw();
try {Thread.sleep((long)1500);}
catch (InterruptedException ex) {println("Error!");}
root.clearHL();
redraw();
loop();
return root;
You can only have one thread doing the drawing, and if you jam that thread up with sleep etc, it will "hang" until it gets a chance to get out of your code and back to rendering code inside the JRE.
There are plenty of tutorials around about it, Google is your friend!
e.g.: http://www.java-tips.org/java-se-tips/java.awt/how-to-create-animation-paint-and-thread.html
Think of it as you drawing onto a page, and every now and then the page is pulled out of your notebook to be displayed. Doesn't matter if you take 10 seconds to draw a circle, then rub it out. All that matters is what is on the page when it gets displayed.
I'm not sure if i got your problem, and the code is not runnable, but...maybe you need a simpler approach? A little timer made by yourself? The thing is that draw() executes all instructions before rendering a frame at the end of draw(). So if you stop draw() it will pause, without doing any draw, and then continues making all changes and drawing at the end. I mean if I do:
draw(){
fill(0);
ellipse(10,10,10,10);
delay(1000);
fill(255,255,0);
ellipse(10,10,10,10);
}
I will never see the black ellipse as it is covered by the yellow one before render takes place...at the end of draw. But the program is gonna hang for one second every frame...
So maybe a simple timer could do it for you. Here a general sample of a timer you could try to adapt for your needs:
PFont font;
String time = "000";
int initialTime;
int interval = 1000;
int times;
color c = color(200);
void setup() {
size(300, 300);
font = createFont("Arial", 30);
background(255);
fill(0);
smooth();
//set the timer as setup is done
initialTime = millis();
}
void draw()
{
background(255);
//compare elapsed time if bigger than interval...
if (millis() - initialTime > interval)
{
//display the time
time = nf(int(millis()/1000), 3);
// reset timer
initialTime = millis();
//increment times
times++;
}
// an arbitrary ammount
if (times == 3) {
//do somethng different
c = color(random(255), random(255), random(255));
// reset times
times = 0;
}
//draw
fill(0);
text(time, width/2, height/2);
fill(c);
ellipse(75, 75, 30, 30);
}
I'm trying to show the famous mouth opening/closing animation of the pacman character in a throwaway pacman game I'm making to teach myself game programming.
What I'm doing is drawing the open mouth image, then redrawing the closed mouth image at the exact same (x/y) location. But this doesn't work, and I just see the closed mouth animation all the time.
If I put this in a loop, the system just freezes and you see flickering where the open mouth image this, but you don't see the images being replaced.
I've tested and made sure that both images are being loaded correctly and as expected.
Here's my startAnim() function, its called when you double click at the applet:
public void beginGame() //Called from engine.java
{
isRunning=true;
repaint();
pacman.startAnim();
}
public void startAnim() //In different class, pacman.java
{
Image orig;
while (engine.isRunning)
{
orig=this.getCurrentImg();
draw(engine.getGraphics());
this.setCurrImg(currImg2);
this.draw(engine.getGraphics());
this.setCurrImg(orig);
this.draw(engine.getGraphics());
try
{
Thread.sleep(100);
}
catch (InterruptedException e) {}
}
}
public void draw(Graphics g) //Called from engine.paint()
{
g.drawImage(getCurrentImg(), getX(),
getY(), engine);
}
you have to sleep between the 2 images. otherwise you will only see the last image painted.
eg.
while( running )
{
image 1
draw
sleep
image 2
draw
sleep
}
something like this:
public void startAnim() //In different class, pacman.java
{
final int cnt = 2;
Image[] imgs = new Image[ cnt ];
int step = 0;
imgs[ 0 ] = closedMouthImage;
imgs[ 1 ] = openMouthImage;
while ( engine.isRunning )
{
this.setCurrImg( imgs[ step ] );
draw(engine.getGraphics());
step = ( step + 1 ) % cnt;
try
{
Thread.sleep(100);
}
catch (InterruptedException e) {}
}
}
As sfossen said, you need a delay between drawing the images.
A couple of other things to consider.
For smooth animation, you probably need more than just the "mouth open" and "mouth closed" images. You need two or three intermediate images.
To make resource management easier, you might want to put all of your animation frames together in a single, wide image, that will look like a "filmstrip". Then, to draw a frame, you use the (x, y) offset for the frame your interested in.
Finally, if you draw all the frames each time you go through the main loop of your program, Pac-Man will do a complete chomp every time you move him. You should think about drawing just one frame each time through the main loop and using a variable to track which frame you're on.
Example (pseudocode)
frameWidth = 32
frameIndex = 0
while(running) {
// Draw just the frame of your animation that you want
drawImage(pacmanX, pacmanY, filmStrip, frameIndex * frameWidth, 0, frameWidth, frameHeight)
frameIndex = (frameIndex + 1) % frameCount
// Update position of pacman & ghosts
// Update sound effects, score indicators, etc.
}