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.
Related
My first posted question here, so I'm begging forgiveness in advance for my question-formatting inexperience.
My 2D graphics game normally runs perfectly on my system. But when Norton Internet Security’s ccSvcHst.exe process is running with very high CPU utilization, it has strange interactions with the game. I have identified the specific lines of Java code which are impacted.
// Graphics setup, just trying to give some background
GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice device = e.getDefaultScreenDevice();
GraphicsConfiguration grafConf = device.getDefaultConfiguration();
Frame f = new JFrame(grafConf);
device.setFullScreenWindow(f);
f.createBufferStrategy(2);
BufferStrategy buffStrat = f.getBufferStrategy();
Graphics grafx = buffStrat.getDrawGraphics();
// Create some sprites to draw here...
// Launch a game loop to execute every 15ms
Timer timer = new Timer();
TimerTask task = new TimerTask() { public void run() { gameLoop(); }};
timer.schedule(task,0,15);
public void gameLoop() {
BufferedImage erase = getEraseBI();
// Erase each the 64x64 sprites...
int x, y; x = getX(1); y = getY(1);
long t1start = System.currentTimeMillis();
grafx.drawImage(erase, x, y, null);
long t1end = System.currentTimeMillis();
x = getX(2); y = getY(2);
long t2start = System.currentTimeMillis();
grafx.drawImage(erase, x, y, null);
long t2end = System.currentTimeMillis();
// and so on, for each sprite to erase
// Move stuff and redraw it; no problems here...
// Now flip the page to show what we’ve drawn
long show1 = System.currentTimeMillis();
buffStrat.show();
long show2 = System.currentTimeMillis();
grafx.dispose();
grafx = buffStrat.getDrawGraphics();
}
Normally, the drawImage() and buffStrat.show() calls execute so fast that the difference between the two times shows as 0. But after a couple of minutes, when there are more than about a dozen sprites to erase, one or more of the drawImage() calls (but not all of them), as well as the buffStrat.show() call suddenly take 25-35ms instead of 0. This behavior persists until the game is stopped. Rebooting the system clears the high-CPU ccSvcHst condition, restoring normal, stable, play-for-3-hours-straight-without-any-issues game performance.
Without getting into a political discussion about the merits of Norton tools, I'm just curious - has anyone else seen this interaction between Java graphics and NIS ccSvcHst.exe?
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.
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
I've made this fairly simple View. It's supposed to animate a red line that travels from left to right over a period of time (DURATION). It should update it's position every DELAY, so that you can adjust the animation to be more smoother if you would want that. It should be noted that I'm testing this on the emulator. Well, if I increase DELAY then the animation finishes faster, even though it should have no effect on the total animation time. Am I simply animating too fast, hogging all the resources? Or is my math off?
public class AnimView extends View {
// Animation duration in milliseconds
private static final int DURATION = 4000;
// Update frame every delay (in milliseconds)
private static final int DELAY = 10;
private int pos;
private long lastTick;
private Paint paint;
public AnimView(Context context, AttributeSet attrs) {
super(context, attrs);
pos = 0;
lastTick = 0;
paint = new Paint();
paint.setColor(Color.RED);
}
#Override
protected void onDraw(Canvas canvas) {
if(System.currentTimeMillis() - lastTick >= DELAY) {
// Calculate a new position for the line
pos += (int) (((double) DELAY / DURATION) * getWidth());
lastTick = System.currentTimeMillis();
}
canvas.drawRect(pos, 0, pos + 1, getHeight(), paint);
if(pos < getWidth()) {
// Position is still below getWidth, keep animating
postInvalidate();
}
}
}
You should be updating lastTick in the if statement:
long currentTime = System.currentTimeMillis();
if(currentTime - lastTick >= DELAY) {
// Calculate a new position for the line
pos += (int) (((double) DELAY / DURATION) * getWidth());
lastTick = currentTime;
}
Otherwise you will draw each time after DELAY passes. So when you increase DELAY, less drawing happens and so the program finishes faster.
To add some additional color to this thread, this is a well known problem in game development. You can find lots of resources on it if you search for "Fixed time step vs Variable time step", for example: http://gafferongames.com/game-physics/fix-your-timestep/
The gist of it is that the speed of your animation is directly tied to your frame rate at the moment. This is why in the old NES megaman games, you'd have periods of slow down when there were lots of enemies on the screen at once (because the CPU was busier calculating all the data for those entities, so each frame took longer to process).
There are two ways around this problem:
Move to a variable time step where you multiply each movement by the amount of time that progressed between each frame of animation. That way, when that amount changes for any reason, your animation looks the same.
Separate the update and draw phases so that you can control the time diff that is passed into the update method, regardless of how fast you're drawing.
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.
}