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
Related
At the beginning I have to say that I'm learning java on my own and may make a stupidest mistake ever...
I'm facing a problem with FPS drop while drawing Bitmaps in my 2d game.
My game's map consists of Tiles 100x100px, and every tile has some surface graphic and may have something else (like tree, rock, or whatever).
I have 2 main drawing methods. Both looks similar. First is drawing map and second everything that is on it (trees etc.).
Here is a code. I'm limiting what is to be drawn in this method.
public static void drawMap(Canvas canvas, Player player, ArrayList<MapField> map)
{
int a = player.getShiftX()/Constants.TILE_SIZE;
int b = player.getShiftY()/Constants.TILE_SIZE;
for (int x = a-Constants.VISIBILITY_X; x<=a+Constants.VISIBILITY_X; x++)
{
if (x>=0&&x<=99)
{
for (int y = b-Constants.VISIBILITY_Y; y<=b+Constants.VISIBILITY_Y*2-1; y++)
{
if (y>=0&&y<=99)
{
map.get(x+y*100).update();
map.get(x+y*100).draw(canvas);
}
}
}
}
}
Then I'm calling:
public void draw(Canvas canvas)
{
canvas.drawBitmap(Graphics.TILES_BITMAP[tileId], null, rect, p);
}
When I limit "vision" to 3 squares each direction (drawing about 60 tiles) FPS is 60. As soon as I get rid of that limit (whole screen is to be drew - about 250 tiles) FPS are dropping to 27-30 which makes game unplayable.
It is normal behaviour? Is Java that limited?
Or just maybe I made a mistake here?
Full code to be seen here (if anyone care to check):
gitlink
Someone told me that with that amount of graphics i should already use some OpenGL and suggested me to learn LibGDX for example. But as for me, pure java is more elegant :)
Ok, i've managed to solve this issue. It would only for for API26+ though.
Instead of just:
canvas = this.surfaceHolder.lockCanvas();
i've put:
canvas = this.surfaceHolder.lockHardwareCanvas();
And now again i have 60FPS+ :)
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 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.
What is the best way to render in game loop on android?
When I'm rendering in java I know how to do this. I create buffer strategy, than graphics, I draw, than dispose and than flip buffer. Is there Andorid equivalent of that?
I looked into view, but it doesn't work correctly when I try draw a lot. I looked into SurfaceView than, but I can't understand how I can refresh drawing on it. invalidate breaks the loop, postInvalidate doesn't work. I lock canvas and draw on it, and than unlock and post in "create" method from surface view (locking canvas in loop doesn't work, only white screen in app appears). So I don't get it.
What's the most efficient way to render heavily in Android?
The common approach to render within a SurfaceView is through its SurfaceHolder.
Usually you'll get a Canvas through the holder and draw on it:
SurfaceHolder surfaceHolder = surfaceView.getHolder();
Canvas canvas = surfaceHolder.getSurface().lockCanvas();
//draw in the canvas
canvas.drawPoint(...);
surfaceHolder.unlockCanvasAndPost(canvas);
The correct approach is to loop all the code between the lockCanvas() and unlockCanvasAndPost() (inclusive) in a separate thread (the render thread) and control the framerate with a Thread.sleep() inside the loop.
EDIT:
There are many ways to control the FPS of the render thread, this is a basic one, just set the wanted FPS on a constant:
public class RenderThread extends Thread {
private boolean running;
private final static int MAX_FPS = 30;
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
#Override
public void run() {
long beginTime;
long timeDiff;
int sleepTime;
sleepTime = 0;
while (running) {
beginTime = System.currentTimeMillis();
//RENDER
// How long did the render take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
}
}
}
There are lots of references if you search for game loops.
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.
}