I'm a completely newbie to android programming, having done some java for my computing levels but nothing too complex!
I'm working on a game where an object falls down the screen and has to be sorted into the relevant 'box' when it reaches the bottom.
I've got a surface view running with a thread etc, using canvas draw methods, however, i can't for the life of me see how i will be able to make the falling object reach a speed where it'll present a challenge to the user.
Running the thread with a change of 1 in the y direction causes the object to crawl down the screen. Greater changes in Y lead to jumpy graphics.
Would OpenGL make any difference or are there other canvas methods i can implement?
Hope that makes sense!
Thanks in advance
----Thread------
public void run()
{
Canvas canvas;
while(running)
{
canvas = null;
try{
canvas = this.surfaceholder.lockCanvas();
synchronized(surfaceholder)
{
gamepanel.Check();
this.gamepanel.onDraw(canvas);
}
}finally
{
if(canvas != null)
{
surfaceholder.unlockCanvasAndPost(canvas);
}
}
}
}
----SurfaceView-------
protected void onDraw(Canvas canvas){
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.gamebackground), 0, 0, null);
SortItOut.sortitout.Letter.draw(canvas);
}
-----Letter----- (Each object is a different letter)
public static void draw(Canvas canvas)
{
y += 1;
canvas.drawBitmap(LetterObject, x, y, null);
}
Those are the methods i would believe are relevant (The Check method is simply to check whether the object has reached the bottom of the screen).
You must load all your bitmaps in the constructor for the SurfaceView, never in onDraw()
Aside from the bitmap loading problem, you can make it fall faster my increasing the rate of the y change. If you do it too much, the box will appear to jump, but I bet you could get away with up to 10 pixel changes before that would happen (experiment).
You would only need to do OpenGL in this case if performance was slowing you down. I don't think that's the case. Although, I would stop loading the bitmap in the onDraw method and put it in the onCreate or some constructor. onDraw gets called hundreds of times and that's killing your app.
Related
So I am basically creating a 2D physics engine I want to use for other projects in the future.
The only problem here is the performance of my Surface View.
I get a constant 60 logic-updates per second, but only 6 frames per second no matter what I do.
If I call the draw() method in my calculating thread, I get 6 fps.
If I create a whole new thread which is only drawing, without any delay in the loop
#Override
public void run() {
while(running) {
if(holder.getSurface().isValid()) {
canvas = holder.lockCanvas();
canvas.drawColor(Color.DKGRAY);
for (GameObject object : objects) {object.draw(canvas);}
holder.unlockCanvasAndPost(canvas);
}
gs.totalFrames++;
}
}
(gs = Game Surface) I get 6 fps. The draw() function of my game object is pretty simple:
public void draw(Canvas canvas) {
canvas.drawCircle((float) x, (float) y, 10, paint);
}
Why is the performance so bad? (I only have 5 game objects, so that's 5 circles, which should not be a performance problem at all!)
I also tried a runnable posting itself delayed, but that was bad, too.
Calling the draw() function in the GameSurface from the thread instead of doing the drawing directly in it, didn't change anything. A tiny delay didn't improve it either.
Do I somehow use the SurfaceView in a way it's not supposed to be used in?
What is the problem?
My code here receives a value for bmpX and bmpY and uses them to draw a bitmap at that location on the screen. This works exactly as I want it to, but I would rather use the canvas.translate() function to handle the movement of the image, rather than just canvas.drawBitmap(), because I will be applying other draw elements to the canvas and I want them all to move the same amount and direction.
My question is: where can I move the canvas.drawBitmap() in the code so that it just draws onto the canvas at the start, but it is moved any time after that using canvas.translate() instead? Anywhere I place the code still freezes the image at that point where I draw it, regardless of how much I change the position of canvas.translate(). I'm imaging its possible to "stick" the image to the canvas, so to speak, and then move the canvas, which also moves the image with it.
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
if(!surfaceHolder.getSurface().isValid()) {
continue;
}
canvas = surfaceHolder.lockCanvas();
canvas.drawRGB(255, 255, 255);
canvas.drawBitmap(bmp, bmpX-(bmp.getWidth()/2),
bmpY-(bmp.getHeight()/2), null);
//canvas.translate(bmpX, bmpY);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
I hope I explained my problem clearly enough. Thanks!
Answering my own question here for clarity, afetr info I got offline. UI cannot be done on a thread like this, I needed to be using an android UI thread instead. runOnUIUpdatethread and async tasks
I am currently working on an app that allows for free-drawing.
The current method I am using is as follows:
currentLine is a list that keeps a history of all points that ACTION_MOVE returns.
public boolean onTouchEvent (MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Point p = new Point(event.getX(),event.getY());
currentLine.addPoint(p);
invalidate();
break;
}
return true;
}
I then take these points and draw them in the onDraw method of my class.
#Override
protected void onDraw(Canvas c) {
super.onDraw(c);
//Draw Background Color
c.drawColor(Color.BLUE);
//Setup Paint
Paint p = new Paint();
p.setStyle(Style.FILL);
p.setColor(COLOR.WHITE);
//iterate through points
if(currentLine.size()>0){
for(int x = 0;x<currentLine.size();x++){
c.drawCircle(currentLine.get(x).getX(), currentLine.get(x).getY(), 3, p);
}
}
}
And this method works great, with no lag or anything.
Except, it does not get enough of the points that it needs to.
For example, if I am to drag my finger quickly across the entire screen, it may only draw 15 points of the whole event.
How can I improve the performance/speed of the MotionEvent? How can I get more points? Or is there something else I should be doing?
----EDIT----
I have managed to solve it myself.
Instead of using drawCircle, I switched to drawLine.
Example:
if(points.size()>0){
for(int x = 0;x<points.size()-1;x++){
c.drawLine(points.get(x).getX(), points.get(x).getY(), points.get(x+1).getX(), points.get(x+1).getY(), p);
}
}
This produces solid lines, which is what I wanted.
However, for the sake of knowledge, I would still like to know how to speed up MotionEvents.
A detailed answer would be appreciated
The bottleneck is the drawing method, obviously.
If you are working on android 3.0+, draw all those crazy things on the GPU.
Add the attribute
android:hardwareAccelerated="true"
to the <application> tag in your manifest. This will unbelievably increase drawing time.
Additionally, try to not redraw the whole thing if only a little needs to be updated.
Invoke invalidate(Rect dirty) instead of invalidate().
You should also get more points by utilizing event.getHistoricalX/Y() functions
Put a small thread sleep in the motion event; that helped me solve the problem when a ton of movement events were jamming the listener.
Thread.sleep(100);
invalidate(); is harmful for performance. Try to calculate bounds rect and call invalidate(bounds)
I'm trying to test out some different methods of drawing to a Canvas, without triggering garbage collection. Even the most basic examples cause frequent gcs. Example:
class Panel extends View {
private int mX = 0;
private Paint mPaint = new Paint();
public Panel(Context context) {
super(context);
mPaint.setColor(0xFFFF0000);
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
canvas.drawRect(mX, 0, mX+40, 40, mPaint);
mX++;
postInvalidate();
}
}
I get the same result with SurfaceView (lunar lander example). GCs about every 10 seconds or so, pretty jarring in a realtime game. I'm not making any allocations in the draw loop above, so something must be allocated in the canvas etc classes (unfortunately).
I had success with an opengl test, no gcs, but I was hoping to avoid getting into opengl. I'm pretty familiar with it, but it's going to be tough drawing some effects I wanted to achieve using opengl.
Thanks
postInvalidate() may have to create an object. Use invalidate() instead, there is no reason to use postInvalidate() here.
Instead of guessing you should take a look at what is allocated.
I'm new to Android.
I am drawing bitmaps, lines and shapes onto a Canvas inside the OnDraw(Canvas canvas) method of my view. I am looking for help on how to implement smooth scrolling in response to a drag by the user. I have searched but not found any tutorials to help me with this.
The reference for Canvas seems to say that if a Canvas is constructed from a Bitmap (called bmpBuffer, say) then anything drawn on the Canvas is also drawn on bmpBuffer. Would it be possible to use bmpBuffer to implement a scroll ... perhaps copy it back to the Canvas shifted by a few pixels at a time? But if I use Canvas.drawBitmap to draw bmpBuffer back to Canvas shifted by a few pixels, won't bmpBuffer be corrupted? Perhaps, therefore, I should copy bmpBuffer to bmpBuffer2 then draw bmpBuffer2 back to the Canvas.
A more straightforward approach would be to draw the lines, shapes, etc. straight into a buffer Bitmap then draw that buffer (with a shift) onto the Canvas but so far as I can see the various methods: drawLine(), drawShape() and so on are not available for drawing to a Bitmap ... only to a Canvas.
Could I have 2 Canvases? One of which would be constructed from the buffer bitmap and used simply for plotting the lines, shapes, etc. and then the buffer bitmap would be drawn onto the other Canvas for display in the View?
I should welcome any advice!
Answers to similar questions here (and on other websites) refer to "blitting". I understand the concept but can't find anything about "blit" or "bitblt" in the Android documentation. Are Canvas.drawBitmap and Bitmap.Copy Android's equivalents?
I seem to have found an answer. I have put the bulk of the drawing code (which was previously in onDraw()) in a new doDrawing() method. This method starts by creating a new bitmap larger than the screen (large enough to hold the complete drawing). It then creates a second Canvas on which to do the detailed drawing:
BufferBitmap = Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888);
Canvas BufferCanvas = new Canvas(BufferBitmap);
The rest of the doDrawing() method is taken up with detailed drawing to BufferCanvas.
The entire onDraw() method now reads as follows:
#Override protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(BufferBitmap, (float) -posX, (float) -posY, null);
}
The position variables, posX and posY, are initialised at 0 in the application's onCreate()method. The application implements OnGestureListener and uses the distanceX and distanceY arguments returned in the OnScroll notification to increment posX and posY.
That seems to be about all that's needed to implement smooth scrolling. Or am I over-looking something!?
I had this problem too,
I did the drawing like this:
Canvas BigCanvas = new Canvas();
Bitmap BigBitmap = new Bitmap(width,height);
int ScrollPosX , ScrollPosY // (calculate these with the onScrollEvent handler)
void onCreate()
{
BigCanvas.SetBitmap(BigBitmap);
}
onDraw(Canvas TargetCanvas)
{
// do drawing stuff
// ie. BigCanvas.Draw.... line/bitmap/anything
//draw to the screen with the scrolloffset
//drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
TargetCanvas.DrawBitmap(BigBitmap(new Rect(ScrollPosX,ScrollPosY,ScrollPosX + BigBitmap.getWidth(),ScrollPosY + BigBitmap.getHeight(),new Rect(0,0,ScreenWidth,ScreenHeight),null);
}
for smooth scrolling you'd need to make some sort of method that takes a few points after scrolling (i.e the first scroll point and the 10th) , subtract those and scroll by that number in a for each loop that makes it gradually slower ( ScrollAmount - turns - Friction ).
I Hope this gives some more insight.
Continuation of reply to Viktor ...
In fact, the situation is more complicated. Because the doDrawing process is quite slow (taking 2-3 seconds on my slow old HTC Hero phone) I found it desirable to pop up a Toast message to advise the user that it was happening and to indicate the reason. The obvious way to do this was to create a new method containing just 2 lines:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");`
doDrawing();
}
and to call this method from other places in my program instead of doDrawing().
However, I found that the Toaster either never appeared or flashed up so briefly that it could not be read. My workaround has been to use a time check Handler to force the program to sleep for 200 milliseconds between displaying the Toast and calling doDrawing(). Although this slightly delays the start of a redraw I feel this is a price worth paying in terms of the program's usability because the user knows what is going on.
reDrawBuffer() now reads:
public void redrawBuffer(String strReason) {
Toaster.Toast(strReason, "Short");
mTimeCheckHandler.sleep(200);
}`
and the Handler code (which is nested within my View class) is:
private timeCheckHandler mTimeCheckHandler = new timeCheckHandler();
class timeCheckHandler extends Handler {
#Override
public void handleMessage(Message msg) {
doDrawing();
}
public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
}`
No need for the activity to be restarted! (Per prepbgg's Jan 27 10 reply to his Jan 17 10 'answer') Rather than recycling the bitmap and incurring the overhead of having the activity reloaded, you can avoid having the application loaded by putting the 'android:configChanges' attribute shown below, in the 'activity' element of the AndroidManifest.xml file for the app. This tells the system the the app will handle orientation changes and that it doesn't need to restart the app.
<activity android:name=".ANote"
android:label="#string/app_name"
android:configChanges="orientation|screenLayout">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This method can be used to get a notification when the orienation is changed:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
prt("onConfigurationChanged: "+newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
prt(" PORTRAIT");
} else {
prt(" LANDSCAPE");
}
} // end of onConfigurationChanged
prepbgg: I don't think the code will work because canvas.drawBitmap does not draw into the bitmap but draws the bitmap on-to the canvas.
Correct me if I am wrong!