I have designed a simple multitouch system that tracks finger location, and their status on the screen. The way that the program is designed, when two fingers are put down, they are assigned a number based on the order of placement. If finger one is lifted, finger two is still called finger two.
This code works perfectly part of the time. Meaning that I'll run my application in testing and sometimes multitouch works every time, and sometimes using more than one finger makes the application completely fail. It is irregularly reliable.
I am curious if this is a hardware error, or something about the way that I am handling touch input. The source is all available below.
Thanks for any help that you can offer!
-Nathan Tornquist
EDIT: I think that the issue may be with the way that eventListeners are registered and unregistered. Occasionally the application will crash, and work perfectly again when I reopen it.
EDIT2: The issue is simply how irregular the program is. Sometimes when I open it, it tracks the fingers perfectly. The first finger put down stays number 1, the second finger stays number 2, etc. Regardless of if you lift finger 1 after placing 2, the numbers stay assigned. Other times you'll place two fingers and which finger is 1 and which is 2 changes. The application seems to lose track of them, and the numbers switch as you leave the fingers on the screen.
EDIT3: I have tried "Touchscreen boosters" to try to try the screen to always respond correctly. It did not solve the problem.
EDIT4: After more testing, this is clearly a code error. When the application is first started, it always works perfectly. After the device is locked (when the activity is paused) and then unlocked (when the activity is resumed) multitouch stops working and I get an application that works well for single touch and is confused by multitouch.
MultitouchGameFixActivity.java
package com.nathantornquist.multitouchgame;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
public class MultiTouchGameFixActivity extends Activity{
/** Called when the activity is first created. */
MainGamePanel viewPanel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Window state functions.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//This works without declaring a viewPanel instance here.
//The instance declaration is needed to pass the
//onPause and onResume commands though.
viewPanel = new MainGamePanel(this);
setContentView(viewPanel);
}
//Restarts the accelerometer after onPause
protected void onResume() {
super.onResume();
viewPanel.resume(this);
}
//Standard Method run when the Application loses focus.
//This runs the pause() function in the viewPanel so that
//the accelerometer can be paused.
protected void onPause() {
super.onPause();
viewPanel.pause();
}
protected void onDestroy() {
super.onDestroy();
viewPanel.destroy();
}
}
MainThread.java
package com.nathantornquist.multitouchgame;
import com.nathantornquist.multitouchgame.MainGamePanel;
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private SurfaceHolder surfaceHolder;
private MainGamePanel gamePanel;
private boolean running;
public boolean pleaseWait = true;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run()
{
Canvas canvas;
while (running) {
if(!pleaseWait) {
canvas = null;
// try locking the canvas for exclusive pixel editing on the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// update game state
this.gamePanel.update();
// draws the canvas on the panel
this.gamePanel.onDraw(canvas);
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
else {
synchronized (this) {
try {
wait();
} catch (Exception e) { }
}
}
}
}
}
MainGamePanel.java
package com.nathantornquist.multitouchgame;
import android.R.string;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView implements SensorEventListener, SurfaceHolder.Callback
{
//Variable Declarations.
private MainThread thread;
public int screenWidth;
public int screenHeight;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
Paint paint;
public int fingerOneDown;
public int fingerTwoDown;
public int fingerThreeDown;
public int fingerFourDown;
public float fingerOneX;
public float fingerOneY;
public float fingerTwoX;
public float fingerTwoY;
public float fingerThreeX;
public float fingerThreeY;
public float fingerFourX;
public float fingerFourY;
public MainGamePanel(Context context)
{
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(),this);
paint = new Paint();
paint.setAntiAlias(true);
Display display = ((Activity) context).getWindowManager().getDefaultDisplay();
screenWidth = display.getWidth();
screenHeight = display.getHeight();
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
fingerOneDown = 0;
fingerTwoDown = 0;
fingerThreeDown = 0;
fingerFourDown = 0;
fingerOneX = 0;
fingerOneY = 0;
fingerTwoX = 0;
fingerTwoY = 0;
fingerThreeX = 0;
fingerThreeY = 0;
fingerFourX = 0;
fingerFourY = 0;
setFocusable(true);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
//continue the thread
synchronized (thread) {
thread.pleaseWait = false;
thread.notifyAll();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
//pause the thread
synchronized (thread) {
thread.pleaseWait = true;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
int pointerId = event.getPointerId(pointerIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
Log.d("pointer id - down",Integer.toString(pointerId));
if (pointerId == 0)
{
fingerOneDown = 1;
fingerOneX = event.getX(pointerIndex);
fingerOneY = event.getY(pointerIndex);
}
if (pointerId == 1)
{
fingerTwoDown = 1;
fingerTwoX = event.getX(pointerIndex);
fingerTwoY = event.getY(pointerIndex);
}
if(pointerId == 2)
{
fingerThreeDown = 1;
fingerThreeX = event.getX(pointerIndex);
fingerThreeY = event.getY(pointerIndex);
}
if(pointerId == 3)
{
fingerFourDown = 1;
fingerFourX = event.getX(pointerIndex);
fingerFourY = event.getY(pointerIndex);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL:
Log.d("pointer id - cancel",Integer.toString(pointerId));
if (pointerId == 0)
{
fingerOneDown = 0;
fingerOneX = event.getX(pointerIndex);
fingerOneY = event.getY(pointerIndex);
}
if (pointerId == 1)
{
fingerTwoDown = 0;
fingerTwoX = event.getX(pointerIndex);
fingerTwoY = event.getY(pointerIndex);
}
if(pointerId == 2)
{
fingerThreeDown = 0;
fingerThreeX = event.getX(pointerIndex);
fingerThreeY = event.getY(pointerIndex);
}
if(pointerId == 3)
{
fingerFourDown = 0;
fingerFourX = event.getX(pointerIndex);
fingerFourY = event.getY(pointerIndex);
}
break;
case MotionEvent.ACTION_MOVE:
int pointerCount = event.getPointerCount();
for(int i = 0; i < pointerCount; ++i)
{
pointerIndex = i;
pointerId = event.getPointerId(pointerIndex);
Log.d("pointer id - move",Integer.toString(pointerId));
if(pointerId == 0)
{
fingerOneDown = 1;
fingerOneX = event.getX(pointerIndex);
fingerOneY = event.getY(pointerIndex);
}
if(pointerId == 1)
{
fingerTwoDown = 1;
fingerTwoX = event.getX(pointerIndex);
fingerTwoY = event.getY(pointerIndex);
}
if(pointerId == 2)
{
fingerThreeDown = 1;
fingerThreeX = event.getX(pointerIndex);
fingerThreeY = event.getY(pointerIndex);
}
if(pointerId == 3)
{
fingerFourDown = 1;
fingerFourX = event.getX(pointerIndex);
fingerFourY = event.getY(pointerIndex);
}
}
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas)
{
paint.setColor(Color.WHITE);
paint.setStyle(Style.FILL);
canvas.drawPaint(paint);
if (fingerOneDown == 1)
{
paint.setColor(Color.BLUE);
paint.setTextSize(20);
canvas.drawText("1", fingerOneX, fingerOneY - 30, paint);
}
if (fingerTwoDown == 1)
{
paint.setColor(Color.RED);
paint.setTextSize(20);
canvas.drawText("2", fingerTwoX, fingerTwoY - 30, paint);
}
if (fingerThreeDown == 1)
{
paint.setColor(Color.GREEN);
paint.setTextSize(20);
canvas.drawText("3", fingerThreeX, fingerThreeY - 30, paint);
}
if (fingerFourDown == 1)
{
paint.setColor(Color.BLACK);
paint.setTextSize(20);
canvas.drawText("4", fingerFourX, fingerFourY - 30, paint);
}
}
public void update() {
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event) {
}
public void pause() {
mSensorManager.unregisterListener(this);
}
public void resume(Context context) {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
}
public void destroy() {
thread.setRunning(false);
if (thread != null)
{
Thread killThread = thread;
thread = null;
killThread.interrupt();
}
}
}
I'll throw out some ideas, even though my android skills are a bit lacking...
How are you testing this? On a real device I guess? Can this be tested on the emulator? It seems like it would be prone to hardware issues.
If the there is a glitch and the screen looses track of the fingers even just for a millisec, wont it start over with indexing the fingers? If that is the case it would number the fingers pretty randomly causing the issues you are experiencing.
Some ideas:
Dont track the index of fingers, just keep list of the fingers and their relative positions. But I guess part of the game requires you to keep an index of each finger.
Log if you loose connection with a finger, that will show if this is an isse. I guess MotionEvent.ACTION_POINTER_UP is the event you should log then. In fact, I'd add a log for each event possible just to see what is happening.
Add a method that tries to determine which finger has been placed on the screen, i.e it compares the current position of the finger with the known values of previous fingers and if its within a threshold, assume its the same finger. Probably want to add a timer to "forget" previous fingers aswell.
I guess you have been looking at the multitouch example on Android developer blog to make sure you are using the APIs correctly?
Hope it may give you some help.
The problem is solved by disabling WidgetLocker. That application seems to mess up some programs occasionally. I've noticed issues in other applications as well when I lock the screen while playing a game.
Related
I am trying do develop may first android game in 2d. I intend to build it from scratch without the use of an engine. I have manage to create the following thread:
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private int FPS = 30;
private double averageFPS;
private GamePanel gamePanel;
private SurfaceHolder surfaceHolder;
private boolean running;
public static Canvas canvas;
public MainThread(SurfaceHolder surfaceHolder, GamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount = 0;
// how many milliseconds it take to run through the loop
long targetTime = 1000 / FPS;
while (running) {
startTime = System.nanoTime();
canvas = null;
// try to lock the canvas for pixel editing
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// the main game loop
this.gamePanel.update();
this.gamePanel.draw(canvas);
}
} catch (Exception e) {
} finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
} catch (Exception e) {
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - timeMillis;
try {
System.out.println(waitTime);
this.sleep(waitTime);
} catch (Exception e) {
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if (frameCount == FPS) {
averageFPS = 1000 / ((totalTime / frameCount) / 1000000);
frameCount = 0;
totalTime = 0;
System.out.println(averageFPS);
}
}
}
The problem I have is that the averageFPS keeps dropping when adding stuff to my game. With only the background it works at 30 on my Nexus 5 but upon adding character and obstacles it drops to 22-21.
Is there anyway I can do to optimize this ?
Thank you
UPDATE: my GamePanel looks like this:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
import java.util.Random;
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback {
public static final int WIDTH = 1920;
public static final int HEIGHT = 1080;
public static float MOVESPEED = -12;
private Random rand = new Random();
private MainThread thread;
private Background bg;
private Treadmill tm;
private Player player;
private ArrayList<Box> boxes;
private int minDistance = 600;
private int score;
public GamePanel(Context context) {
super(context);
// add callback service to the holder to intercept events
getHolder().addCallback(this);
// make gamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
retry = false;
thread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// instantiate objects
thread = new MainThread(getHolder(), this);
bg = new Background(BitmapFactory.decodeResource(getResources(), R.drawable.background));
tm = new Treadmill(BitmapFactory.decodeResource(getResources(), R.drawable.ground));
player = new Player(BitmapFactory.decodeResource(getResources(), R.drawable.character));
boxes = new ArrayList<Box>();
//start game loop
thread.setRunning(true);
thread.start();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (player.jump == false) {
player.setVelocity(-14f);
player.setJump(true);
}
return true;
}
return super.onTouchEvent(event);
}
public void update() {
bg.update();
tm.update();
player.update();
if (boxes.size() == 0) {
boxes.add(new Box(BitmapFactory.decodeResource(getResources(), R.drawable.box),
WIDTH));
} else if (boxes.get(boxes.size() - 1).getX() < WIDTH) {
if (WIDTH - boxes.get(boxes.size() - 1).getX() < minDistance) {
boxes.add(new Box(BitmapFactory.decodeResource(getResources(), R.drawable.box),
rand.nextInt(400 - 50) + WIDTH + minDistance));
}
}
for (int i = 0; i < boxes.size(); i++) {
if (boxes.get(i).getX() <= player.getX() && boxes.get(i).getX() >= player.getX() - 12) {
score++;
MOVESPEED -= 0.2;
System.out.println(MOVESPEED);
}
if (collision(boxes.get(i), player)) {
boxes.remove(i);
break;
}
if (boxes.get(i).getX() < -100) {
boxes.remove(i);
break;
}
}
for (int i = 0; i < boxes.size(); i++) {
boxes.get(i).update();
}
}
#Override
public void draw(Canvas canvas) {
final float scaleFactorX = getWidth() / (WIDTH * 1.f);
final float scaleFactorY = getHeight() / (HEIGHT * 1.f);
if (canvas != null) {
final int saveState = canvas.save();
canvas.scale(scaleFactorX, scaleFactorY);
// draw the background
bg.draw(canvas);
tm.draw(canvas);
player.draw(canvas);
for (Box bx : boxes) {
bx.draw(canvas);
}
drawText(canvas);
canvas.restoreToCount(saveState);
}
}
public boolean collision(GameObject a, GameObject b) {
if (Rect.intersects(a.getRectangle(), b.getRectangle())) {
return true;
}
return false;
}
public void drawText(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(50);
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
canvas.drawText("Score: " + (score), 15, HEIGHT - 20, paint);
}
Thank you for all the help so far
The problem doesn't seem to be in the Main Thread Activity, you followed tutorial fine (Except for the line - System.out.println(waitTime); - but i don't think thats the problem cause.)
The problem is most likely in the GamePanel. ;)
Don't ever use Thread.sleep() in game loops. It is not guaranteed to sleep for the specified amount of time.
Anyway, I think that this is pretty much expected behaviour when using Canvas. If I were you I'd consider using OpenGL ES for anything like graphics rendering. Definitely, this makes it much more complex (even though Android has a lot of things already done for you). But the benefit is quite obvious - you get actual real-time graphics performance.
I was trying to draw some balloons on screeen using canvas in SurfaceView class, I was successfully able to draw Multiple balloons on canvas and animate them by swapping different images. The problem is When I try touching on Oneballoon I need to remove it from screen .Here I get this exception and I am stuck
Code:
MainActivity:
package com.pradhul.game.touchball;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GameView(this));
/*TODO Hide the bottom navigation bar */
}
}
GameView.java
package com.pradhul.game.touchball;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GameView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {
private static final int NUM_OF_BALLOONS = 5; //TODO for more than one balloons animations dont work(?)
/*use SurfaceView because we want complete control over the screen.
* unlike extending View class the oDraw() Method will not be called automatically
* from the method onSurfaceCreated() we have to call it Manually and pass a canvas object into it
* */
private final SurfaceHolder holder;
private GameLoopThread gameLoopThread;
private List<Balloon> balloons = new ArrayList<>();
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
holder = getHolder();
holder.addCallback(this);
createBalloons(NUM_OF_BALLOONS);
this.setOnTouchListener(this);
}
private void createBalloons(int count) {
for(int i=0 ; i< count ;i++){
balloons.add(createBalloon());
}
}
#Override
protected void onDraw(Canvas canvas) {
if(canvas != null) {
canvas.drawColor(Color.WHITE);
for(Balloon balloon : balloons){
try {
gameLoopThread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balloon.onDraw(canvas);
}
}
}
#SuppressLint("WrongCall")
#Override
public void surfaceCreated(SurfaceHolder holder) {
/*this is called when the view is created*/
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
/*pausing game Thread*/
gameLoopThread.setRunning(false);
while (true){
try {
gameLoopThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Balloon createBalloon(){
return new Balloon(this);
}
#Override
public synchronized boolean onTouch(View v, MotionEvent event) {
Log.d("OnTouch real -", "x: " + event.getX() + ", Y: " + event.getY());
/* for (int i = balloons.size()-1; i >= 0; i--) {
Balloon balloon = balloons.get(i);
Log.d("OnTouch collision -", !balloon.isCollision(event.getX(), event.getY())+"");
if (!balloon.isCollision(event.getX(), event.getY())) {
balloons.remove(0);
break;
}
}*/
Iterator<Balloon> balloonIterator = balloons.iterator();
while(balloonIterator.hasNext()){
Balloon balloon = balloonIterator.next();
balloons.remove(0);
}
return true;
}
}
GameLoopThread.java
package com.pradhul.game.touchball;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
public class GameLoopThread extends Thread {
private GameView view;
private boolean running = false;
public GameLoopThread(GameView view){
this.view = view;
}
public void setRunning(boolean run){
running = run;
}
#SuppressLint("WrongCall")
public void run(){
while (running){
Canvas canvas = null;
try{
canvas = view.getHolder().lockCanvas();
synchronized (view.getHolder()){
view.onDraw(canvas);
}
}finally{
if(canvas != null) {
view.getHolder().unlockCanvasAndPost(canvas);
}
}
}
}
}
Balloon.java
package com.pradhul.game.touchball;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;
import java.util.Random;
public class Balloon {
private static final int BALLOON_SPEED = 10;
private int y = 0;
private int x = 0;
private int speed = 1;
private GameView gameView;
private Bitmap balloon;
public Bitmap[] normalBalloons;
private int balloonIndex = 0;
private int normalImages[] = {R.drawable.normal_01,R.drawable.normal_02,R.drawable.normal_03,
R.drawable.normal_04,R.drawable.normal_05,R.drawable.normal_06,R.drawable.normal_07,
R.drawable.normal_08,
};
private int crackingImages[] = {R.drawable.crack_01,R.drawable.crack_02,R.drawable.crack_03,
R.drawable.crack_04, R.drawable.crack_05,R.drawable.crack_04,R.drawable.crack_03,
R.drawable.crack_02
};
private boolean reverseSwap = false;
public Balloon(GameView gameView){
this.gameView = gameView;
normalBalloons = new Bitmap[8];
setUpImages();
}
public void onDraw(Canvas canvas){
/*draws the balloon in canvas */
animateBalloon();
update(canvas.getWidth());
canvas.drawBitmap(balloon, x, y, null);
}
public boolean isCollision(float x2, float y2) {
return x2 > x && x2 < x + balloon.getWidth() && y2 > y && y2 < y + balloon.getHeight();
}
private int getRandomX(int maxVal) {
Random rand = new Random();
return rand.nextInt(maxVal);
}
private void animateBalloon() {
/*Animates the balloon by swapping resource image at each call*/
this.balloon = getBalloons();
Log.d("Balloon",balloonIndex % normalBalloons.length + "");
}
private void update(int canvasWidth) {
/*updates the y position for moving the balloon*/
if (y <= 0){
/*so that the balloon starts from bottom
* gameView will return a height only after the View is ready
* getting 0 in constructor of this class*/
y = gameView.getHeight();
/*x is assigned a random between the width od the canvas
* so that the balloons will appear random positions from below*/
x = getRandomX(canvasWidth - balloon.getWidth());
}
if (y > gameView.getHeight() - balloon.getHeight() - speed) {
speed = -BALLOON_SPEED;
}
y = y + speed;
Log.d("Balloon","Positions:"+x+","+y);
}
private Bitmap getBalloons() {
if(balloonIndex == normalBalloons.length-1) {
reverseSwap = true;
}
if(balloonIndex == 0){
reverseSwap = false;
}
balloonIndex = reverseSwap?balloonIndex-1:balloonIndex+1;
return normalBalloons[balloonIndex];
}
private void setUpImages() {
/*setting up resources array*/
for(int count =0; count < normalImages.length; count++){
Bitmap balloon = BitmapFactory.decodeResource(gameView.getResources(), normalImages[count]);
normalBalloons[count] = balloon;
}
}
}
I am confused that why it causes an error like this , Can anybody can take look at it and suggest me a solution, is this the right way to remove ?
please share any suggestion
Thanks
It's the wrong way to remove.
If you iterate over a Collection / List, and want to remove the current element, you must use Iterator.remove() method.
In your case, simply call balloonIterator.remove() instead of balloons.remove(0)
Even simpler, since you want to remove all elements from the list - you should simply call balloons.clear() and remove the loop completely.
This exception is due to concurrent modification of list balloons.
As soon as you touch surface view onDraw() gets invoked with onTouch(), in which you are working on list balloons.
I think you should add touch listener to balloon instead of GameView.
okay ,
I need to wrap all my code inside a synchronized holder inorder this to work
(don't know much about it)
#Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("OnTouch","x:"+event.getX()+"Y:"+event.getY());
synchronized (getHolder()){
for (int i=0 ;i<balloons.size();i++){
balloons.remove(0);
break;
}
}
return true;
}
I've started the development of a guitar application, and am stuck on a graphical aspect. My screen contains a background image, which you can move with 2 fingers, by sliding up or down. My background image, a bitmap, moves at low fps on an android device. I'm looking to see how to make it run smoother.
Code details:
I have 3 classes: one main activity, a second for graphics, and a third as a splash screen (I have't included the last one as it has nothing to do with my problems).
MainActivity.java
package com.example.androidapplication;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
public class MainActivity extends Activity implements OnTouchListener{
FretBoard ourView;
float y1, y2, dy;
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
ourView = new FretBoard(this);
ourView.setOnTouchListener(this);
setContentView(ourView);
}
//Method to be used in future
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
ourView.pause();
}
//Method to be used in future
#Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
ourView.resume();
}
#Override
public boolean onTouch(View v, MotionEvent event){
//For debugging
if(event!= null) ourView.fingerCount = event.getPointerCount();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
//Save first y position when finger touches screen
y1 = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if(event != null && event.getPointerCount() == 2){
//Save second y position when finger moves
y2 = event.getY();
//Total distance moved
dy = y2 - y1;
if((ourView.bgY + dy)<0) ourView.bgY += dy/2;
//For Debugging
System.out.println("Y1 : " + y1 + " Y2 : " + y2 + " DY : " + dy);
//Redraw the Screen
ourView.invalidate();
//Reset y1
y1 = y2;
}
break;
}
return true;
}
}
FretBoard.java
package com.example.androidapplication;
import java.io.InputStream;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class FretBoard extends SurfaceView implements Runnable{
//Background image variables
Bitmap background;
InputStream is = getResources().openRawResource(R.drawable.fret_board);
//Debugger text variables
Paint mPaint = new Paint();
String string = "Fingers: ";
int fingerCount = 0;
//Position Variables
int width, height;
float bgY = 0, bgX = 0;
//Holder variable
SurfaceHolder ourHolder;
//To be used in future
Thread ourThread = null;
Boolean isRunning = false;
public FretBoard(Context context) {
super(context);
// TODO Auto-generated constructor stub
ourHolder = getHolder();
ourThread = new Thread(this);
ourThread.start();
}
//To be used in the future
public void pause(){
isRunning = false;
while(true){
try {
ourThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
ourThread = null;
}
//To be used in the future
public void resume(){
isRunning = true;
ourThread = new Thread(this);
ourThread.start();
}
//Creates a InputStream to set width, then converts to bitmap to be
//drawn as background on the canvas.
public Bitmap bmScale(InputStream is){
BitmapFactory.Options o = new BitmapFactory.Options();
o.inSampleSize = 2;
Bitmap bit = BitmapFactory.decodeStream(is, null, o);
background = Bitmap.createScaledBitmap(bit, width, 2300, true);
bit.recycle();
return background;
}
//This is the draw method where I need help
#Override
public void run() {
// TODO Auto-generated method stub
while(isRunning){
if(!ourHolder.getSurface().isValid()) continue;
//Lock canvas to draw
Canvas canvas = ourHolder.lockCanvas();
//Do Things Here:
width = getWidth();
height = getHeight();
//Collision detection for the background
if(bgY > 0 ) bgY = 0;
//Draws the background
bmScale(is);
canvas.drawBitmap(background, bgX, bgY, null);
//For debugging, displays the number of fingers on the screen
canvas.drawText(string + fingerCount, 50, 50, mPaint);
//Unlock canvas to show
ourHolder.unlockCanvasAndPost(canvas);
}
}
}
It seems you are loading/reading and scaling the bitmap on each render pass. This is enormously inefficient.
(see call to bmScale(is); in FretBoard.java).
Instead, load (and scale your) bitmap only once (when the view gets laid out) and then draw the already loaded and scaled bitmap.
Also, you seem to use a SurfaceView for your FretBoard. Why? In your code sample, you are not using OpenGL or Video/Camera textures at all.
Scaling in your loop (as mentioned by the other answer here) is definitely your major problem. Once you fix that, things should be much smoother. You will probably find some minor stutters still though because you are creating other objects in your loop, namely strings. Given that you will never have more than 5 fingers on the fretboard at once (OK, maybe theoretically 10 if you're tapping) there are only 5 or 10 possible values for string (which by the way is a confusing name, especially in this context). It'd be better to create these up front as well outside of the loop and just pick the correct reference.
I am creating a very simple game, because I am new to developing on the android platform, but not programming, in general. Anyway, when I run the app via the emulator, or sometimes through an actual device, all I see are the two paddles being drawn, including ball, on the screen, and when I touch the screen, or simulate touching with a mouse, the event onTouch, doesn't get fired. I do not know why, can anyone point me to the right direction, I am new to developing on android devices.
Code:
package main.game;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.*;
public class DrawingArea extends SurfaceView
{
private Ball ball = null;
private Paddle player = null;
private Paddle computer = null;
private Vector2 playerPos = Vector2.Zero;
private boolean startGame = false;
private boolean initiated = false;
private Paint paint;
public DrawingArea(Context context, Paint paint) {
super(context);
setOnTouchListener(new Touch());
setWillNotDraw(false);
this.paint = paint;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLUE);
if (initiated == false)
{
ball = new Ball(getWidth() / 2.0f, getHeight() / 2.0f, 32, paint);
player = new Paddle(playerPos.getX(), playerPos.getY(), 32, 128, paint);
Paint p = new Paint();
p.set(paint);
p.setColor(Color.GREEN);
computer = new Paddle(getWidth() - 32, 0, 32, 128, p);
GameLoop gloop = new GameLoop(this);
gloop.start();
UpdateLoop uloop = new UpdateLoop(this);
uloop.start();
initiated = true;
}
ball.draw(canvas);
player.draw(canvas);
computer.draw(canvas);
}
class GameLoop extends Thread
{
private DrawingArea area;
private boolean running = true;
public GameLoop(DrawingArea area)
{
this.area = area;
}
#Override
public void run() {
int frame = 0;
while(running)
{
this.area.invalidate();
if(frame == Integer.MAX_VALUE) { frame = 0;}
frame++;
}
}
public void closeThread()
{
running = false;
}
}
class Touch implements View.OnTouchListener
{
#Override
public boolean onTouch(View v, MotionEvent event) {
startGame = true;
playerPos = new Vector2(0, event.getY());
if (playerPos.getY() < (player.getY() + 256))
{
player.translateY(-0.5f);
}
else if (playerPos.getY() > (player.getY() + 256))
{
player.translateY(0.5f);
}
return true;
}
}
class UpdateLoop extends Thread
{
private DrawingArea area;
private boolean running = true;
private boolean forward = true;
public UpdateLoop(DrawingArea area)
{
this.area = area;
}
#Override
public void run() {
int frame = 0;
while(running)
{
if (startGame == true)
ball.translate(0.5f, 0.5f, 45.0f, forward);
if (ball.getX() < 0 || ball.getY() < 0 || ball.getX() > area.getWidth()|| ball.getY() > area.getHeight()|| ball.Intersects(computer) || ball.Intersects(player))
{
if(forward == true)
forward = false;
else
forward = true;
}
if(frame == Integer.MAX_VALUE) { frame = 0;}
frame++;
}
}
}
}
Update:
DrawingArea area = new DrawingArea(this, paint);
setContentView(R.layout.main);
LinearLayout main = (LinearLayout)findViewById(R.id.main);
main.addView(area, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
main.setOnTouchListener(this);
Instead of registering your listener to surfaceview
setOnTouchListener(new Touch());
Try attaching the listener to the layout for the view something like this
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.yourlayout);
//the whole screen becomes sensitive to touch
mLinearLayoutMain = (LinearLayout) findViewById(R.id.layout_main);
mLinearLayoutMain.setOnTouchListener(this);
}
Hi, I have recently started trying to make android apps
and have currently made a few simple ones, but, I am still really confused with the touch methods.
What I'm trying to do is: When your finger is held down on the screen, x increments, but it stops when you release your finger. The problem is that I can't find any method or any way of doing this.
Here is my code:
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class zombView extends SurfaceView{
private Bitmap bmp, grass, joystick;
private SurfaceHolder holder;
Timer t = new Timer();
float x = 0, y = 0;
boolean forward;
public zombView(Context context) {
super(context);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(final SurfaceHolder holder) {
t.scheduleAtFixedRate(new TimerTask(){
public void run(){
Canvas c = holder.lockCanvas(null);
onDraw(c);
holder.unlockCanvasAndPost(c);
if(forward == true){
x++;
}
}
},200,200);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
grass = BitmapFactory.decodeResource(getResources(), R.drawable.grassland);
joystick = BitmapFactory.decodeResource(getResources(), R.drawable.joystic);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
canvas.drawBitmap(grass, getWidth() - getWidth(), getHeight() - getHeight(), null);
canvas.drawBitmap(joystick, getWidth() - getWidth(),joystick.getHeight(), null);
canvas.drawBitmap(bmp, x, y, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getX() > 10 && event.getX() < 1000){
if(event.getY() > 10 && event.getY() < 1000){
x++;
}
}
return super.onTouchEvent(event);
}
}
The MotionEvent class has a getAction method, which specifies what kind of event did your onTouchEvent method has just received. There are three actions that you will use more often:
ACTION_DOWN, which tells you that the user has just put his finger on the screen;
ACTION_MOVE, which tells that the user is still holding his finger on the screen;
ACTION_UP, which tells that the user has taken his finger off the screen.
Depending on what kind of event has just been acquired by your listener you can change the behavior of your code. For more information check the documentation. Hope this helps.