I have a few issues I am having a hard time find good information on how to fix. First off I have a Game Over screen that shows up when it should. I have tried to use setting the Threads running to false so it stop running and then when the screen is touched to set it back to true, but it does not take the screen back to it running. Also I'm going to need to be able to actually clear all the times so that it resets it (what is a good way to do that). This similarly I will need I am assuming for the Pause and Resume. If running isnt set to false it appears to continue running even with sleep() just in slower increments. Here is my thread to see if you notice what I can do.
public class GameLoopThread extends Thread {
private GameView view;
public static boolean running = false;
static final long FPS = 10;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
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) {}
}
}
}
Now this part is causing a large issue for the Pause/Resume. I have a handler.postDelayed handling the spawning of my sprites. Basically when the screen appears paused (cause it looks like it is if i use the sleep) the problem is that the handler (which is in the GameView) is seeming like the time is still running for it. Is there a way to reset it back to zero... preferably on both the classes for the Game Over and to pause the handler as well during the Pause? Thanks
EDIT: As of right now I'm trying to use an options menu to make a new game by making it reopen the activity that runs the game. It appears to reset it but it freezes it in the process. Anyone know how I can fix this?
#Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.newgame:
GameView.gameState=0;
GameLoopThread.running=true;
Intent game = new Intent(PlayGame.this, PlayGame.class);
startActivity(game);
break;
case R.id.pause: Toast.makeText(this, "You pressed Pause", Toast.LENGTH_LONG).show();
break;
case R.id.quit: Toast.makeText(this, "You pressed Quit", Toast.LENGTH_LONG).show();
break;
}
return true;
}
Thats my options menu.
This may not seem like the anwser you are looking for, but if you want to create code that's easily extendable (and learn a new desgin pattern!) then this is the way to go.
Most games use a State Machine for these kind of things.
For example
interface GameState{
public void render(long elapsedTime);
}
Now you can create different states, like a MenuState, GameState, PauseState, OptionState, GameOverState, etc.
Some pseudo code to get the idea across:
State currentState = null;
public void run(){
while(true){
// Do your loop here
currentState.render(time);
}
}
public void setState(State state){
currentState = state;
}
States Example:
public class MenuState implements GameState{
public void render(long elapsedTime){
//Render menu here
}
}
Or
public class GameState implements GameState{
public void render(long elapsedTime){
//Render gameplay here
}
}
currentState points to the current state and decides which one gets rendered/updated. Just change it's reference and your pause menu gets drawn.
This is a very common design pattern in games, search around!
Good luck!
Related
I developed an standalone app for a smartwatch, and I want to have an OnTouchListner which detect if a Touch lasted for at least 3 sec and perform action only in that case.
Here is the code I use : (a simple boolean state which is set to false when MotionEvent is up and a PostDelayed action on the Handler)
private int longClickDuration = 3000;
private boolean isLongPress = false;
static Runnable longTouchRunnable;
mContainerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
longTouchRunnable = new Runnable() {
#Override
public void run() {
if (isLongPress) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
long[] vibrationPattern = {0, 500, 50, 300};
//-1 - don't repeat
final int indexInPatternToRepeat = -1;
vibrator.vibrate(vibrationPattern, indexInPatternToRepeat);
// set your code here
setStateStarting(); //Block the GUI, only one click and waiting for tablet
presenter.onStartClick();
Log.d("Long","LONG CLICK PERFORMED");
// Don't forgot to add <uses-permission android:name="android.permission.VIBRATE" /> to vibrate.
}
}
};
switch (currentState) {
case START: {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
isLongPress = true;
Log.e("Long touch","Start");
myUIHandler.postDelayed(longTouchRunnable, longClickDuration);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
isLongPress = false;
myUIHandler.removeCallbacks(longTouchRunnable);
Log.e("Long touch", "Stop");
}
return true;
}
}
return false;
}
});
My problem :
Everything work perfectly expect that the Runnable doesn't seem to be delete when it's necessary.
--> If I click quickly two times on the view and then a third time and holding it (So the boolean state is true for the three PostDelayed which came after 3 sec) I will have my specific action code executed 3 times --> the Runnable task wasn't removed with the ACTION_UP event.
Info : The message debug are prompt as expected...
How can I properly destroy a Runnable that was postdelayed in a handler ?
Thanks
handler.removeCallbacksAndMessages(null);
In the docs for removeCallbacksAndMessages it says...
Remove any pending posts of callbacks and sent messages whose obj is token. If a token is null, all callbacks and messages will be removed.
removeCallbacks only stops pending messages (Runnables). If your runnable has already started, then there's no stopping it (at least not this way).
Alternatively, you can extend the Runnable class and give it some kind of kill switch like this:
public class MyRunnable implements Runnable
{
public boolean killMe = false;
public void run()
{
if(killMe)
return;
/* do your work */
}
public void killRunnable()
{
killMe = true;
}
}
This will only prevent it from starting, but you could occasionally check killMe and bail out. If you are looping the runnable (like some kind of background thread) you can say:
while(!killMe) {
/* do work */
}
I've been working on a sort of a game engine for Android but I have some serious stability issues.
I use a SurfaceView with an implemented Runnable for Multithreading, to get some work of my main / UI thread. The simplified version of this class is shown below:
private class MenuView extends SurfaceView implements Runnable
{
Thread T = null;
SurfaceHolder holder;
Canvas c;
boolean run;
public MenuView(Context context) {
super(context);
holder = getHolder();
}
#Override
public void run() {
while(run)
{
if(!holder.getSurface().isValid())
{
continue;
}
c = holder.lockCanvas();
//Doing updates and drawing stuff
holder.unlockCanvasAndPost(c);
}
}
public void pause()
{
run = false;
try
{
T.join();
}
catch (InterruptedException e)
{
Log.e("Error:", "joining thread failed!");
}
}
public void resume()
{
run = true;
T = new Thread(this);
T.start();
}
//bunch of helper methods with no effect on this problem :(
}
So as you can see, my "Worker" thread is caught in an infinite while loop that is controlled by the "run" boolean, and this boolean is set using pause() and resume() methods called in onPause() and onResume() methods of my activity as shown here:
MenuView menuV;
Random Rand = new Random();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Screen = getRealScreenSize(getApplicationContext());
menuV = new MenuView(this);
menuV.setOnTouchListener(this);
logoV = new LogoView(this);
setContentView(logoV); Log.d("APP", "SET LOGO VIEW");
//^ This is my splash screen, again irrelevant to the issue, after few seconds of running, this view calls the resume() of my SurfaceView and sets it as the contentView.
}
#Override
protected void onPause() {
super.onPause();
Log.d("APP", "ON PAUSE");
menuV.pause();
}
#Override
protected void onResume() {
super.onResume();
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
//^hides the goddamn navigation bar...
Log.d("APP", "ON RESUME");
menuV.resume();
}
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
Log.d("APP", "ON TOUCH DOWN");
for(int i = 0; i < menuV.button.length ; i++)
{
if(menuV.button[i].active)
{
if(event.getX() > menuV.button[i].pos.left && event.getX() < menuV.button[i].pos.right && event.getY() > menuV.button[i].pos.top && event.getY() < menuV.button[i].pos.bottom)
{
menuV.button[i].pressed = true;
return true;
}
}
}
}
else if(event.getAction() == MotionEvent.ACTION_UP)
{
Log.d("APP", "ON TOUCH UP");
for(int i = 0; i < menuV.button.length ; i++)
{
menuV.button[i].pressed = false;
}
}
return false;
}
So my touch event is handled by the UI thread, but it's accesing info that the worker thread is constantly using, the same thing happens in onPause() and onResume(), the UI thread is changing the "run" boolean which is always used by the worker thread, I'm saying this because it seems that the ANRs are happening when touching the screen and when power-off-ing and power-on-ing the phone. Rendering doesn't stop, it seems that the worker thread is just fine but the UI thread messes up badly, it stops responding and this line shows on my Android Monitor:
Thread[2,tid=18257,WaitingInMainSignalCatcherLoop,Thread*=0x558cbf1310,peer=0x12c470a0,"Signal Catcher"]: reacting to signal 3
Wrote stack traces to '/data/anr/traces.txt'
Please let me know if there is any further useful information - I will EDIT in anything you point out as important after this line, Thanks!
Try marking run variable as volatile:
volatile boolean run;
Maybe you have caching-related problem.
java.lang.IllegalThreadStateException: Thread already started
at java.lang.Thread.checkNotStarted(Thread.java:849)
at java.lang.Thread.start(Thread.java:1059)
at com.name.MainActivity$MenuView$1.surfaceCreated(MainActivity.java:496)
How can I avoid this? I have an inner class MenuView which extends SurfaceView. It updates a canvas on a thread. onSurfaceCreated() I start the thread, and onSurfaceDestroyed() I call thread.join() to stop the thread. However, if the Google Play Sign in window pops up, and I press the home button while it is in focus, and reopen the app. I get a force close error and the exception that I posted above.
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
allow_thread_to_run = true;
boolean retry = true;
while (retry) {
if (t!=null && !t.isAlive()) {
t.start();
retry = false;
}
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
allow_thread_to_run = false;
boolean retry = true;
if(t!=null && t.isAlive()) {
while (retry) {
try {
t.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
}
So why does this Google Play window coming into focus cause my thread to start twice. From what I see, my setup has the necessary check isAlive() to make sure to avoid this!
I agree with #adelphus's view.If you care for the code of Thread calss,you will find that.Every Thread object has a flag that reflects whether this Thread has already been started.
boolean hasBeenStarted = false;
Once you invoke start() method,it will check the state and change if it is false,otherwise throw an exception like what you have got.see,
public synchronized void start() {
checkNotStarted();
hasBeenStarted = true;
nativeCreate(this, stackSize, daemon);
}
later,the state will never be changed. When you invoke start() again,it will throw an exception.
I try to use this code to prevent multi-click in ImageView but it doesn't help.
Boolean isClicked = false;
#Override
public void onClick(View v)
{
if (v == imgClick && !isClicked)
{
//lock the image
isClicked = true;
Log.d(TAG, "button click");
try
{
//I try to do some thing and then release the image view
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
isClicked = false;
}
}
In the log cat, I can see 5 lines "button click" when I click on ImageView for 5 times as quickly as possible. I can see the log cat print the first line, wait for a while (2 seconds) and then print the next line. I think when I click the ImageView, the fired event is moved to queue in order, isn't it?. So how can I stop that?
I also try to use setEnable() or setClickable() instead of isClicked variable but it doesn't work too.
Just try this working code
Boolean canClick = true; //make global variable
Handler myHandler = new Handler();
#Override
public void onClick(View v)
{
if (canClick)
{
canClick= false; //lock the image
myHandler.postDelayed(mMyRunnable, 2000);
//perform your action here
}
}
/* give some delay..*/
private Runnable mMyRunnable = new Runnable()
{
#Override
public void run()
{
canClick = true;
myHandler.removeMessages(0);
}
};
Instead of sleeping in 2 seconds, I use some task like doSomeThing() method (has accessed UI thread), and I don't know when it completed. So how can I try your way?
//I referred this android link. You can handle thread more efficiently but i hope below code will work for you..
//you try this and
Boolean canClick = true; //make global variable
public void onClick(View v) {
if(canClick){
new DownloadImageTask().execute();
}
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
Log.d("MSG","Clicked");
canClick =false;
//perform your long operation here
return null;
}
protected void onPostExecute(Bitmap result) {
canClick =true;
}
}
You could keep track of the last consumed click upon your View, and based on it either perform the necessary actions, or simply return:
private long calcTime;
private boolean isClickedLately(final long millisToWait)
{
if (System.currentTimeMillis() - calcTime < millisToWait)
return true;
return false;
}
#Override
public void onClick(View v)
{
if (isClickedLately(2000))
return;
calcTime = System.currentTimeMillis();
Log.d(TAG, "consuming button click");
// perform the necessary actions
}
With the millisToWait parameter you can adjust the threshold of "waiting", but if you know that you want to wait exactly 2 seconds between two consecutive clicks, you can eliminate it.
This way you don't have to deal with Threads, which is good, since it's not a great idea to make the gui thread wait.
Well I have made a custom Android UI, and I need my UI view to handle this control.
a) While my mouse button is pressed (onDown / Key pressed) , the view should keep doing a thing (For example : Key is down)
b) As soon as i release my mouse key , the view should say (example: Key is up).
The Sample Flow should be something like when I press the view.
Output:
(I press mouse button on the view and hold it)
Key is down
Key is down
Key is down
Key is down
Key is down
Key is down
(I now release mouse button)
Key is up.
To more explain my problem . I am posting a code snippet where the logic should go
#Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, event.getAction());
}
When I press my mouse button , it prints "0" (meaning mouse is down), while if i leave it , it prints on the log "1", meaning mouse is up. That should help with the logic.
Thanks for helping.
Well I have tried something like
private static int checkValue = 0;
#Override
public boolean onTouchEvent(MotionEvent event) {
checkValue = event.getAction();
while (checkValue == 0 ){
Log.d(TAG, "Pressed");
checkValue = checkMethod(event.getAction);
}
private int checkMethod(int test){
if (checkValue == 0){
checkValue = 0;
}
else checkValue = 1;
}
Just define an OnTouchEventListener...
private OnTouchListener onTouchListener = new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//start loop or background task
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//do something different
}
}
return true;
}
}
...and assign it to your button.
button.setOnTouchListener(onTouchListener);
When the user touches the button the ACTION_DOWN event is fired. When the user releases the button, the ACTION_UP event is fired. If you want you can start a loop in a thread which can check against a boolean variable. I didn't check if it is correct but it should look like this:
private OnTouchListener onTouchListener = new OnTouchListener() {
private boolean flag = false;
#Override
public boolean onTouch(View v, MotionEvent event) {
if (v.getId() == R.id.button) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
flag = true;
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
while(flag) {
//do something
}
}
});
thread.start();
} else if (event.getAction() == MotionEvent.ACTION_UP) {
flag = false;
}
}
return true;
}
}
Maybe you could create and start a thread when the touch occurs that has a volatile boolean stop = false field and that checks for this value in every loop after sleeping for a while. When the touch ends, you can set the boolean variable to false.
Edit: added an example
private MyHandler h;
public void handleDown() {
System.out.println("touch began");
h = new MyHandler();
h.start();
}
public void handleUp() {
h.pleaseStop = true;
System.out.println("touch ended");
}
class MyHandler extends Thread {
volatile boolean pleaseStop = false;
public void run() {
while (!pleaseStop) {
System.out.println("touch is active...");
Thread.sleep(500);
}
}
}
checkValue = event.getAction();
while (checkValue == 0 ){
You should never ever be using magical integers. Use the constants as defined by the API:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_DOWN
In fact your code isn't even accounting for move events; it seems to assume that if it isn't a down event it is an up... which is completely wrong.
Look at the documentation to see what action values you can expect, and do the right thing for the specific actions you are interested in. For example up:
http://developer.android.com/reference/android/view/MotionEvent.html#ACTION_UP
Create a thread which is initiated by the touch and have boolean flag to keep it alive. When the touch is released, set the flag to false so the thread joins (terminates).
Because the thread is independent from the main logic, the process is faster.