I am pretty new to Android development and I am trying to make a game in which my character moves on tiles across the screen. Since each tile is a bitmap of its own I redraw it after the character has moved on it.
Now for some reason every time I draw the character sprite the entire view sort of flickers.
It seems like on every even draw the background I created is visible and on every odd draw the background is black. Also, the character sprite leaves a trail but only half of it is visible, depending on whether it's an even or an odd draw. My guess is that for some reason there are two views or something on which the canvas is drawing.
I would've uploaded images but I can't :(
Does anyone have a clue what I'm doing wrong? Thanks a lot for any kind of help.
Here is the method moving the character:
// Moves sprite on screen
private void MoveSprite()
{
// Run as long as the sprite's location didn't reach its destination
while (m_gameView.m_playerControl.m_MoveDestination.x != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.x ||
m_gameView.m_playerControl.m_MoveDestination.y != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.y)
{
// Draw sprite with FPS control
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = m_gameView.getHolder().lockCanvas();
synchronized (m_gameView.getHolder())
{
// Draws player and refreshes the tiles it was previously on
m_gameView.m_playerControl.onDraw(c);
}
}
finally
{
if (c != null)
{
m_gameView.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0)
Thread.sleep(sleepTime);
else
Thread.sleep(10);
}
catch (Exception e)
{
}
}
// After drawing char movement, changing flag to false
m_fIsCharMoved = false;
Check out screen width and height based drawing.
you should draw inside the screen.
I think u r drawing the characters out of the screen width or height
Related
I have been experimenting with squeezing as much performance out of SurfaceView as possible. Currently, I'm subclassing it and implementing a runnable interface on it instead of a callback. I understand there is no hardware acceleration on it.
Still, if I either draw a canvas primitive vertical line scrolling across the screen or a bitmap vertical line, both run slower and slower after each pass. This felt to me like a memory leak, or is it just Android itself? Is OpenGL or another library really my last resort?
I've drawn plenty of scrolling backgrounds before at decent speeds (I think around 5 pixels per tick, this I'm aiming around 20-50 pixels a tick which if anything would be less stops along the way to render).
EDIT: Here is the SurfaceView extended, the thread it makes, the drawing method, and the initialization of it. Basically, this is in a slightly bigger class that just holds this screen's data. The drawXYZ() methods simply use the canvas primitives or a bitmap to paint mainly as the background, which is a solid background color with some vertical and horizontal lines on it like a music staff, little calculating is involved.
The drawCursor is what makes the scrolling vertical line and when I just let it loop the scrolling from left to right, it eventually lags much slower than the first scroll.
public class MySurfaceView extends SurfaceView implements Runnable
{
Thread renderThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public MySurfaceView() {
super(mainActivity);
this.holder = getHolder();
holder.setFixedSize(screenW, screenH);
}
public void resume() {
running = true;
renderThread = new Thread(this);
renderThread.start();
}
#Override
public void run() {
while (running) {
if (!holder.getSurface().isValid()) {
continue;
}
Canvas canvas = holder.lockCanvas();
if(canvas != null) {
doDraw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
}
public void pause() {
running = false;
while (true) {
try {
renderThread.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
protected void doDraw(Canvas canvas)
{
canvas.drawColor(Color.rgb(56, 56, 62));
lastNotePlayed = OptionsContainer.getNotePlaying();
//Draw contours (rows).
paint.setColor(Color.rgb(0, 255, 255));
paint.setStrokeWidth(3);
paint.setTextSize(35);
drawContours(canvas, paint);
//Beats per measure (BPM).
paint.setColor(Color.rgb(233, 232, 232));
paint.setStrokeWidth(1);
paint.setStyle(Paint.Style.STROKE);
paint.setPathEffect(bpmLines);
drawBPM(canvas, paint);
paint.setPathEffect(null);
//Draw measures.
paint.setStrokeWidth(5);
drawMeasures(canvas, paint);
//Draw note node inputs.
paint.setColor(Color.rgb(76, 255, 0));
for (int i = 0; i < OptionsContainer.noteList.length; i++) {
if (OptionsContainer.noteList[i].getContour() != 0) {
if (OptionsContainer.noteList[i].getContour() > (OptionsContainer.contour / 2)) {
//Staff on left side, below note.
canvas.drawBitmap(lowerStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY(), null);
} else {
canvas.drawBitmap(higherStaffBmp, OptionsContainer.noteList[i].getX(), OptionsContainer.noteList[i].getY() - 40, null);
}
}
}
//Draw cursor.
paint.setStrokeWidth(2);
paint.setColor(Color.WHITE);
drawCursor(canvas, paint);
if (OptionsContainer.isRest)
canvas.drawBitmap(restBmp, (OptionsContainer.screenWidth / 2), (screenHeight - 100) / 2, null);
}
}
#Override
public void init() {
surfaceView = new MySurfaceView();
surfaceView.setLayoutParams(layoutParams);
surfaceView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// Normalize x,y between 0 and 1
float x = event.getX();
float y = event.getY();
if (x < (OptionsContainer.screenWidth) && y < screenH) {
NoteNode note = new NoteNode(x, y, MainActivity.options);
if (note.getContour() == OptionsContainer.noteList[note.getBeat() - 1].getContour()) {
OptionsContainer.noteList[note.getBeat() - 1] = new NoteNode(x, screenHeight + 200, MainActivity.options);
} else {
OptionsContainer.noteList[note.getBeat() - 1] = note;
}
}
}
return true;
}
});
mainActivity.addContentView(surfaceView, layoutParams);
surfaceView.resume();
}
EDIT #2: Final Answer
Add Path.reset() after the path is drawn in drawBPM(). I'd imagine that stops a memory leak of that path which is trying to keep track of ALL the paths it has been writing and overwriting, little to our knowledge just looking at the lines on the screen. There was a similar Stack Overflow question but fadden's debugging tips below were very helpful for initially trying to figure out what and where it was going wrong.
"Squeezing performance" and Canvas-rendering don't really go together on a SurfaceView, but you can do okay on many devices.
Grafika's "multi-surface test" Activity features a bouncing circle, rendered in software. I haven't noticed it get slower over time, so I suspect something is wrong in your code. Note Grafika does not subclass SurfaceView, and I generally recommend against doing so -- it's too easy to do the wrong thing. The only valid reason to subclass SurfaceView is if you want to draw on both the Surface and the View, e.g. for some sort of mask effect.
You didn't show any code, so there's not much more we can tell you.
I don't see anything blatantly wrong in the code; seems pretty straightforward. I'd check to make sure OptionsContainer.noteList.length isn't growing without bound. Next step would be to use traceview to figure out which part of the rendering is slow, or just spread System.nanoTime() calls around to identify which part is getting progressively slower. If everything in the method shown is executing at a consistent speed except drawCursor(), move the time-check calls into there, narrowing it down until you find what's draining your performance.
If something is consuming memory quickly enough to cause heap issues, you should see a great deal of GC activity in the logcat output. The DDMS allocation tracker tool can help with that.
Currently I'm trying to implement a simple animation: I draw on the canvas of an Surfaceview and want to move a single-colored circle smoothly across the screen.
To achieve that I calculate a slightly moved circle, draw the canvas and let the thread then sleep for a few miliseconds. This doesn't run very smoothly.
So I found the animator object of google, that was written for that use. Does it something different then my code or will it just similarly calculate the moved circle every few miliseconds?
Here is the code I use for the drawing:
public void run() {
circle= new Circle(getRandPoint());
Canvas canvas = null;
while (running) {
if(!circle.IsMoving()){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
newPos = getRandPoint();
circle.setNewPos(newPos);
}
circle.calculateMovement();
// PAINT
try {
canvas = holder.lockCanvas();
synchronized (holder) {
draw(canvas);
}
} finally {
if (canvas != null) {
holder.unlockCanvasAndPost(canvas);
}
}
// WAIT
try {
Thread.sleep(30); // Wait some time until I need to display again
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
And here are the important methods of my Circle class:
public void setNewPos(Point p){
nPosX = p.x;
nPosY = p.y;
dx = (nPosX - posX);
dy = (nPosY - posY);
// normalize dx and dy and multiply it by speed
double dxdy = Math.abs(dx+dy);
dx = dx/dxdy;
dy = dy/dxdy;
dx *= speed;
dy *= speed;
}
public void calculateMovement(){
posX += dx;
posY += dy;
}
Thanks for every answer.
Does it something different then my code or will it just similarly calculate the moved circle every few miliseconds?
Android's animation framework does a number of things that your code does not. Some of the convenient things it offers out of the box are the ability to pause animations, or play animations on different views in coordination with each other (either at the same time or in sequence, for example).
It also has the distinct benefit of already being written and tested, so you don't need to do that yourself.
There are some much bigger benefits that you can from the framework though-
First, one issue that you have in your code is that it doesn't care about when your graphics are actually rendered on screen. By blindly delaying for 30 milliseconds, you could be skipping GPU frames or performing draws that won't actually be visible to the user. The framework's animation functions are optimized to avoid these issues. This is likely the source of the jank you are seeing in your implementation.
Second, the framework provides the ability to define more authentic motion through the use of interpolators. Real objects don't move in a linear fashion, so using an interpolator allows you to define more realistic motion, such as slowing down as the animation reaches the final position.
I'm trying to do a menu based on bitmaps. The menu itself should be movable through screentouch move events, basically I want to drag the buttons around on the view. The button also includes collision detection, so whenever they touch they bounce from each other.
But I have some problems when it comes to drawing my bitmaps. Currently I'm using a rectangle to scale my bitmap to fit the window of my device. Want i want and can not get currently is for smoother movements of my bitmaps without flickering. Is the only option to move to open gl? Or have I missed something big in my code?
This is in my surfaceview for drawing each button, where MenuButton is the class that holds the bitmap and updates its position according to a touch and drag move.
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
for(MenuButton menuButton : menuButtonSprites) {
menuButton.onDraw(canvas);
}
}
I want the bitmaps to scale to each device's width and for that i use a rectangle for the bitmap to fit in.
public MenuButton(MenuView v, Bitmap bmp, int yPosition){
this.menuView = v;
this.menuButton = bmp;
this.xMax = v.getWidth();
this.yPosistion = yPosition;
menuButtonRectangle = new Rect(xMin, this.yPosistion-yMin, xMax, this.yPosistion+yMax);
}
public void update(int y){
if(menuButtonPressed)
{
this.yPosistion = y;
menuButtonRectangle.set(xMin, yPosistion-yMin, xMax, yPosistion+yMax);
}
}
public void onDraw(Canvas canvas){
canvas.drawBitmap(menuButton, null, menuButtonRectangle, null);
}
I also have a thread that updates the draw
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
Canvas c = null;
while (running) {
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
}
finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
}
catch (Exception e) {
}
}
}
I don't really know what I'm doing wrong and why i can't manage to get a smooth movements of my buttons. Is it a downside for using canvas or have I missed something really important :D?
Usually This problem occurs when there is sync problem exists while painting. This may due to the higher Frame rate or also may be the lower frame rate. These kind of issue can be fixed by Double buffering or adjusting the Frame Rate.
Double buffering means, Instead of drawing the Image directly on to the main canvas, we will be creating an empty bitmap of screen size and getting the graphics object. Drawing every thing on to the bitmap then directly drawing this bitmap to the main canvas.
I'm shooting for an animation in a live wallpaper. Here's the code. It pretty much follows the CubeWallpaper demo:
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
final BufferedInputStream buf;
final Bitmap bitmap, rbitmap;
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
try {
buf = new
BufferedInputStream(assets.
open(folder+"/"
+imageList[ilen++])
);
bitmap = BitmapFactory.
decodeStream(buf);
rbitmap = Bitmap.createBitmap
(bitmap,
0,0,imageWidth,imageHeight,
transMatrix,false);
c.drawBitmap(rbitmap,
paddingX,
paddingY,
null);
if ( ilen >= imageCount ) ilen=0;
}
catch (Exception e) { e.printStackTrace(); }
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
// Reschedule the next redraw
mHandler.removeCallbacks(mDrawCube);
if (mVisible) {
mHandler.postDelayed(mDrawCube, fps);
}
}
where "transMatrix" is a scaling and rotation matrix predefined before.
It's supposed to render at 30fps but of course it doesn't do that. My initial guess is that the BufferedInputStream is one factor. I should probably cache a few of these as I go along along with the Bitmaps. But any other ideas? Will I have to use the OpenGL hack for live wallpapers?
Yes, the BufferedInputStream and BitmapFactory really shouldn't be in drawFrame() at all. You're loading and creating resources on every single frame, and that's a huge waste. Like you said, cache as many as you can beforehand, and if you find the need to load more during drawing, use a separate thread to do it so it doesn't slow the drawing.
I had the same problem: slow canvas rendering in context of live wallpapers.
I agree with others saying that you shouldn't do any cpu/io heavy while rendering e.g. loading images especially on the UI thread.
However there is one more thing you should note. You request a redraw (mHandler.postDelayed(...)) AFTER the frame was rendered. If you desire a 30 fps and thus you request a redraw in (1000 / 30) 33ms then it will NOT result in 30 frames per sec. Why?
Let's assume it takes 28ms to render all your stuff to the canvas. After it's done you request a redraw after 33 millis. That is, the period between redraws is 61 ms which equals with 16 frames per sec.
You have two options to solve it:
1) Put the mHandler.postDelayed(...) code to the beginning of the drawFrame(...) method. This seems OK but it has some disadvantages: If your actual FPS is very close to the maximal possible FPS on an actual device - with other words the UI thread is busy all the time with you canvas rendering - then there won't be time for the UI thread to do other stuff. It doesn't necesseraly mean that your LWP or the home screen will lag but you (your LWP) might start missing some touch events (as my LWP did).
2) The better solution is to start a separate thread when the surface is created and pass it the reference to the SurfaceHolder. Do the rendering in this separate thread. The render method in this thread would look like this:
private static final int DESIRED_FPS = 25;
private static final int DESIRED_PERIOD_BETWEEN_FRAMES_MS = (int) (1000.0 / DESIRED_FPS + 0.5);
#Override
public void run() {
while (mRunning) {
long beforeRenderMs = SystemClock.currentThreadTimeMillis();
// do actual canvas rendering
long afterRenderMs = SystemClock.currentThreadTimeMillis();
long renderLengthMs = afterRenderMs - beforeRenderMs;
sleep(Math.max(DESIRED_PERIOD_BETWEEN_FRAMES_MS - renderLengthMs, 0));
}
}
So I have a bitmap that I have loaded from a resource file (an PNG image):
Bitmap map = BitmapFactory.decodeResource(getResources(), R.drawable.wave);
If I draw this bitmap only once using canvas.drawBitmap(...); then there is no problem. However, If I draw that very same bitmap multiple times, then the picture keeps flashing back and forth, not steady like before.
I suspected that I cannot use the same bitmap more than once so I tried to load the image into a new bitmap every time when I want to draw the same picture, but it does not help, the behavior still persists.
The program is complicated, but basically, I want to draw a ocean wave. I have a image of a small wave. To make the effect of the wave moving from the left edge of the screen to the right edge. I keep track of the position of the left edge of the bitmap.
// The ocean.
private ArrayList<Wave> waves;
// Draw the waves and update their positions.
for (int i = 0; i < this.waves.size(); i++)
{
Wave wave = this.waves.get(i);
// Go through each of the sub-waves of this current wave.
for (int j = 0; j < wave.getSubWaveEdges().size(); j++)
{
// Get the sub wave.
final float subWaveEdge = wave.getSubWaveEdges().get(j);
canvas.drawBitmap( wave.getSubWave(j), subWaveEdge, 40, brush);
wave.setSubWaveEdge(j, subWaveEdge + (float) 0.5);
}
// Update this current wave.
wave.update();
// If the wave has passed the left edge of the screen then add a new sub-wave.
if (wave.getFarthestEdge() >= 0)
wave.addSubWaveEdges(wave.getFarthestEdge() - this.getWidth());
}
If the left edge of a bitmap is inside the screen then I create a new bitmap from the same image file and draw. Here is the class Wave:
private class Wave
{
private Bitmap wave;
private float farthestEdge;
private ArrayList<Float> subWaveEdges;
private ArrayList<Bitmap> subWaves;
public Wave(Bitmap wave)
{
this.wave = wave;
this.farthestEdge = 0;
this.subWaveEdges = new ArrayList<Float>();
this.subWaves = new ArrayList<Bitmap>();
}
public Bitmap getWave ()
{ return this.wave; }
public void setWave (Bitmap wave)
{ this.wave = wave; }
public float getFarthestEdge ()
{ return this.farthestEdge; }
public void setFarthestEdge (final float furthestEdge)
{ this.farthestEdge = furthestEdge; }
public ArrayList<Float> getSubWaveEdges ()
{ return subWaveEdges; }
public void setSubWaveEdge (final int index, final float value)
{
this.subWaveEdges.remove(index);
this.subWaveEdges.add(value);
}
public void addSubWaveEdges (final float edge)
{
this.subWaveEdges.add(edge);
Bitmap newSubWave = BitmapFactory.decodeResource(getResources(), R.drawable.wave);
newSubWave = Bitmap.createScaledBitmap(newSubWave, MasterView.this.getWidth(), newSubWave.getHeight(), true);
this.subWaves.add(newSubWave);
}
public Bitmap getSubWave(final int index)
{ return this.subWaves.get(index); }
public void update ()
{
// Check to see if there is any sub-wave going outside of the screen.
// If there is then remove that wave.
for (int index = 0; index < this.subWaveEdges.size(); index++)
if (this.subWaveEdges.get(index) > MasterView.this.getWidth())
{
this.subWaveEdges.remove(index);
this.subWaves.remove(index);
}
// Set the farthest edge to the other side of the screen.
this.farthestEdge = MasterView.this.getWidth();
// Get the farthest edge of the wave.
for (int index = 0; index < this.subWaveEdges.size(); index++)
if (this.subWaveEdges.get(index) < this.farthestEdge)
this.farthestEdge = this.subWaveEdges.get(index);
}
}
Another suspicion that I have is that may be when I create two bitmaps from the same resource file, the pixels of the image are divided among two bitmaps, meaning that each bitmap only gets part of the pixels, not all. I am suspecting this because when the bitmaps are drawn, the parts where they overlaps are drawn steadily, no flashing.
Anyone has stumbled upon this problem and know how to fix?
Thanks,
Viktor Lannér, Thank you for helping, but I don't think that's the problem. I understand it is hard to read my codes since it is only a small piece of the big program.
However, I found the problem: This is not mentioned in my original question, but in order to simulate the two waves moving after one another, I have to draw the next wave as soon as the first wave enters the screen. However, each wave is longer than the width of the screen. Therefore, I have to draw the next wave from "outside" the screen if you know what I mean. It means that the next wave is drawn from a negative x-coordinate from outside the screen:
// If the wave has passed the left edge of the screen then add a new sub-wave.
if (wave.getFarthestEdge() >= 0)
wave.addSubWaveEdges(wave.getFarthestEdge() - this.getWidth());
And I found out that it does not like this. This is what causes the flashing back and forth.
In order to fix this, instead of drawing the next wave from outside the screen, I use this method:
canvas.drawBitmap (Bitmap bitmap, Rect source, Rect destination, Paint paint)
This method allows you to specify a rectangular region on the bitmap to be drawn to the screen and a rectangular region on the screen where that part of the bitmap will be drawn over. I use this method to draw the next wave. As the next wave moves into the screen, I change the "source" and "destination" appropriately to draw parts of the bitmap.
I just wanted to say that I had an issue where the images on my canvas were flashing back and forth, or, flashing between black and my first frame until I made a movement, almost as if the canvas was rapidly switching between its current and last image.
This might have had something to do with your situation, and to fix it I found out that it was because I was locking the canvas every frame, even when I had nothing to draw. For whatever reason, that lock, I think, created this situation.
I got around it by doing something like this:
if (needToRedraw == true) {
canvas = mSurfaceHolder.lockCanvas(null);
... logic to eventually draw on that canvas ...
}
Before canvas.drawBitmap(...) call; try to use canvas.drawColor(Color.BLACK) to clear the Canvas from previous drawings.
Sample code:
// Stuff.
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(wave.getSubWave(j), subWaveEdge, 40, brush);
// Stuff.