I'm trying to create a simple game loop (its not really a game yet) that displays a circle, then after it ticks 100 times draws another circle. I also have a text field that should display how many times the loop has ran. Relevant code is as follows:
MainActivity
public class MainActivity extends Activity
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
DrawView v = new DrawView(this);
v.setBackgroundColor(Color.WHITE);
setContentView(v);
}
}
DrawView
public class DrawView extends SurfaceView implements SurfaceHolder.Callback
{
Paint p = new Paint();
MainThread thread;
private int y=0;
public DrawView(Context c)
{
super(c);
thread = new MainThread(this, getHolder());
thread.running = true;
getHolder().addCallback(this);
setFocusable(true);
}
public void draw(Canvas c)
{
if(c==null)
return;
super.onDraw(c);
p.setColor(Color.RED);
p.setTextSize(32);
p.setTypeface(Typeface.SANS_SERIF);
c.drawCircle(getWidth()/2-100,getHeight()/2, 50, p);
c.drawText("y = " + y, 50, 50, p);
if(y==100)
c.drawCircle(getWidth()/2+100,getHeight()/2, 50, p);
else
y++;
}
public void surfaceCreated(SurfaceHolder p1)
{
thread.start();
}
MainThread
public class MainThread extends Thread
{
private DrawView page;
private SurfaceHolder holder;
public boolean running;
public MainThread(DrawView p, SurfaceHolder h)
{
super();
page = p;
holder = h;
}
#Override
public void run()
{
while(running)
{
Canvas c = holder.lockCanvas();
page.draw(c);
holder.unlockCanvasAndPost(c);
}
}
}
It just displays the first circle and the text saying "y = 2." Nothing seems to update, or its doing it twice and then stopping. I am new to Android programming but not to Java. I'm sure I'm just missing something simple. Thanks for any help.
EDIT: Upon further observation, it seems the thread crashes randomly. Everytime I run the app, it dislays "y = " and then a different number each time. I'd reckon it makes it that many ticks before crashing. After I close the app, I get a message that says "Unfortunately, MyApp has stopped." I don't know enough about how Android works to know why its crashing.
EDIT 2: I've discovered its throwing an IllegalArgumentException on the line holder.unlockCanvasAndPost(c). Again, l'm not sure why. Can anyone explain what's happening and how to fix it?
EDIT 3: Logging the value of y each tick reveals that it couts up correctly and stops when it reaches 100 as intended. What happens onscreen does not reflect that for some reason.
I think what you're looking for is a combination of a Handler and a Runnable.
The handler calls the runnable, which runs once, then decides if it should call again.
something like:
private Handler renderHandler = new Handler();
private Runnable renderRunnable = new Runnable() {
#Override
public void run() {
//... do stuff here;
if(shouldRunAgain){
renderHandler.postDelayed(renderRunnable, 1000); // for 1 second
}
}
};
renderHandler.post(renderRunnable);
edit:
without seeing a log cat, I'm going to make an assumption that the UI updates are happening from a thread other than the main UI thread, causing the crash.
have a look at this, this, and this - I think these are more in line of what you're looking for.
Related
I'm in the process of writing my first android app, an asteroids clone, and I've got one last bug I can't figure out. The game activity will occasionally freeze when the game ends and tries to go to the game over activity. The game activity has a few UI views that stop working before it freezes. There is a Button to pause the game which stops responding to input. There is also a TextView for the score that I am updating using runOnUIThread() (since I am running the game on a separate thread) which stops updating.
There are a few things that make this puzzling to me. First off, I'm not seeing any error messages. The bug only happens about 1 out of every 5 times the game activity is started and it happens at random points while it is running. It happens when the game activity starts about half the time, but seemingly may occur at any point, although it does tend to happen sooner than later even when it doesn't occur right at the start. Also, the game thread and the custom game View I made keep updating fine when the bug occurs. Finally, I have two virtual joystick Views I made to control the player's motion and shooting which also keep updating and responding to touch input fine. These last two really confuse me because at first I thought the main thread was doing too much work or getting stuck in a loop or something, but if I understand correctly (and according to some tests I've done) drawing and touch input happen on the main thread, and as far as I can tell all views are drawing fine and touch input is working for the joysticks but not the pause button. Also, if the main thread was unresponsive, wouldn't I get an app not responsive popup? That never happens though.
I've tried removing all instances of runOnUIThread() just to see if that fixed it with no effect other than breaking the score TextView and leaving the app stuck in the game activity even when the bug doesn't occur. I've tried printing the names of the threads returned by Thread.currentThread() in the game loop, in the onDraw() method of the game view, in the onTouch() method of JoystickView and when updating the score TextView in the game activity to ensure I actually am running 2 separate threads and everything is running on the threads they should be (game thread for the game loop, main thread for everything else), and that checked out. I've tried cutting down on creating new objects during the game loop, not sure what that would do other than help performance to be honest but I figured it couldn't hurt, also to no effect.
This all makes it difficult for me to know what code to post. Here are some guesses:
The onCreate, goToGameOverActivity, and updateScoreTextView methods from my game activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
setContentView(R.layout.activity_play);
gameView = findViewById(R.id.gameView);
game = new Game(this, gameView);
float joystickRadius = 150;
CartesianPoint2D motionJoystickCenter = new CartesianPoint2D(
10 + joystickRadius,
Constants.getScreenInformation().getHeight() - 10 - joystickRadius);
motionJoyStickView = new MotionJoystickView(this, joystickRadius, motionJoystickCenter, game);
CartesianPoint2D shootJoystickCenter = new CartesianPoint2D(
Constants.getScreenInformation().getWidth() - 10 - joystickRadius,
Constants.getScreenInformation().getHeight() - 10 - joystickRadius);
shootJoystickView = new ShootJoystickView(this, joystickRadius, shootJoystickCenter, game);
scoreTextView = findViewById(R.id.playActivityScoreTextView);
pauseButton = findViewById(R.id.playActivityPauseButton);
soundEffects = new SoundEffects(this);
game.start();
}
public void goToGameOverActivity() {
game.stop();
Intent intent = new Intent(this, GameOverActivity.class);
startActivity(intent);
finish();
}
public void updateScoreTextView(int score) {
scoreTextView.setText(String.valueOf(score));
scoreTextView.invalidate();
}
The Runnables I am using to update the score TextView and signal that the game is over using runOnUIThread():
package com.group18.spacerocks.play.view;
import com.group18.spacerocks.play.PlayActivity;
public class UpdateScoreTextView implements Runnable {
protected int newScore;
protected PlayActivity playActivity;
public UpdateScoreTextView(int newScore, PlayActivity playActivity) {
System.out.println("Setting ScoreTextView to " + newScore + ".");
this.newScore = newScore;
this.playActivity = playActivity;
}
#Override
public void run() {
playActivity.updateScoreTextView(newScore);
}
}
package com.group18.spacerocks.play;
public class GoToGameOverActivity implements Runnable {
protected PlayActivity playActivity;
public GoToGameOverActivity(PlayActivity playActivity) {
this.playActivity = playActivity;
}
#Override
public void run() {
playActivity.goToGameOverActivity();
}
}
The run() method from the GameThread class extending thread which updates the game:
public void run() {
super.run();
while (running) {
while (paused) {
currentTime = System.nanoTime();
lastUpdateTime = currentTime;
try {
sleep(1);
}
catch (InterruptedException e) {
e.printStackTrace()
}
}
currentTime = System.nanoTime();
timeSinceLastUpdate = currentTime - lastUpdateTime;
if (timeSinceLastUpdate >= TARGET_UPDATE_LENGTH) {
currentUpdateLength = currentTime - lastUpdateTime;
game.update(currentUpdateLength);
gameView.invalidate();
lastUpdateTime = currentTime;
UPS++;
timeSinceLastUpdate = 0;
if (currentTime - lastSecondTime >= 1000000000) {
System.out.println("UPS: " + UPS);
UPS = 0;
lastSecondTime = currentTime;
}
}
}
}
And finally the code for my JoystickView class, which has 2 child classes for motion and shooting:
package com.group18.spacerocks.play.joystick;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import com.group18.spacerocks.R;
import com.group18.spacerocks.play.CartesianPoint2D;
import com.group18.spacerocks.play.PlayActivity;
import com.group18.spacerocks.play.Game;
public class JoystickView extends View implements View.OnTouchListener {
protected boolean inUse;
protected float radius;
protected float buttonRadius;
protected CartesianPoint2D center;
protected CartesianPoint2D buttonOffset;
protected PlayActivity playActivity;
protected Game game;
protected Paint outlinePaint;
protected Paint buttonPaint;
public JoystickView(PlayActivity playActivity, float radius, CartesianPoint2D center, Game game) {
super(playActivity);
inUse = false;
this.radius = radius;
buttonRadius = radius / 2;
this.center = center;
this.buttonOffset = CartesianPoint2D.getInstance();
this.playActivity = playActivity;
this.game = game;
setLayoutParams(new ViewGroup.LayoutParams((int) radius * 2, (int) radius * 2));
setX(center.getX() - radius);
setY(center.getY() - radius);
outlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outlinePaint.setAlpha(100);
outlinePaint.setStyle(Paint.Style.STROKE);
outlinePaint.setColor(Color.WHITE);
outlinePaint.setStrokeWidth(1);
buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
buttonPaint.setAlpha(100);
buttonPaint.setStyle(Paint.Style.STROKE);
buttonPaint.setColor(playActivity.getResources().getColor(R.color.textTitleColor));
buttonPaint.setStrokeWidth(10);
setOnTouchListener(this);
ViewGroup viewGroup = playActivity.findViewById(R.id.playActivityLayout);
viewGroup.addView(this);
}
public void onDraw(Canvas canvas) {
canvas.drawCircle(radius, radius, radius, outlinePaint);
canvas.drawCircle(radius + buttonOffset.getX(), radius + buttonOffset.getY(), buttonRadius, buttonPaint);
}
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
inUse = false;
buttonOffset.setX(0);
buttonOffset.setY(0);
}
else {
inUse = true;
CartesianPoint2D motionPosition = new CartesianPoint2D(motionEvent.getX(), motionEvent.getY());
CartesianPoint2D centerOffset = new CartesianPoint2D(motionPosition.getX() - radius, motionPosition.getY() - radius);
if (centerOffset.getMagnitude() > radius) {
buttonOffset = CartesianPoint2D.createFromMagnitudeAndDirection(radius, centerOffset.getDirection());
} else {
buttonOffset = centerOffset;
}
}
invalidate();
return true;
}
}
Any insight is appreciated, I've been trying to fix this for a few days now and I'm at a loss.
Still don't know exactly what the cause was, but I fixed it by making the game view an extension of SurfaceView so I could draw in a thread other than the main thread.
I've been building a game for some time, and just realized I did something very wrong the whole time, and still don't really know better.
I have a control class, my MainActivity, which pretty much only does the following:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
operator=new Operator(getBaseContext());
setContentView(R.layout.activity_main); //<- framelayout with the view and two buttons on top
}
//Buttons:
public void shoot(View view) {
operator.shoot(view.getId());
}
public void pause(View view) {
AndronXView.running=!AndronXView.running;
}
Then there is my View, which draws Actors and makes my workerthread compute everything:
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
animHandler = new Handler();
animHandler.postDelayed(loadLvl1, 5000);
arrayOfActors = operator.getActors(); //simplyfied to one array
if(arrayOfActors==null)
arrayOfActors=new Actor[0]; //if op hasn't loaded yet, prevent NPE in onDraw
//stripped of unimportant color and size computing stuff
}
protected void onDraw(final Canvas canvas){
//Should I copy player before doing this? Never got problems here so far.
canvas.drawBitmap(operator.player.getCurrentGraphic(), operator.player.getX(), operator.player.getY(), null);
for(Actor actor:arrayOfActors) {
canvas.drawBitmap(actor.getCurrentGraphic(), actor.getX(), actor.getY(), null);
}
if (running) {
operator.run(); //Compute. Realized my mistake here and changed the inside, wait for it.
animHandler.postDelayed(r, Operator.FRAME_RATE); //r=invalidate();
animHandler.post(loadActors); //get Arrays from op
}else animHandler.post(wait);
}
Runnable wait = new Runnable() {
#Override
public void run() {
if (running)
animHandler.post(r);
else animHandler.postDelayed(this, Operator.FRAME_RATE);
}
};
#Override
public boolean onTouchEvent(#NonNull MotionEvent event){
int action = event.getAction();
if(action==MotionEvent.ACTION_DOWN || action==MotionEvent.ACTION_MOVE){
operator.player.setSpeed((event.getX()-operator.player.getHorizontalMid())
/AndronX.PLAYER_MOVE_LAG,
(event.getY()-operator.player.getVerticalMid())/AndronX.PLAYER_MOVE_LAG);
}
return true;
}
And then there is my Operator, extending Thread, which computes movement and interactions of the actors in the background:
public Operator(Context baseContext) {
this.player = new Player(baseContext); //context to load drawable from ressource
arrayListOfActors=new ArrayList<>();
//Looper.prepare(); //it crashed and said only one Looper/Thread
//Looper.loop(); //so I removed it
opHandler = new Handler();
}
#Override
public void run() {
opHandler.post(gameStep); //before, I had the whole processing inside this run().
}
private Runnable gameStep = new Runnable(){
player.move();
computeEveryMemberOf(arrayListOfActors); //much computing happens here, usually
//contains 1-30 Actors that get cross-referenced
arrayOfActors = arrayListOfActors.toArray(new Actor[arrayListOfActors.size()]);
}
public Actor[] getActors(){
return arrayOfActors;
}
Before, I had the computign directly in my operators run() method, which I realized made the background thread useless. I#m not sure though if this is the right way, should I let the operator loop itself, would the two threads kinda stay in sync? Does it even matter?
Or should I go run(){ sleep(FRAME_RATE); compute();}?
Edit: A big problem arose, and I'm not sure if it's because of this, so I really need an answer here how to do this the right way.
With every step, I move some actors a little bit up or down, in a cosinus wave like speed (like it's projection to an axis), and currently, the actual movement doesn't get through to the view, they just jump from max to min and back, although they do it in the desired speed(looks like extreme lag).
Actually you can see for yourself what this problem is: https://dl.dropboxusercontent.com/u/28278772/AndronX.apk
I'm writing a simple Whack a Mole clone, and I've got my UI elements declared in a GridLayout in a layout.xml, then assigned to ImageView variables in an array programmatically. I've got a startGame() method that simply takes a random int, pulls it from the array and causes it to go visible for a second, then repeats. For some reason, when I put this code in a while() loop, it causes my UI to go blank as soon as it's launched.
I know it's the while() loop because I tried taking the code out of the while() loop, and it ran correctly (once), but turns everything white when placed in a while loop.
Here's the method causing the problem:
public void startGame() {
gameStarted = true;
while(gameStarted) {
randomInt = rand.nextInt(11);
mole[randomInt].setVisibility(View.VISIBLE);
handler.postDelayed(new Runnable() {
#Override
public void run() {
mole[randomInt].setVisibility(View.INVISIBLE);
}
}, 5000);
}
}
All the other relevant code is in onCreate, it's otherwise just a skeleton Activity subclass.
public class WAM_Activity extends Activity {
private ImageView[] mole = new ImageView[11];
private int[] moleId = {R.id.mole1, R.id.mole3, R.id.mole4, R.id.mole5, R.id.mole6, R.id.mole7, R.id.mole8, R.id.mole9, R.id.mole10, R.id.mole11, R.id.mole12};
private boolean gameStarted;
private int randomInt = 0;
private Random rand = new Random();
Handler handler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wam_view_layout);
for (int i = 0; i < 11; i++) {
mole[i] = (ImageView) findViewById(moleId[i]);
mole[i].setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//do stuff eventually
}
});
}
gameStarted = true;
startGame();
}
Any idea why this isn't working? I've been staring at it for hours and I'm quite stumped.
Android doesn't work that way, when onCreate is called, it need to be finished in order for the app to keep responding, I'm surprised you are not getting any "App not respopnding" error.
If you want to create a "game loop" you can simply by creating a new Thread and putting the while in there.
Activity's lifecycle must be executed without blocking them for the app to operate correctly, for more info check here.
Do you know about threads? if you want i can post an example of how to do that with threads but it might be long and if you don't know what a Thread is it will be too confusing for you.
Edit: Ok I'll make an example of a Thread
When I create my games I usually have only one Activity that the only thing it does is creating a custom SurfaceView and nothing else.
public class GameActivity extends Activity
{
//This is a custom class that extends SurfaceView - I will write it below
private GameSurface game;
#Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
//Create a new instance of the game
game = new GameSurface(this);
//Set the View of the game to the activity
setContentView(game);
}
}
You can also add extra stuff like onSaveInstanceState to save game data and restore them later but I don't want to add them now so the code looks simple.
This class was very simple, let's move on to our SurfaceView. The reason I picked a SurfaceView to do that it's because it is made to allow custom graphics to be drawn on it - exactly what we want on a video game. I will try to make the class as simple as possible:
/*SurfaceHolder.Callback will run some functions in our class when
our surface is completed - at that point we can initialize data
that have to do with the View's width/height.
I don't know if you've noticed that on a View's onCreate()
when you call getWidth() or getHeight() you get 0, that's because
the surface is not initialized yet, this is a way to fix that.
Also we need a Runnable to run the Thread inside this class,
no need to make more classes and make it more complicated*/
public class GameSurface extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//This is our thread - we need the "running" variable to be
//able to stop the Thread manually, this will go inside our "while" loop
private Thread thread;
private boolean running;
//Right here you can add more variables that draw graphics
//For example you can create a new class that has a function that
//takes Canvas as a parameter and draws stuff into it, I will add
//a Rect in this case which is a class already made by android
//but you can create your own class that draws images or more
//complicated stuff
private Rect myRect;
//Rect needs a paint to give it color
private Paint myPaint;
//Constructor
public GameSurface(Context context)
{
super(context);
//This is the callback to let us know when surface is completed
getHolder().addCallback(this);
}
//When a class implements SurfaceHolder.Callback you are forced to
//create three functions "surfaceCreated", "surfaceChanged" and
//"surfaceDestroyed" these are called when the surface is created,
//when some settings are changed (like the orientation) and when
//it is about to be destroyed
#Override
public void surfaceCreated(Surface holder)
{
//Let's initialize our Rect, lets assume we want it to have 40
//pixels height and fill the screen's width
myRect = new Rect(0, 0, getWidth(), 40);
//Give color to the rect
myPaint = new Paint();
myPaint.setARGB(0, 255, 0, 0);
//In case you are not familiar with the Rect class, as
//parameters it gets Rect(left, top, right, bottom)
//Time to start our Thread - nothing much to explain here if
//you know how threads work, remember this class implements
//Runnable so the Thread's constructor gets "this" as parameter
running = true;
thread = new Thread(this);
thread.start();
}
//We won't use this one for now, but we are forced to type it
//Even if we leave it empty
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
//When the surface is destroyed we just want the Thread to
//terminate - we don't want threads running when our app is not visible!
#Override
public void surfaceDestroyed(SurfaceHolder holder)
//We will type this function later
{destroyThread();}
//Time for the interesting stuff! let's start with input
#Override
public boolean onTouchEvent(MotionEvent event)
{
//The logic is as follows: when our Rect is touched, we want
//it to become smaller
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (myRect.contains((int) event.getX(), (int) event.getY())
{
myRect.right -= 5;
//Return true - we did something with the input
return true;
}
}
return super.onTouchEvent(event);
}
//This is our update, it will run once per frame
private void update()
{
//Let's assume we want our rect to move 1 pixel downwards
//on every frame
myRect.offset(0, 1);
}
//Now for our draw function
public void draw(Canvas canvas)
{
//Here we want to draw a background and our rect
canvas.drawARGB(0, 0, 0, 255);
canvas.drawRect(myRect, myPaint);
}
//The only thing left is our run() function for the Thread
#Override
public void run()
{
//Screen
Canvas canvas;
//Our game cycle (the famous while)
while(running)
{
//Count start time so we can calculate frames
int startTime = System.currentTimeMillis();
//Update our game
update();
//Empty screen so it can obtain new instance
canvas = null;
//Try locking the canvas for pixel editing on surface
try
{
//Try getting screen
canvas = getHolder().lockCanvas();
//Succeeded
if (canvas != null) synchronized (getHolder())
{
//Actual drawing - our draw function
draw(canvas);
}
} finally
{
//Draw changes
if (canvas != null) getHolder().unlockCanvasAndPost(canvas);
}
//End Frame - 1000/30 means 30 frames per second
int frameTime = System.currentTimeMillis() -startTime;
if (frameTime < 1000/30)
try { Thread.sleep(1000/30 -frameTime); } catch (InterruptedException e){}
}
}
//Last but not least, our function for closing the thread
private void destroyThread()
{
//Stop thread's loop
running = false;
//Try to join thread with UI thread
boolean retry = true;
while (retry)
{
try {thread.join(); retry = false;}
catch (InterruptedException e) {}
}
}
}
I may have made some minor mistakes (probably with case sensitive letters) so feel free to correct these, I wrote the code at once so I didn't have time to test it, it should work flawlessly though.
If you have any more questions, need more explanation or something is not working right let me know!
I am quite new to Android and want to create a simple game.
Therefor i need a thread which is drawing a transparent Rectangle on different positions every 4 seconds with a 2 second break (without drawing).
I got it working with a "recursive" thread calling a new instance of itself with handler.postdelayed.
My feeling about threads tells me, that this isn't a very nice way...
While searching here for similar topics, i found out about the Timer construct. Can i use this for my problem?
Is there a better way to do this?
(edit) The thread is meant for highlighting part of the gameboard, but only for 4 seconds. After that there should be 2 seconds without highlighting. Then 4 seconds highlighting the next part of the board etc.
(edit2) I couldn't use sleep, because it froze my UI. If anyone has a similar situation, here is how i solved it:
public class myRunnable implements Runnable {
private int duration;
private int counter;
private boolean highlight;
public myRunnable(int duration, boolean highlight) {
this.duration = duration;
this.highlight = !highlight;
}
#Override
public void run() {
if (highlight) {
// highlight 4s long
highlight();
invalidate();
myThread = new myRunnable(duration, highlight);
postDelayed(myThread, duration);
} else {
// pause (2s)
resetHighlight();
invalidate();
myThread = new myRunnable(duration, highlight);
postDelayed(myThread, noHighlightDuration);
}
}
}
The Best way to do this is use to use invalidate() to call the onDraw() function and a method to update the position. Something like this-
int x,y;
protected void onDraw(Canvas canvas) {
x=10;
y=10;
canvas.drawRect(___);
update();
invalidate();
}
private void update()
{/*Change the x and y/*}
Here everytime inavlidate() is called from anywhere the onDraw() is called again with the new x and y.
There are multiple similar questions like mine, but these questions didn't help me.
I'm making a game. The game thread, SurfaceView and Activity is already finished and works so far. The problem is that the canvas is not redrawn. At startup, it draws the icon on the background, but at every tick, the icon doesn't move (It should move once a second). I want to mention, that I never needed to call postInvalidate. I have a working example where I never called it, but it doesn't work in my current example (I don't want to go into it deeper, since I actually don't need to call it). I copied the current code from my working example, the concept and the way of implementation is exactly the same, but my current code doesn't refresh the canvas. When I log the drawing positions in onDraw method, I see that it's coordinates are updated every second as expected, so I can be sure it's a canvas drawing problem. I have searched for hours but I didn't find what's different to my working example (except that I'm using another Android version and I don't extend thread but implement Runnable, because it's a bad style to extend thread. Nevertheless, I also extended thread to see if there is any difference, but it doesn't help). I already tried to clean the canvas by using canvas.drawColor(Color.BLACK), but that didn't help either. I already tried to use background colors instead of a background image which changes randomly every tick, but it didn't change but stays always the same.
I figured out that the canvas at the very first call has a density of (for example) 240. After the second tick, the canvas density is always 0. I know that the density will not help me here, but maybe it's an important information for someone.
Here are the important classes....
game layout, R.layout.game
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/gameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.mydomain.mygame.base.game.GameSurface
android:id="#+id/gameSurface"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:background="#drawable/background_game" >
</com.mydomain.mygame.base.game.GameSurface>
<!-- ...more definitions -->
</LinearLayout>
GameActivity (contains layout)
public class GameActivity extends Activity
{
#SuppressWarnings("unused")
private GameSurface gameSurface;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
gameSurface = (GameSurface)findViewById(R.id.gameSurface);
//TODO on button click -> execute methods
}
}
GameSurface (log in onDraw shows updated coordinates every tick)
public class GameSurface extends SurfaceView implements SurfaceHolder.Callback
{
private GameThread thread;
protected final static int TICK_FREQUENCY = 100;// ms, stays always the same. It's a technical constant which doesn't change
private static final String TAG = GameSurface.class.getSimpleName();
public GameSurface(Context context, AttributeSet attrs)
{
super(context, attrs);
ShapeManager.INSTANCE.init(context);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
setFocusable(true); // make sure we get key events
thread = new GameThread(holder, this);
}
public void updateStatus()
{
GameProcessor.INSTANCE.updateShapes();
}
#Override
protected void onDraw(Canvas canvas)
{
for (Shape shape : GameProcessor.INSTANCE.getShapes())
{
Log.i(TAG, "getX()=" + shape.getX() + ", getY()=" + shape.getY());
canvas.drawBitmap(shape.getBitmap(), shape.getX(), shape.getY(), null);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
//will never invoked since we only operate in landscape
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
// start the thread here so we don't busy-wait in run
thread.setRunning(true);
new Thread(thread).start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.i(TAG, "executing surfaceDestroyed()...");
thread.setRunning(false);
}
}
GameThread
public class GameThread implements Runnable
{
private SurfaceHolder surfaceHolder;
private boolean running = false;
private GameSurface gameSurface;
private long lastTick;
public GameThread(SurfaceHolder surfaceHolder, GameSurface gameSurface)
{
this.surfaceHolder = surfaceHolder;
this.gameSurface = gameSurface;
lastTick = System.currentTimeMillis();
}
#Override
public void run()
{
Canvas canvas;
while (running)
{
canvas = null;
if (System.currentTimeMillis() > lastTick + GameSurface.TICK_FREQUENCY)
{
long timeDifference = System.currentTimeMillis() - (lastTick + GameSurface.TICK_FREQUENCY);
try
{
canvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
gameSurface.updateStatus();
gameSurface.draw(canvas);
}
}
finally
{
if (canvas != null)
{
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
lastTick = System.currentTimeMillis() - timeDifference;
}
}
}
public void setRunning(boolean running)
{
this.running = running;
}
}
Any ideas why this code doesn't update my canvas? I can't explain it. I do not post ShapeManager and GameProcessor since they don't have anything to do with the problem (and they only load and control the current states and speed of the game).
[UPDATE]
I figured out that onDraw() is invoked before the game thread has started. That means that canvas is passed to this method before thread is using it. The interesting thing is that, after the thread has started, it always uses the same canvas, but it's not the canvas reference which is passed the very first time. Although canvas = surfaceHolder.lockCanvas(null); is assigned every tick, it's always the same reference, but it's not the original reference.
In a working example of mine, the reference is always the same, since I create the bitmaps at constructor initialization time. I can't do that in my current implementation, since I have to do calculations with values I get from onMeasure() which is invoked much later than the constructor.
I tried to somehow pass the original canvas to the thread, but the reference still changes. Meanwhile I think this is the problem, but I don't know how to solve it yet.
As often happens, I found the solution on my own.
Obviously it's really a problem that it draws to different canvas instances. I'm still not sure why this happens. Didn't have the problem before. Nevertheless, I can avoid drawing to canvas by setting setWillNotDraw(true); in my GameSurface constructor and I must not invoke gameSurface.draw(canvas) in my thread, but gameSurface.postInvalidate() instead.