Reputation: 1154
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.
Upvotes: 0
Views: 1071
Reputation: 1494
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!
Upvotes: 2