I have a Main Activity and in the corresponding XML Layout I have a Custom View that draws game objects (my tank and 10 enemies), a few buttons to control my tank and fire bullets and a TextView to show my score. My custom view is a GameSurfaceView java class that is a half screen game board.
Here is some of my code:
public class GameSurfaceView extends SurfaceView implements Runnable {
private static Context gContext;
public GameSurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
resume();
gContext = context;
}
public void resume() {
isRunning = true;
gameThread = new Thread(this);
gameThread.start();
}
public void pause() {
isRunning = false;
boolean retry = true;
while (retry) {
try {
gameThread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public void run() {
while (isRunning) {
// We need to make sure that the surface is ready
if (!holder.getSurface().isValid()) {
continue;
}
long started = System.currentTimeMillis();
// update
step();
// draw
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
render(canvas);
holder.unlockCanvasAndPost(canvas);
}
//detect all possible collisions
detectCollisions();
float deltaTime = (System.currentTimeMillis() - started);
int sleepTime = (int) (FRAME_PERIOD - deltaTime);
if (sleepTime > 0) {
try {
gameThread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
while (sleepTime < 0) {
step();
sleepTime += FRAME_PERIOD;
}
}
}
//Called from MainActivity
public void dispatchKey(int tDirection) {
Toast.makeText(gContext, "Hi", Toast.LENGTH_LONG).show();
gameStarted = true;
if (tDirection == FIRE)
Fire();
else if (tDirection != tank.Direction)
turnTankDirection = tDirection;
}
private void detectCollisions() {
//Collision Detection between tank and enemy
Toast.makeText(gContext, "Collision", Toast.LENGTH_LONG).show();
}
}
My questions:
1- Why the Toast in dispatchKey() runs correctly but Toast in detectCollisions() makes a force close?
2- How to update TextView in detectCollisions() method?
3- How to show a DialogAlert when a collision detected in detectCollisions() method?
My problem relates mainly to gContext variable.
Thanks.
Regarding question 1: Maybe this effects the second thread. While dispatchKey() is called from Activity, detectCollision() is invoked from the sureface-thread. Did you tried to call detectCollision() from activity?
Regarding question 3: let your activity implement a listener, which will be called if collision is detected. The same thing you could use as solution of question 1 and 2.
You should try:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
//Make toast or manipulate TextView
}
});
Related
I have an activity like below. I want the threads inside the activity terminate when the activity is paused or stopped. I searched and found volatile Boolean solution which didn't work for me. When i put the activity in pause or stop state, download continues, which i don't want it.
public class MyActivity extends Activity {
//some code here
private void foo(){
new Thread (new Runnable (){
#Override
public void run() {
//download something from internet
}
}).start();
}
}
i used this pattern which didn't work:
public class MyActivity extends Activity {
volatile Boolean state = true;
//some code here
private void foo(){
new Thread (new Runnable (){
#Override
public void run() {
while (state) {
//download something from internet
}
}
}).start();
}
#Override
public void onPause(){
super.onPause();
state = false;
}
#Override
public void onStop(){
super.onStop();
state = false;
}
}
Here's an example of the structure of a thread which stops when back is hit, which I made for my game (and works). One key difference is you're using a thread in your Activity, while in mine I call a View in my Activity and run the thread in the View. If I hit back, I return to my Activity, and can call the thread again by hitting 'start.' BECAUSE my thread behaves the way you want yours too, even if it's happening in View, I thought you may find it helpful.
Where my of my synchronization happens is with the touchscreen values, and making sure those are updated in the thread and calling function.
The thread is trashed totally unless you have a way of saving the state (if you need to).
Your thread will need to synchronize with other functions you want controlling/sharing values with the thread, like onPause, etc..
**you'll need to have some synchronized test value inside your while loop which can then change state to false, otherwise the thread will continue on its own, which is the point of threads.
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private final GameActivity gameActivity;
private GameThread _thread;
private boolean previouslyRunning = false;
private int width; //Screen width
private int height; //Screen height
private boolean newGame = true;
public GameView(Context context) {
super(context);
getHolder().addCallback(this);
this.gameActivity = (GameActivity) context;
_thread = new GameThread(getHolder(), this);
setFocusable(true);
setFocusableInTouchMode(true);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
super.onSizeChanged(w, h, oldw, oldh);
//_thread.initialize();
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!previouslyRunning) {
_thread = new GameThread(getHolder(), this);
_thread.initialize();
}
_thread.setRunning(true);
_thread.start();
previouslyRunning = true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//TODO - this was an Auto-generated method stub...
//TODO - research what this might be useful for
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
//Put stuff that needs destructed here.
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// will will try again and again
//TODO: figure it out....
}
}
}
public boolean onTouchEvent(MotionEvent event) {
int numPointers = event.getPointerCount();
int ptrIdx = 0;
int touch = event.getActionMasked();
if (touch == MotionEvent.ACTION_DOWN) {
while (ptrIdx < numPointers) {
int id = event.getPointerId(ptrIdx);
float xp = event.getX(ptrIdx) / width;
if (xp > 0.6) {
_thread.shieldFront = false;
}
if (xp > 0.6 && !attacks) {
attacks = true;
_thread.attackandDefendToggle(true);
} else if (xp > 0.6 && attacks) {
attacks = false;
_thread.attackandDefendToggle(false);
} else if ((xp < 0.4 && xp > 0.2) && !movedRight) {
movedRight = true;
_thread.moveRight(true);
} else if ((xp < 0.4 && xp > 0.2) && movedRight) {
movedRight = false;
_thread.moveRight(false);
} else if (xp < 0.2 && !movedLeft) {
movedLeft = true;
_thread.moveLeft(true);
} else if (xp < 0.2 && movedLeft) {
movedLeft = false;
_thread.moveLeft(false);
}
ptrIdx++;
}
}
if (touch == MotionEvent.ACTION_UP) {
_thread.moveLeft(false);
_thread.moveRight(false);
_thread.attackandDefendToggle(false);
attacks = false;
_thread.shieldFront = true;
}
return true;
}
class GameThread extends Thread {
/****************************
* Public functions *
****************************/
public GameThread(SurfaceHolder surfaceHolder, GameView panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
// put sounds here.
soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
//more sounds later
//TODO: create sounds
}
/************************************************
* update() function updates all variables, *
* such as physics, Canvas draw points, score *
* life, etc.. It is called before draw. *
************************************************/
private void update() {
// all the values I want updated with each callback
}
/************************************************
* draw() function creates images on screen, *
* but it performs no logic. *
************************************************/
private void draw(Canvas canvas) {
if (canvas == null) {
return;
}
//Draw stuff on screen
}
public void initialize() {
// Values I want the program to start with;
}
public void setRunning(boolean run) {
_run = run;
}
//Code below actually runs the thread.
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
// Update the game state
update();
// Draw image
draw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
I'm trying to write a vintage videogame for Android devices, which "screen" is displayed through a SurfaceView subclass called GraphicSurface. I would also show on the bottom-right corner of the layout an alpha overlay of a "joypad" picture (ImageView subclass called JoyPadView) touch-events sensitive, to use as game controller. Reading around I chose the most accredited solution as follows but it doesn't work. In fact the joypad never appears although it still receive touch events.
I guess the problem is the surfaceView drawing thread continuing to cover the joypad picture but I don't know what really happens and how to manage it.
Here is the layout XML code:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.munchman.GraphicSurface
android:id="#+id/graphicSurface"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.munchman.JoyPadView
android:id="#+id/joyPadView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:alpha="0.8"
android:src="#drawable/joypad"
android:visibility="visible" />
</FrameLayout>
And here's the (simplified) code of SurfaceView subclass:
public class GraphicSurface extends SurfaceView implements SurfaceHolder.Callback {
//... class attributes
// INNER CLASS: DRAWING THREAD --------------
class DrawingThread extends Thread {
private boolean running = false;
public synchronized void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long timeInterval = 1000l / fps;
long startTime = System.currentTimeMillis();
long sleepTime;
Bitmap scrBmp;
Canvas c;
while (running) {
// DRAWING
c = null;
startTime = System.currentTimeMillis();
try {
c = holder.lockCanvas();
if (c != null && screen != null) {
synchronized (holder) {
//... "screen" drawing code
c.drawBitmap(scrBmp, origRect, destRect, null);
}
}
else {
if (c == null) Log.w(TAG, "run(): lockCanvas returned null canvas");
else Log.w(TAG, "run(): null screen!");
}
} catch (Exception e) {
Log.e(TAG, "run(): drawing canvas:" + Log.getStackTraceString(e) );
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
// SLEEP TIME
sleepTime = timeInterval - (System.currentTimeMillis()-startTime);
try {
if (sleepTime > 0) sleep(sleepTime);
else sleep(timeInterval);
} catch (Exception e) {
Log.e(TAG, "run(): sleeping: " + e);
}
}
}
}
private DrawingThread drawingThread = null;
// CONSTRUCTORS -------------------
public GraphicSurface(Context context) {
super(context);
init();
}
public GraphicSurface(Context context, AttributeSet attributes) {
super(context, attributes);
init();
}
public GraphicSurface(Context context, AttributeSet attributes, int defStyle) {
super(context, attributes, defStyle);
init();
}
// CLASS METHODS -----------------
private void init () {
Log.d(TAG, "init()");
holder = getHolder();
holder.addCallback(this);
setFocusable(true);
drawingThread = new DrawingThread();
drawingThread.setName(TAG);
fps = DEFAULT_FPS;
//... other code
}
/** Enable the SurfaceView embedded drawing thread.
*/
public synchronized void startDrawingThread() {
Log.d(TAG, "startDrawingThread()");
if (drawingThread == null) drawingThread = new DrawingThread();
if (!drawingThread.isAlive()) {
drawingThread.setRunning(true);
drawingThread.start();
}
}
}
Can anyone help me ?
I try to display one from list of bitmaps during onDraw.
When i'm passing list to the canvas all are display and stay in their places.
When I pass one random bitmaps it's redrawing canvas all the time.
All works when i'm using public void drawEnemy(Canvas canvas) but not exactly like I want when using public void drawEn(Canvas canvas).
I want to display one random bitmap, then after a few seconds, delete it and display other bitmap. I think the problem is how I implemented onDrow() method. It's redrawing canvas all the time.
Activity:
public class NewGameActivity extends Activity{
NewGame newgame;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
// Landscape mode
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// no title
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// content Newgame.java
newgame = new NewGame(this);
setContentView(newgame);
}
Thread:
public class MainThread extends Thread{
private SurfaceHolder surfaceHolder;
private NewGame screen;
public MainThread(SurfaceHolder surfaceHolder, NewGame ekran) {
super();
this.surfaceHolder = surfaceHolder;
this.screen= screen;
}
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
Canvas canvas;
while (running) {
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
this.screen.onDraw(canvas);
}
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
SurfaceView:
public class NewGame extends SurfaceView implements SurfaceHolder.Callback{
private MainThread thread;
private EnemyManager manager;
public NewGame(Context context) {
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(), this);
manager = new EnemyManager();
// TODO Auto-generated constructor stub
//adding enemy
Enemy e1 = new Enemy(BitmapFactory.decodeResource(getResources(), R.drawable.card), 1);
Enemy e2 = new Enemy(BitmapFactory.decodeResource(getResources(), R.drawable.horse), 2);
EnemyLocation l1 = new EnemyLocation(60, 180);
EnemyLocation l2 = new EnemyLocation(60, 50);
manager.AddEnemy(e1, l1);
manager.AddEnemy(e2, l2);
setFocusable(true);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.saloon), 0, 0, null);
manager.drawEn(canvas);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread.setRunning(false);
thread.stop();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
manager.handleActionDown((int)event.getX(), (int)event.getY());
}
return true;
}
}
EnemyManager:
public class EnemyManager {
private ArrayList<Enemy> enemyList;
private ArrayList<Enemy> suspects;
private Enemy cow;
private String message;
private int suspectID;
private Random rnd;
public String getMessage() {
return message;
}
public EnemyManager(){
enemyList = new ArrayList<Enemy>();
suspects = new ArrayList<Enemy>();
}
public void AddEnemy(Enemy enemy, EnemyLocation loc){
// set x,y enemy localization
enemy.setX(loc.getX());
enemy.setY(loc.getY());
enemyList.add(enemy);
}
public void clearEnemy() {
enemyList.clear();
}
// message if enemy touched
public void handleActionDown(int x, int y) {
for (Enemy enemy: enemyList) {
if (enemy.wasTouched(x, y)) {
message = enemy.getId();
return;
}
}
}
public void PrepareEnemy(){
suspectID = enemyList.get(rnd.nextInt(enemyList.size()+1)).getId();
suspects = new ArrayList<Enemy>();
suspects.add(getSuspectByID(suspectID));
}
private Enemy SingleEnemy(){
Double i = 1 + Math.random() * ((enemyList.size()-1)+1);
cow = getSuspectByID(i.intValue());
return cow;
}
private Enemy getSuspectByID(int suspectID) {
for (Enemy s: enemyList) {
if (s.getId() == suspectID) {
return s;
}
}
return null;
}
public void drawEn(Canvas canvas){
try {
Enemy k = SingleEnemy();
canvas.drawBitmap(cow.picture, cow.x, cow.y, null);
} catch (Exception e) {
// TODO: handle exception
}
}
// draw enemy
public void drawEnemy(Canvas canvas) {
try {
for (Enemy enemy: enemyList) {
canvas.drawBitmap(enemy.picture, enemy.x, enemy.y, null);
}
} catch (Exception e) {
// TODO: handle exception
}
}
}
das
As for as understand you are trying to do something like this (if it's not, please correct me):
This is rendering the canvas with all components:
Draw background
Draw enemy
To "refresh" the canvas you simply do something like this:
Draw background
Update
To pause the rendering you could do something like this:
int lastUpdateTime;
int delayTime = 2000; 2 seconds
if(System.currenttimeMillis() > lastUpdateTime + delayTime) {
// Finished waiting
}
You should only define lastUpdateTime when you want to wait and not in every iteration.
NB: Don't call Thread.sleep() in a rendering thread!
I am fairly new developer and trying to make a live wallpaper app. Among many animations, my first target is to show a rotating Bitmap which is actually a Black Hole.
public class Painting extends Thread {
/** Reference to the View and the context */
private SurfaceHolder surfaceHolder;
private Context context;
/** State */
private boolean wait;
private boolean run;
/** Dimensions */
private int width;
private int height;
/** Time tracking */
private long previousTime;
boolean first = true;
Bitmap hole;
int degree;
public Painting(Context con , SurfaceHolder surf)
{
context = con;
surfaceHolder = surf;
this.wait = true;
Log.i("Live Test","UnInitialized");
Drawable d = (con.getResources().getDrawable(R.drawable.vlack));
hole = ((BitmapDrawable)d).getBitmap();
hole.prepareToDraw();
if(hole != null)
Log.i("Live Test","Initialized");
run = true;wait = false;
degree = 0;
}
#Override
public void run()
{
while (run) {
this.run = true;
Canvas c = null;
Log.i("Live Test","Draw Color");
while (run) {
try {
c = this.surfaceHolder.lockCanvas();
synchronized (this.surfaceHolder) {
doDraw(c);
}
} finally {
if (c != null) {
this.surfaceHolder.unlockCanvasAndPost(c);
Log.i("Live Test","Unlocked And Posted");
}
}
// pause if no need to animate
synchronized (this) {
if (wait) {
try {
wait();
} catch (Exception e) {
Log.i("Live Test","Error wait");
}
}
}
}
}
}
public void setSurfaceSize(int width, int height) {
this.width = width;
this.height = height;
synchronized(this) {
this.notify();
}
}
/**
* Pauses the livewallpaper animation
*/
public void pausePainting() {
this.wait = true;
synchronized(this) {
this.notify();
}
}
/**
* Resume the livewallpaper animation
*/
public void resumePainting() {
this.wait = false;
synchronized(this) {
this.notify();
}
}
/**
* Stop the livewallpaper animation
*/
public void stopPainting() {
this.run = false;
synchronized(this) {
this.notify();
}
}
private void doDraw(Canvas canvas) {
if(first)
{
canvas.save();
canvas.drawColor(0x60444444);
canvas.drawBitmap(hole, 80,80,null);
canvas.restore();
first = false;
}
else
{
long currentTime = System.currentTimeMillis();
long elapsed = currentTime - previousTime;
if (elapsed > 20) {
canvas.save();
degree+= 5;
if(degree>359)degree = degree -358;
canvas.rotate((float) degree);
canvas.restore();
Log.i("Live Test","rotated");
}
previousTime = currentTime;
}
}
}
So I am trying to rotate the bitmap and show it again so it looks like its sucking stars and all.
Also I have removed Basic onPause onResume Functions so that you guys can understand the code easily. I know there is something basic I am missing, but What?
Hmmm. It would take more time than I have right now to puzzle this out, but I do have one suggestion: code your wallpaper using a simpler approach. Scrap the thread and do something more along the lines of the Cube example, that is to say make a runnable which schedules calls to itself using postDelayed. Hope this helps. George.
I'm experimenting with SurfaceView and creating simple animations with it, I don't understand why my animation (changins screen color from black to white) is working here (without using SurfaceHolder.Callback)
public class MySurface extends SurfaceView {
private boolean playing = true;
private int counter = 0;
public MySurface(Context context){
super(context);
new Anim().start();
}
private class Anim extends Thread {
#Override
public void run() {
while (playing) {
try {
sleep(1000);
draw();
counter++;
} catch (Exception e){
e.printStackTrace();
}
}
}
private void draw() {
SurfaceHolder holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
if (counter % 2 == 0) {
canvas.drawColor(Color.WHITE);
} else
canvas.drawColor(Color.BLACK);
holder.unlockCanvasAndPost(canvas);
}
}
}
}
But it's not working here, what's the difference? I think it might be caused by calling .run() instead of .start()
public class MySurface extends SurfaceView implements SurfaceHolder.Callback {
Anim anim;
private boolean playing = true;
private int counter = 0;
public MySurface(Context context){
super(context);
}
// all Callbacks are overrided i didn add them to make code easier to read
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
anim = new Anim();
anim.run();
}
private class Anim extends Thread {
#Override
public void run() {
while (playing) {
try {
sleep(1000);
draw();
counter++;
} catch (Exception e){
e.printStackTrace();
}
}
}
private void draw() {
SurfaceHolder holder = getHolder();
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
if (counter % 2 == 0) {
canvas.drawColor(Color.WHITE);
} else
canvas.drawColor(Color.BLACK);
holder.unlockCanvasAndPost(canvas);
}
}
}
}
EDIT: I forgot to add getHolder().addCallback(this) that caused the issue.
Could someone still point out the differnces between those two methods and tell which one is better?
EDIT2: anim.start() was also needed