Custom view not being completely drawn - android

I am trying to make a cannon game that is printed in an Android Developer's book and having trouble with the drawing of the actual game. I can hear my sounds, the alert dialog at the end of the game is being shown after my timer runs out even though I cant see the timer, and the white background is shown, just no game elements
Here is the main activity:
//CannonGame.java
//Main Activity for the Cannon game app.
package com.deitel.cannongame;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.media.AudioManager;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.SimpleOnGestureListener;
public class CannonGame extends Activity {
private GestureDetector gestureDetector; //listens for double taps
private CannonView cannonView; //custom view to display the game
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); //call super's onCreate method
setContentView(R.layout.main); //inflate the layout
//get the cannonview
cannonView = (CannonView) findViewById(R.id.cannonView);
//initialize the GuestureDetector
gestureDetector = new GestureDetector(this, gestureListener);
//allow for volume keys to set game volume
setVolumeControlStream(AudioManager.STREAM_MUSIC);
} //end method onCreate
//when the app is pushed to the background, pause it
#Override
public void onPause(){
super.onPause(); //call the super method
cannonView.stopGame(); //terminates the game
} //end method onPause
//release resources
#Override
protected void onDestroy(){
super.onDestroy();
cannonView.releaseResources();
} //end method onDestroy
//called when the user touches the screen in this Activity
#Override
public boolean onTouchEvent(MotionEvent event){
//get int representing the type of action which caused this event
int action = event.getAction();
//the user touched the screen or dragged aong the screen
if (action == (MotionEvent.ACTION_DOWN) || action == MotionEvent.ACTION_MOVE)
cannonView.alignCannon(event); //align the cannon
//call the GuestureDetector's onTouchEvent method
return gestureDetector.onTouchEvent(event);
} //end onTouchEvent
//listens for touch events sent to the GuestureDetector
SimpleOnGestureListener gestureListener = new SimpleOnGestureListener(){
//called when the use doubletaps the screen
#Override
public boolean onDoubleTap(MotionEvent e){
cannonView.fireCannonball(e); //fire the cannonball
return true; //the event was handled
} //end method onDoubleTap
}; //end gestureListener
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.cannon_game, menu);
return true;
}
}
Here is the custom view the main activity calls and all it's properties:
//CannonView.java
//Displays the Cannon Game
package com.deitel.cannongame;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Paint;
import android.media.SoundPool;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class CannonView extends SurfaceView implements SurfaceHolder.Callback{
private CannonThread cannonThread; //controls the game loop
private Activity activity; //to display Game Over dialog in GUI thread
private boolean dialogIsDisplayed = false;
//constants for game play
public static final int TARGET_PIECES = 7; //sections in the target
public static final int MISS_PENALTY = 2; //seconds deducted on a miss
public static final int HIT_REWARD = 3; //seconds added on a hit
//variables for the game loop and tracking statistics
private boolean gameOver; //is the game over?
private double timeLeft; //the amount of time left in seconds
private int shotsFired; //the number of shots the user has fired
private double totalTimeElapsed; //the number of seconds elapsed
//variables for the blocker and target
private Line blocker; //start and end points of the blocker
private int blockerDistance; //blocker distance from the left
private int blockerBeginning; //blocker distance from the top
private int blockerEnd; //blocker bottom edge distance from the top
private int initialBlockerVelocity; //initial blocker speed multiplier
private float blockerVelocity; //blocker speed multiplier during game
private Line target; //start and end points of the target
private int targetDistance; //target distance from left
private int targetBeginning; //target distance from the top
private double pieceLength; //length of target piece
private int targetEnd; //target bottom distance from the top
private int initialTargetVelocity; //initial target speed multiplier
private float targetVelocity; //target speed multiplier during game
private int lineWidth; //width of the target and blocker
private boolean[] hitStates; //is each target piece hit?
private int targetPiecesHit; //number of target pieces hit (out of 7)
//variables for the cannon and cannonball
private Point cannonball; //cannonball image's upper-left corner
private int cannonballVelocityX; //cannonball's x velocity
private int cannonballVelocityY; //cannonball's y velocity
private boolean cannonballOnScreen; //is the cannonball on the screen
private int cannonballRadius; //cannonball radius
private int cannonballSpeed; //cannonball speed
private int cannonBaseRadius; //cannon base radius
private int cannonLength; //cannon barrel length
private Point barrelEnd; //the endpoint of the cannon's barrel
private int screenWidth; //width of the screen
private int screenHeight; //height ofthe screen
//constants and variables for managing sounds
private static final int TARGET_SOUND_ID = 0;
private static final int CANNON_SOUND_ID = 1;
private static final int BLOCKER_SOUND_ID = 2;
private SoundPool soundPool; //plays sound effects
private Map<Integer, Integer> soundMap; //maps IDs to SoundPool
//Paint vairables used when drawing each item on the screen
private Paint textPaint; //paint used to draw text
private Paint cannonballPaint; //paint used to draw the cannonball
private Paint cannonPaint; //paint used to draw the cannon
private Paint blockerPaint; //paint used to draw the blocker
private Paint targetPaint; //paint used to draw the target
private Paint backgroundPaint; //paint used to clear the drawing area
//public constructor
public CannonView(Context context, AttributeSet attrs){
super(context, attrs); //call super's constructor
activity = (Activity) context;
//register SurfaceHolder.Calback listener
getHolder().addCallback(this);
//initiate Lines and points representing game items
blocker = new Line(); //create the blocker as a Line
target = new Line(); //create the target as a Line
cannonball = new Point(); //create the cannonball as a point
//initialize hitStates as a boolean array
hitStates = new boolean[TARGET_PIECES];
//initialize SoundPool to play the app's three sound effects
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
//create Map of sounds and pre-load sounds
soundMap = new HashMap<Integer, Integer>(); //create new HashMap
soundMap.put(TARGET_SOUND_ID, soundPool.load(context, R.raw.target_hit, 1));
soundMap.put(CANNON_SOUND_ID, soundPool.load(context, R.raw.cannon_fire, 1));
soundMap.put(BLOCKER_SOUND_ID, soundPool.load(context, R.raw.blocker_hit, 1));
//construct Paints for drawing text, cannonball, cannon, blocker, and target; these are configured
//in method onSizeChanged
textPaint = new Paint(); //paint for drawing text
cannonballPaint = new Paint(); //paint for drawing the cannonball
cannonPaint = new Paint(); //paint for drawing the cannon
blockerPaint = new Paint(); //Paint for drawing the blocker
targetPaint = new Paint(); //Paint for drawing the target
backgroundPaint = new Paint(); //paint for drawing the background
} //end CannonView constructor
//called when the size of this view changes --including when this view is first added to the view hierarchy
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh){
super.onSizeChanged(w, h, oldw, oldh);
screenWidth = w; //store the width
screenHeight = h; //store the height
cannonBaseRadius = h / 18; //cannon base radius 1/18 screen height
cannonLength = w / 8; //cannon length 1/8 screen width
cannonballRadius = w / 36; //cannonball radious 1/36 screen width
cannonballSpeed = w * 3 / 2; //cannonball speed multiplier
lineWidth = w / 24; //target and blocker 1/24 screen width
//configure instance variables related to the blocker
blockerDistance = w * 5 / 8; //blocker 5/8 screen width from left
blockerBeginning = h / 8; //distance from top 1/8 screen height
blockerEnd = h * 3 / 8; //distance from top 3/8 screen height
initialBlockerVelocity = h / 2; //initial blocker speed multiplier
blocker.start = new Point(blockerDistance, blockerBeginning);
blocker.end = new Point(blockerDistance, blockerEnd);
//configure instance variables related to the target
targetDistance = w * 7 / 8; //target 7/8 screen width from left
targetBeginning = h / 8; //distance from top 1/8 screen height
targetEnd = h * 7 / 8; //distance from top 7/8 screen height
pieceLength = (targetEnd - targetBeginning) / TARGET_PIECES;
initialTargetVelocity = -h / 4; //initial target speed multiplier
target.start = new Point(targetDistance, targetBeginning);
target.end = new Point(targetDistance, targetEnd);
//endpoint of the cannon's barrel initially points horizontally
barrelEnd = new Point(cannonLength, h / 2);
//configure Paint objects for drawing game elements
textPaint.setTextSize(w / 20); //text size 1/20 of screen width
textPaint.setAntiAlias(true); //smoothes the text
cannonPaint.setStrokeWidth(lineWidth * 1.5f); //set line thickness
blockerPaint.setStrokeWidth(lineWidth); //set line thickness
targetPaint.setStrokeWidth(lineWidth); //set line thickness
backgroundPaint.setColor(Color.WHITE); //set background color
newGame(); //set up and start a new game
} //end method onSizeChange
public void newGame(){
//set every element of hitStates to false--restores target pieces
for (int i = 0; i < TARGET_PIECES; ++i)
hitStates[i] = false;
targetPiecesHit = 0; //no target pieces have been hit
blockerVelocity = initialBlockerVelocity; //set initial blocker velocity
targetVelocity = initialTargetVelocity; //set initial target velocity
timeLeft = 10; //start the countdown at 10 seconds
cannonballOnScreen = false; //the cannonball is not on the screen
shotsFired = 0; //set the initial number of shots fired
totalTimeElapsed = 0.0; //set the time elapsed to zero
blocker.start.set(blockerDistance, blockerEnd);
blocker.end.set(blockerDistance, blockerEnd);
target.start.set(targetDistance, targetBeginning);
target.end.set(targetDistance, targetEnd);
if (gameOver){
gameOver = false; //the game is not over
cannonThread = new CannonThread(getHolder());
cannonThread.start();
} //end if
} //end method newGame
//called repleatedly by the CannonThread to update the game elements
private void updatePositions(double elapsedtimeMS){
double interval = elapsedtimeMS / 1000.0; //convert to seconds
if(cannonballOnScreen){ //if there is currently a shot fired
//update cannonball position
cannonball.x += interval * cannonballVelocityX;
cannonball.y += interval * cannonballVelocityY;
//check for collision with blocker
if(cannonball.x + cannonballRadius > blockerDistance && cannonball.x - cannonballRadius
< blockerDistance && cannonball.y + cannonballRadius > blocker.start.y && cannonball.y - cannonballRadius < blocker.end.y){
cannonballVelocityX *= -1; //reverse cannonball's direction
timeLeft -= MISS_PENALTY; //penalize the user
//play blocker sound
soundPool.play(soundMap.get(BLOCKER_SOUND_ID), 1, 1, 1, 0, 1f);
} //end if
//check for collisions with left and right walls
else if(cannonball.x + cannonballRadius > screenWidth || cannonball.x - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.y + cannonballRadius > screenHeight || cannonball.y - cannonballRadius < 0)
cannonballOnScreen = false; //remove cannonball from screen
else if(cannonball.x + cannonballRadius > targetDistance && cannonball.x - cannonballRadius <
targetDistance && cannonball.y + cannonballRadius > target.start.y && cannonball.y - cannonballRadius < target.end.y){
//determine target section number (0 is the top)
int section = (int) ((cannonball.y - target.start.y) / pieceLength);
//check if the piece hasn't been hit yet
if((section >= 0 && section < TARGET_PIECES) && !hitStates[section]){
hitStates[section] = true; //section was hit
cannonballOnScreen = false; //remove cannonball
timeLeft += HIT_REWARD; //add reward to remaining time
//play target hit sound
soundPool.play(soundMap.get(TARGET_SOUND_ID), 1, 1, 1, 0, 1F);
//if all pieces have been hit
if (++targetPiecesHit == TARGET_PIECES){
cannonThread.setRunning(false); //show winning dialog
gameOver = true; //the game is over
} //end if
} //end if
} //end else if
} //end if
//update the blockers position
double blockerUpdate = interval * blockerVelocity;
blocker.start.y += blockerUpdate;
blocker.end.y += blockerUpdate;
//update the target's position
double targetUpdate = interval * targetVelocity;
target.start.y += targetUpdate;
target.end.y += targetUpdate;
//if the blocker hit the top or bottom, reverse direction
if(blocker.start.y < 0 || blocker.end.y > screenHeight)
blockerVelocity *= -1;
//if the target hit the top or bottom, reverse direction
if(target.start.y < 0 || target.end.y > screenHeight)
targetVelocity *= -1;
timeLeft -= interval; //subtract from time left
//if the trimer reached zero
if(timeLeft <= 0){
timeLeft = 0.0;
gameOver = true; //the game is over
cannonThread.setRunning(false);
showGameOverDialog(R.string.lose); //show the losing dialog
} //end if
} //end method updatePositions
//fires a cannonball
public void fireCannonball(MotionEvent event){
if(cannonballOnScreen) //if a cannonball is already on the screen
return; //do nothing
double angle = alignCannon(event); //get the cannon barrel's angle
//move the cannonball to be inside the cannon
cannonball.x = cannonballRadius; //align x-coordinate with cannon
cannonball.y = screenHeight / 2; //centers ball vertically
//get the x component of the total velocity
cannonballVelocityX = (int) (cannonballSpeed * Math.sin(angle));
//get the y component of the total velocity
cannonballVelocityY = (int)(-cannonballSpeed * Math.cos(angle));
cannonballOnScreen = true; //the cannonball is on the screen
++shotsFired; //increment shots fired
//play cannon fired sound
soundPool.play(soundMap.get(CANNON_SOUND_ID), 1, 1, 1, 0, 1f);
} //end method fireCannonball
//aligns the cannon in response to a user touch
public double alignCannon(MotionEvent event){
//get the location of the touch in this view
Point touchPoint = new Point((int) event.getX(), (int) event.getY());
//compute the touch's distance from the center of the screen on the Y axis
double centerMinusY = (screenHeight / 2 - touchPoint.y);
double angle = 0; //initialize angle to 0
//calculate the angle the barrel makes with the horizontal
if(centerMinusY != 0) //prevent division by 0
angle = Math.atan((double) touchPoint.x / centerMinusY);
//if the touch is on the lower half of the screen
if(touchPoint.y > screenHeight / 2)
angle += Math.PI; //adjust the angle
//calculate the endpoint of the cannon barrel
barrelEnd.x = (int) (cannonLength * Math.sin(angle));
barrelEnd.y = (int) (-cannonLength * Math.cos(angle) + screenHeight / 2);
return angle; //return the computed angle
} //end method alignCannon
//draws the game to the given Canvas
public void drawGameElements(Canvas canvas){
//clear the background
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), backgroundPaint);
//display the time remaining
canvas.drawText(getResources().getString(R.string.time_remaining_format, timeLeft), 30, 50, textPaint);
//if a cannonball is currently on the screen, draw it
if(cannonballOnScreen)
canvas.drawCircle(cannonball.x, cannonball.y, cannonballRadius, cannonballPaint);
//draw the cannon barrel
canvas.drawLine(0, screenHeight / 2, barrelEnd.x, barrelEnd.y, cannonPaint);
//draw the cannon base
canvas.drawCircle(0, (int) screenHeight / 2, (int) cannonBaseRadius, cannonPaint);
//draw the blocker
canvas.drawLine(blocker.start.x, blocker.start.y, blocker.end.x, blocker.end.y, blockerPaint);
Point currentPoint = new Point(); //start of current target section
//initialize curPoint to the starting point of the target
currentPoint.x = target.start.x;
currentPoint.y = target.start.y;
//draw the target
for (int i = 1; i <= TARGET_PIECES; ++i){
//if this target piece is not hit, draw it
if(!hitStates[i - 1]){
//alternate coloring the pieces yellow and blue
if(i % 2 == 0)
targetPaint.setColor(Color.YELLOW);
else
targetPaint.setColor(Color.BLUE);
canvas.drawLine(currentPoint.x, currentPoint.y, target.end.x,
(int) (currentPoint.y + pieceLength), targetPaint);
} //end if
//move curPoint to the start of the next piece
currentPoint.y += pieceLength;
} //end for
} //end method drawGameElements
//display an AlertDialog when the game ends
private void showGameOverDialog(int messageId){
//create a dialog dislaying the given String
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext());
dialogBuilder.setTitle(getResources().getString(messageId));
dialogBuilder.setCancelable(false);
//display number of shots fired an total time elapsed
dialogBuilder.setMessage(getResources().getString(R.string.results_format, shotsFired, totalTimeElapsed));
dialogBuilder.setPositiveButton(R.string.reset_game,
new DialogInterface.OnClickListener() {
//called when "Reset Game" Button is pressed
#Override
public void onClick(DialogInterface dialog, int which) {
dialogIsDisplayed = false;
newGame(); //set up and start a new game
} //end method onClick
} //end anonymous inner class
); //end call to setPositiveButton
activity.runOnUiThread(
new Runnable() {
public void run(){
dialogIsDisplayed = true;
dialogBuilder.show(); //display the dialog
} //end run
}//end runnable
); //end call to runOnUiThread
} //end method showGameOverDialog
//pauses the game
public void stopGame(){
if(cannonThread != null)
cannonThread.setRunning(false);
} //end method stopGame
//releases reources; called by CannonGame's onDestroy method
public void releaseResources(){
soundPool.release(); //release all resource used by the SoundPool
soundPool = null;
} //end method releaseResources
//called when surface changes size
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){
} //end method surfaceChanged
//called when surface is first created
#Override
public void surfaceCreated(SurfaceHolder holder){
cannonThread = new CannonThread(holder);
cannonThread.setRunning(true);
cannonThread.start(); //start the game loop thread
} //end method surfaceCreated
//called when surface is destroyed
#Override
public void surfaceDestroyed(SurfaceHolder holder){
//ensure that thread terminates properly
boolean retry = true;
cannonThread.setRunning(false);
while(retry){
try{
cannonThread.join();
retry = false;
} //end try
catch(InterruptedException e){
} //end catch
} //end while
} //end method surfaceDestroyed
//Thread subclass to control the game loop
private class CannonThread extends Thread{
private SurfaceHolder surfaceHolder; //for manipulating canvas
private boolean threadIsRunning = true; //running by default
//initialize the surface holder
public CannonThread(SurfaceHolder holder){
surfaceHolder = holder;
setName("CannonThread");
} //end constructor
//changes the running state
public void setRunning(boolean running){
threadIsRunning = running;
} //end method setRunning
//controls the game loop
#Override
public void run(){
Canvas canvas = null; //used for drawing
long previousFrameTime = System.currentTimeMillis();
while(threadIsRunning){
try{
canvas = surfaceHolder.lockCanvas(null);
//lock the surafceHolder for drawing
synchronized(surfaceHolder){
long currentTime = System.currentTimeMillis();
double elapsedTimeMS = currentTime - previousFrameTime;
totalTimeElapsed += elapsedTimeMS / 1000.0;
updatePositions(elapsedTimeMS); //update game stats
drawGameElements(canvas); //draw
previousFrameTime = currentTime; //updateprevious time
} //end synchronized block
} //end try
finally{
if(canvas != null)
surfaceHolder.unlockCanvasAndPost(canvas);
} //end finally
} //end while
} //end method run
} //end nested class CannonThread
} //end class CannonView
Finally, here is the xml constraint that my custom view uses:
<?xml version="1.0" encoding="utf-8"?>
<com.deitel.cannongame.CannonView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/cannonView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"/>
I should mention that I tried the book's example files off their website, and I get the exact same problem.

Change the xml
android:background="#android:color/transparent"

It is likely that you are being burned by overzealous scaling by your fancy android. I would recommend that you use this:
in your manifest. Just put it after your Application information, like this:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourstuff.app"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10"
android:targetSdkVersion="15" />
<application android:label="#string/app_name" android:icon="#drawable/r">
<activity android:name=".LandingActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".yourOtherActivity"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen"
/>
</application>
<supports-screens android:anyDensity="true" />
</manifest>

Related

I have a NullPointerException using the SurfaceView

I have this code. It isn't complex at all, I'm learning and I was practising and messing around with the surface view. I only want 2 rectangles to be there and an image going down. When we touch in the second rectangle, the image starts going up. We touch the one in the left and the image restarts going down. When it arrives the line 89, it stops and gives the null pointer exception. I guess the error happens when I create the canvas.
public class LearningThreads extends Activity {
ActivitySurface activitySurface;
boolean crossGoesUp = false;//Sets if the cross goes up or down
int leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1;
int leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2;
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
activitySurface = new ActivitySurface(this);
activitySurface.setOnTouchListener(new canvasClicked());
setContentView(activitySurface);//Sets the content to be the class we've created
}
protected void onPause() {//When the app is paused, it calls the method which pauses the thread that is constantly running
super.onPause();
activitySurface.pause();
}
protected void onResume() {//When the app starts or restarts, it calls the method which starts the thread
super.onResume();
activitySurface.resume();
}
public class canvasClicked implements OnTouchListener {
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {//Only if the user starts touching something because I'm not interested in when he releases
if (e.getX() <= leftRectangle1 && e.getX() >= rightRectangle1 && e.getY() <= topRectangle1 && e.getY() >= bottomRectangle1) {//Tests if the user touched one of the rectangles
crossGoesUp = false;
}
if (e.getX() <= leftRectangle2 && e.getX() >= rightRectangle2 && e.getY() <= topRectangle2 && e.getY() >= bottomRectangle2) {//Tests if the user touched the other rectangle
crossGoesUp = true;
}
}
return false;//It doesn't repeat
}
}
public class ActivitySurface extends SurfaceView {
Thread mainThread;
boolean isRunning = false;//Sets when the app is running or not
SurfaceHolder holder;//Gives us useful methods to use in the canvas
int crossY = 0;//Sets the y coordinate of the cross
public ActivitySurface(Context context) {
super(context);
holder = getHolder();
}
public void resume() {
isRunning = true;
mainThread = new Thread(new mainThread());
mainThread.start();
}
public void pause() {
isRunning = false;
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class mainThread implements Runnable {//Takes care of the thread
public void run() {
while(isRunning) {
if (holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it is
continue;
Canvas canvas = holder.lockCanvas();//Creating the canvas: it has a mistake, everytime I use the canvas it gives a NullPointerException
canvas.drawRGB(50, 50, 50);//Setting the color of the canvas
leftRectangle1 = canvas.getWidth()/4 - 40;//Setting the variables so they can be used outside this Thread
topRectangle1 = canvas.getHeight()/2 - 25;
rightRectangle1 = canvas.getWidth()/4 + 40;
bottomRectangle1 = canvas.getHeight()/2 + 25;
leftRectangle2 = canvas.getWidth()/4 + (canvas.getWidth()/4) * 2 - 40;
topRectangle2 = canvas.getHeight()/2 - 25;
rightRectangle2 = canvas.getWidth()/4 + (canvas.getWidth()/4) * 2 + 40;
bottomRectangle2 = canvas.getHeight()/2 + 25;
Paint paint = new Paint();//Setting the paint which will define the colors of the rectangles
paint.setARGB(0, 100, 100, 100);
Rect rectangle1 = new Rect();//Setting the position of the rectangle 1
rectangle1.set(leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1);
Rect rectangle2 = new Rect();//Setting the position of the rectangle 2
rectangle2.set(leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2);
canvas.drawRect(rectangle1, paint);//Drawing the rectangles
canvas.drawRect(rectangle2, paint);
Bitmap cross = BitmapFactory.decodeResource(getResources(), R.drawable.animation);//Creating the image which is going to go up and down
canvas.drawBitmap(cross, canvas.getWidth()/2 - cross.getWidth()/2, crossY, paint);
if (crossGoesUp) {//If the crossGoesUp is true, that means the user last touch was in the rectangle 2, so the image goes up
if (crossY < -cross.getHeight())//Tests if the image isn't out of bounds
crossY = canvas.getHeight() + cross.getHeight();
crossY -= 5;
} else {
if (crossY > canvas.getHeight() + cross.getHeight())//Same as above
crossY = -cross.getHeight();
crossY += 5;
}
holder.unlockCanvasAndPost(canvas);
}
}
}
}
}
This is my logcat:
05-02 07:13:41.897: E/AndroidRuntime(1634): FATAL EXCEPTION: Thread-103
05-02 07:13:41.897: E/AndroidRuntime(1634): Process: garden.apps.my_apps, PID: 1634
05-02 07:13:41.897: E/AndroidRuntime(1634): java.lang.NullPointerException
05-02 07:13:41.897: E/AndroidRuntime(1634): at com.apps.my_apps.LearningThreads$ActivitySurface$mainThread.run(LearningThreads.java:90)
05-02 07:13:41.897: E/AndroidRuntime(1634): at java.lang.Thread.run(Thread.java:841)
you should use SurfaceHolder.Callback, your paint is invisible
paint.setARGB(alpha,Red,Green,Blue) - alpha 0..255 0-invisible 255-visible
public class LearningThreads extends Activity {
ActivitySurface activitySurface;
boolean crossGoesUp = false;//Sets if the cross goes up or down
int leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1;
int leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2;
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
activitySurface = new ActivitySurface(this);
activitySurface.setOnTouchListener(new canvasClicked());
setContentView(activitySurface);//Sets the content to be the class we've created
}
protected void onPause() {//When the app is paused, it calls the method which pauses the thread that is constantly running
super.onPause();
}
protected void onResume() {//When the app starts or restarts, it calls the method which starts the thread
super.onResume();
}
public class canvasClicked implements View.OnTouchListener {
public boolean onTouch(View v, MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {//Only if the user starts touching something because I'm not interested in when he releases
if (e.getX() <= leftRectangle1 && e.getX() >= rightRectangle1 && e.getY() <= topRectangle1 && e.getY() >= bottomRectangle1) {//Tests if the user touched one of the rectangles
crossGoesUp = false;
}
if (e.getX() <= leftRectangle2 && e.getX() >= rightRectangle2 && e.getY() <= topRectangle2 && e.getY() >= bottomRectangle2) {//Tests if the user touched the other rectangle
crossGoesUp = true;
}
}
return false;//It doesn't repeat
}
}
public class ActivitySurface extends SurfaceView implements SurfaceHolder.Callback {
Thread mainThread;
boolean isRunning = false;//Sets when the app is running or not
SurfaceHolder holder;//Gives us useful methods to use in the canvas
int crossY = 0;//Sets the y coordinate of the cross
public ActivitySurface(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
}
public void resume() {
isRunning = true;
mainThread = new Thread(new mainThread());
mainThread.start();
}
public void pause() {
isRunning = false;
try {
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class mainThread implements Runnable {//Takes care of the thread
public void run() {
while (isRunning) {
if (!holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it is
continue;
Canvas canvas = holder.lockCanvas();//Creating the canvas: it has a mistake, everytime I use the canvas it gives a NullPointerException
canvas.drawRGB(50, 50, 50);//Setting the color of the canvas
leftRectangle1 = canvas.getWidth() / 4 - 40;//Setting the variables so they can be used outside this Thread
topRectangle1 = canvas.getHeight() / 2 - 25;
rightRectangle1 = canvas.getWidth() / 4 + 40;
bottomRectangle1 = canvas.getHeight() / 2 + 25;
leftRectangle2 = canvas.getWidth() / 4 + (canvas.getWidth() / 4) * 2 - 40;
topRectangle2 = canvas.getHeight() / 2 - 25;
rightRectangle2 = canvas.getWidth() / 4 + (canvas.getWidth() / 4) * 2 + 40;
bottomRectangle2 = canvas.getHeight() / 2 + 25;
Paint paint = new Paint();//Setting the paint which will define the colors of the rectangles
paint.setARGB(255, 100, 100, 100);
Rect rectangle1 = new Rect();//Setting the position of the rectangle 1
rectangle1.set(leftRectangle1, topRectangle1, rightRectangle1, bottomRectangle1);
Rect rectangle2 = new Rect();//Setting the position of the rectangle 2
rectangle2.set(leftRectangle2, topRectangle2, rightRectangle2, bottomRectangle2);
canvas.drawRect(rectangle1, paint);//Drawing the rectangles
canvas.drawRect(rectangle2, paint);
Bitmap cross = BitmapFactory.decodeResource(getResources(), R.drawable.animation);//Creating the image which is going to go up and down
canvas.drawBitmap(cross, canvas.getWidth() / 2 - cross.getWidth() / 2, crossY, paint);
if (crossGoesUp) {//If the crossGoesUp is true, that means the user last touch was in the rectangle 2, so the image goes up
if (crossY < -cross.getHeight())//Tests if the image isn't out of bounds
crossY = canvas.getHeight() + cross.getHeight();
crossY -= 5;
} else {
if (crossY > canvas.getHeight() + cross.getHeight())//Same as above
crossY = -cross.getHeight();
crossY += 5;
}
holder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
resume();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
pause();
}
}
someActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
//Your code to run in GUI thread here
}//public void run() {
});
I hope this will help you.
I managed to figger it out by myself, the problem was in this block of code:
if (holder.getSurface().isValid())//Tests if the surface is valid, if it is not it won't do anything until it
continue;
I forgot to put the exclamation mark, so the app only did what was below when the surface was not valid and it gave me a NullPointerException.
Thank you anyway.

Box2D body lags/jumps when a camera is centered on it

I'm trying to set the camera position to the position of a body, but when I do this, the body will jump very noticeably. You can see this with the debug renderer but I have a sprite attached in my code. The jump is always in the direction that the sprite is headed. I've got a fixed time step with interpolation, and I update the sprites position to an interpolated value of the current and last position of a box2d body every frame. Then set the camera to the interpolated position.
import java.util.Random;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Input.Orientation;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.JointEdge;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
public class GameScreen implements Screen {
private static learnGame game;
private static OrthographicCamera camera;
private static FitViewport viewport;
private static Random rand;
private static BitmapFont font;
private static Vector3 touch;
private static double frameTime;
private static double accumulator;
private static float animTime = 0f;
private static float step = 1f / 60f;
private static boolean killBody;
private static Buttons buttons;
public static Player tom;
public static InputMultiplexer multiplexer;
public static Bodies world;
public static boolean paused;
private static Sprite tomSprite = new Sprite(new Texture(Gdx.files.internal("chars/bob.png")));
public GameScreen(learnGame learngame) {
GameScreen.game = learngame;
multiplexer = new InputMultiplexer();
Gdx.input.setInputProcessor(multiplexer);
camera = new OrthographicCamera();
viewport = new FitViewport(40, 22.5f, camera);
buttons = new Buttons(game, multiplexer, viewport); //HUD stuff
world = new Bodies(viewport, multiplexer); //creates a box2d world
tom = new Player(world.box2d, 10, 15, 1f, multiplexer); //creates a body with a CircleShape with a radius of 1. Catches user input to apply forces to the body
Assets.loadSprites();
world.box2d.getBodies(world.bodies);
font = new BitmapFont();
touch = new Vector3();
rand = new Random();
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
learnGame.batch.setProjectionMatrix(camera.combined);
frameTime = 0;
if (!paused) {
frameTime = Math.min(Gdx.graphics.getRawDeltaTime(), 0.25);
accumulator += frameTime;
tom.update(); //here I apply forces to the body that I attach a camera to
generalUpdate(delta, touch, camera);
updatePositions(); //get previous positions for interpolation
while (accumulator >= step) {
world.box2d.step(step, 6, 2);
accumulator -= step;
interpolate((float) (accumulator / step));
}
world.box2d.clearForces();
learnGame.batch.begin();
if (tom.getBody().isActive()) {
tomSprite.setSize(tom.getHeight(), tom.getHeight());
tomSprite.setOriginCenter();
tomSprite.setRotation(Assets.tom.angle);//this doesn't work right but not the point.
tomSprite.setPosition(Assets.tom.pos.x, Assets.tom.pos.y);
tomSprite.draw(learnGame.batch);
}
learnGame.batch.end();
cameraUpdate(); //update the camera to the same position as the sprite
} else { //else pause the game
learnGame.batch.begin();
learnGame.batch.draw(Assets.pauset, viewport.getCamera().position.x
- (viewport.getCamera().viewportWidth / 2), viewport.getCamera().position.y
- (viewport.getCamera().viewportHeight / 2), viewport.getWorldWidth(), viewport.getWorldHeight());
learnGame.batch.end();
if (Gdx.input.isKeyJustPressed(Keys.SPACE) || Gdx.input.justTouched()) {
paused = false;
}
}
//destroy fixtures and bodies outside of the world step
for (Fixture fixture : Bodies.fixturesToDestroy) {
if (Bodies.destroyJoint == true) {
world.box2d.destroyJoint(Bodies.joint);
Bodies.joint = null;
Bodies.destroyJoint = false;
}
fixture.getBody().destroyFixture(fixture);
Bodies.fixturesToDestroy.removeValue(fixture, true);
}
for (Body body : Bodies.bodiesToDestroy) {
world.box2d.destroyBody(body);
body.setActive(false);
Bodies.bodiesToDestroy.removeValue(body, true);
}
}
#Override
public void show() {
Assets.firstSound.play();
}
#Override
public void resize(int width, int height) {
viewport.update(width, height, true);
Assets.reloadFont();
}
#Override
public void pause() {
paused = true;
}
#Override
public void resume() {
}
#Override
public void hide() {
paused = true;
}
#Override
public void dispose() {
Bodies.box2d.dispose();
Bodies.debugRenderer.dispose();
Buttons.stage.dispose();
Assets.cFrame.getTexture().dispose();
Assets.firstSound.dispose();
System.out.println("disposed");
}
public void generalUpdate(float delta, Vector3 touch, OrthographicCamera camera) {
if (Gdx.input.isKeyPressed(Keys.PAGE_UP)) {
camera.zoom -= 2f * Gdx.graphics.getDeltaTime();
;
} else if (Gdx.input.isKeyPressed(Keys.PAGE_DOWN)) {
camera.zoom += 2f * Gdx.graphics.getDeltaTime();
}
}
public static void cameraUpdate() {
camera.position.set(Assets.tom.pos, 0);
System.out.println("cam:" + Assets.tom.pos.x);
camera.update();
}
public static void kill() {
world.box2d.dispose();
world.debugRenderer.dispose();
Assets.cFrame.getTexture().dispose();
}
public void updatePositions() {
for (MySprite name : Assets.spriteList) {
name.prevPos = name.body.getTransform().getPosition();
name.prevAngle = name.body.getTransform().getRotation();
}
}
public void interpolate(float alpha) {
for (MySprite name : Assets.spriteList) {
name.pos.x = (name.body.getTransform().getPosition().x) * alpha + name.prevPos.x * (1.0f - alpha);
name.pos.y = (name.body.getTransform().getPosition().y) * alpha + name.prevPos.y * (1.0f - alpha);
name.angle = (name.body.getTransform().getRotation() * alpha + name.prevAngle * (1.0f - alpha));
}
}
}
I have tested my interpolation implementation without the camera moving and it appears to work fine. I've been able to test this on my desktop and on my android. The sprite "jumps" a lot more on my android but it happens on both devices. Not sure where I went wrong here, would really appreciate some input!
It's an old question, I know, but I just solved my Problem today.
I have implemented a fixed Timestep and interpolation, but my body lagged and jittered and jumped like there would be no tomorrow.
Please do following things to your camera Movement:
Use the interpolated Position from the body, NOT the original body position.
Use camera.position.slerp(x, y, z, alpha) to do a smooth movement. (You need to play a bit with the variables.
A really simple Example:
static Vector3 desiredPosition;
static {
desiredPosition = new Vector3(0, 0, 0);
}
public static moveCam(Player player, Camera camera){
desiredPosition.x = player.interpolatedPos.x;
desiredPosition.y = player.interpolatedPos.y;
camera.slerp(desiredPosition, Gdx.graphics.getDeltaTime * 5);
camera.update();
}
and in your render do the following
render(float delta){
moveCam(player, camera);
}
(Instant position setting caused jitter for my moving Object)
I wanted to give up my project, because of this bug. But I managed to do it. I hope it'll help people out there.
€dit: A guess why the jumping body is happening:
The interpolated position is not synchron with the camera (set Position then update world(now interpolated pos is offset with camera), then draw)
But I'm not sure with this.

Get View position

I am developing a simple app that produced bubbles on screen on touch. Bubble move around on the screen and get popped when it reaches the border of screen or if a user touches it. I have successfully coded a bubble to pop when reaches borders of the screen but can't figure out a way to detect if the user touched it.
I want to detect if the user touched any bubble on the screen.
Note:- The bubbles are created using custom view. Also I have included some important functions only but can include whole code if you want. Here's the code
public class BubbleActivity extends Activity {
// These variables are for testing purposes, do not modify
private final static int RANDOM = 0;
private final static int SINGLE = 1;
private final static int STILL = 2;
private static int speedMode = RANDOM;
private static final int MENU_STILL = Menu.FIRST;
private static final int MENU_SINGLE_SPEED = Menu.FIRST + 1;
private static final int MENU_RANDOM_SPEED = Menu.FIRST + 2;
private static final String TAG = "Lab-Graphics";
// Main view
private RelativeLayout mFrame;
// Bubble image
private Bitmap mBitmap;
// Display dimensions
private int mDisplayWidth, mDisplayHeight;
// Sound variables
// AudioManager
private AudioManager mAudioManager;
// SoundPool
private SoundPool mSoundPool;
// ID for the bubble popping sound
private int mSoundID;
// Audio volume
private float mStreamVolume;
// Gesture Detector
private GestureDetector mGestureDetector;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Set up user interface
mFrame = (RelativeLayout) findViewById(R.id.frame);
// Load basic bubble Bitmap
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.b64);
}
#Override
protected void onResume() {
super.onResume();
// Manage bubble popping sound
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Get the size of the display so this view knows where borders are
mDisplayWidth = mFrame.getWidth();
mDisplayHeight = mFrame.getHeight();
}
}
// Set up GestureDetector
private void setupGestureDetector() {
mGestureDetector = new GestureDetector(this,
new GestureDetector.SimpleOnGestureListener() {
// Detecting if user touched bubble here
#Override
public boolean onSingleTapConfirmed(MotionEvent event) {
// Trying to get bubble position but can't just get x=0, y=0 tried
// many things
Log.d(TAG,""+((ViewGroup)mFrame).getChildCount());
for(int i=0; i<((ViewGroup)mFrame).getChildCount(); ++i) {
View nextChild = ((ViewGroup)mFrame).getChildAt(i);
Rect rect = new Rect();
nextChild.getLocalVisibleRect(rect);
int[] location = new int[2];
nextChild.getLocationOnScreen(location);
Log.d(TAG, "X = " + location[0] + " Y = " + location[1]);
}
if(event.getAction() == MotionEvent.ACTION_DOWN){
BubbleView bubbleView = new BubbleView(getApplicationContext(), event.getX(),event.getY());
bubbleView.start();
mFrame.addView(bubbleView);
}
return true;
}
});
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// TODO - delegate the touch to the gestureDetector
return mGestureDetector.onTouchEvent(event);
}
#Override
protected void onPause() {
// TODO - Release all SoundPool resources
super.onPause();
}
// BubbleView is a View that displays a bubble.
// This class handles animating, drawing, popping amongst other actions.
// A new BubbleView is created for each bubble on the display
private class BubbleView extends View {
private static final int BITMAP_SIZE = 64;
private static final int REFRESH_RATE = 40;
private final Paint mPainter = new Paint();
private ScheduledFuture<?> mMoverFuture;
private int mScaledBitmapWidth;
private Bitmap mScaledBitmap;
// location, speed and direction of the bubble
private float mXPos, mYPos, mDx, mDy;
private long mRotate, mDRotate;
public BubbleView(Context context, float x, float y) {
super(context);
log("Creating Bubble at: x:" + x + " y:" + y);
// Create a new random number generator to
// randomize size, rotation, speed and direction
Random r = new Random();
// Creates the bubble bitmap for this BubbleView
createScaledBitmap(r);
// Adjust position to center the bubble under user's finger
mXPos = x - mScaledBitmapWidth / 2;
mYPos = y - mScaledBitmapWidth / 2;
// Set the BubbleView's speed and direction
setSpeedAndDirection(r);
// Set the BubbleView's rotation
setRotation(r);
mPainter.setAntiAlias(true);
}
// Start moving the BubbleView & updating the display
private void start() {
// Creates a WorkerThread
ScheduledExecutorService executor = Executors
.newScheduledThreadPool(1);
// Execute the run() in Worker Thread every REFRESH_RATE
// milliseconds
// Save reference to this job in mMoverFuture
mMoverFuture = executor.scheduleWithFixedDelay(new Runnable() {
#Override
public void run() {
// TODO - implement movement logic.
// Each time this method is run the BubbleView should
// move one step. If the BubbleView exits the display,
// stop the BubbleView's Worker Thread.
// Otherwise, request that the BubbleView be redrawn.
if(!isOutOfView()){
moveWhileOnScreen();
}
else{
stop(true);
}
}
}, 0, REFRESH_RATE, TimeUnit.MILLISECONDS);
}
private synchronized boolean intersects(float x, float y) {
// TODO - Return true if the BubbleView intersects position (x,y)
return false;
}
// Cancel the Bubble's movement
// Remove Bubble from mFrame
// Play pop sound if the BubbleView was popped
private void stop(final boolean popped) {
if (null != mMoverFuture && mMoverFuture.cancel(true)) {
// This work will be performed on the UI Thread
mFrame.post(new Runnable() {
#Override
public void run() {
// TODO - Remove the BubbleView from mFrame
if (popped) {
log("Pop!");
// TODO - If the bubble was popped by user,
// play the popping sound
mFrame.removeView(BubbleView.this);
//mMoverFuture.cancel(true);
mSoundPool.play(mSoundID, 1, 1, 1, 0, 1);
}
log("Bubble removed from view!");
}
});
}
}
// Change the Bubble's speed and direction
private synchronized void deflect(float velocityX, float velocityY) {
log("velocity X:" + velocityX + " velocity Y:" + velocityY);
//TODO - set mDx and mDy to be the new velocities divided by the REFRESH_RATE
mDx = velocityX/REFRESH_RATE;
mDy = velocityY/REFRESH_RATE;
}
// Draw the Bubble at its current location
#Override
protected synchronized void onDraw(Canvas canvas) {
// TODO - save the canvas
canvas.save();
// TODO - increase the rotation of the original image by mDRotate
mRotate = mRotate + mDRotate;
// TODO Rotate the canvas by current rotation
canvas.rotate(mRotate, mXPos + mScaledBitmapWidth/2, mYPos + mScaledBitmapWidth/2);
// TODO - draw the bitmap at it's new location
canvas.drawBitmap(mScaledBitmap, mXPos, mYPos,mPainter);
// TODO - restore the canvas
canvas.restore();
}
private synchronized boolean moveWhileOnScreen() {
// TODO - Move the BubbleView
// Returns true if the BubbleView has exited the screen
mXPos = mDx+mXPos;
mYPos = mDy+mYPos;
postInvalidate();
return false;
}
private boolean isOutOfView() {
// TODO - Return true if the BubbleView has exited the screen
if(mXPos + mScaledBitmapWidth/2 >= mDisplayWidth - mScaledBitmapWidth/2 || mXPos <0
||mYPos + mScaledBitmapWidth/2 >= mDisplayHeight - mScaledBitmapWidth/2 || mYPos <0){
return true;
}
return false;
}
}
Update :-
To clarify a bit, I want to get the location of all the bubbles on the screen and then compare them to event.getX() and event.getY() to detect if i tapped on any bubble. II have to check bubble tap in onSingleTapConfirmed(). I am correctly able to get the total number of bubbles but can't detect their location on the screen.
for(int i=0; i<((ViewGroup)mFrame).getChildCount(); ++i) {
View nextChild = ((ViewGroup)mFrame).getChildAt(i);
Rect rect = new Rect();
nextChild.getLocalVisibleRect(rect);
int[] location = new int[2];
nextChild.getLocationOnScreen(location);
Log.d(TAG, "X = " + location[0] + " Y = " + location[1]);
}
Above code gives the correct number of bubbles but return their coordinates as 0,0.
In your onSingleTapConfirmed function, try the following to iterate through your BubbleViews and pass the Event X and Y coordinates on.
for(int i=0;i<mFrame.getChildCount();i++){
BubbleView bubbleThis = (BubbleView) mFrame.getChildAt(i);
if (bubbleThis.intersects(event.getX(),event.getY())){
bubbleThis.stop(true);
return true;
}
}
The function in BubbleView should then return true if the X and Y fall inside its boundaries. I will add the function inside intersects function in BubbleView as clarification:
private synchronized boolean intersects(float x, float y) {
if ( (x>mXPos && x<(mXPos+mScaledBitmapWidth)) && (y>mYPos && y<(mYPos+mScaledBitmapWidth)) ) {
return true;
}
return false;
}
If you want to know if a user tapped a bubble, set its onClickListener. If you want to know if the user just touched it, override its onTouchEvent and look for ACTION_DOWN.
How are you implementing the onDown() method of your SimpleOnGestureListener?
Please take a look at these answers:
Gesture Detector not working
Android GestureDetector with SimpleOnGestureListener within SurfaceView
Detect which View was tapped in the onSingleTapConfirmed method
Bubble is circle in shape, so you just need to compare its radius with the distance between bubble center and the position.
mRadius = radius of the bubble
mDistance = distance between (event.getX(), event.getY()) and bubble center (mXPos + mRadius, mYPos + mRadius)

Screen Recorder Android Plugin in Unity

I'm developing an Unity-Android Plugin to record game screen and create a mp4 video file.I follow to Android Breakout game recorder patch sample in this site : http://bigflake.com/mediacodec/.
First, I create my CustomUnityPlayer class that extends UnityPlayer class and override onDrawFrame method.Here is my CustomUnityPlayer class code :
package com.example.screenrecorder;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.ContextWrapper;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import com.unity3d.player.*;
public class CustomUnityPlayer extends UnityPlayer implements GLSurfaceView.Renderer {
public static final String TAG = "ScreenRecord";
public static final boolean EXTRA_CHECK = true; // enable additional assertions
private GameRecorder recorder;
static final float mProjectionMatrix[] = new float[16];
private final float mSavedMatrix[] = new float[16];
private EGLDisplay mSavedEglDisplay;
private EGLSurface mSavedEglDrawSurface;
private EGLSurface mSavedEglReadSurface;
private EGLContext mSavedEglContext;
// Frame counter, used for reducing recorder frame rate.
private int mFrameCount;
static final float ARENA_WIDTH = 768.0f;
static final float ARENA_HEIGHT = 1024.0f;
private int mViewportWidth, mViewportHeight;
private int mViewportXoff, mViewportYoff;
private final float[] mViewMatrix = new float[16];
private final float[] mRotationMatrix = new float[16];
private float mAngle;
public CustomUnityPlayer(ContextWrapper context) {
// TODO Auto-generated constructor stub
super(context);
this.recorder = GameRecorder.getInstance();
}
private boolean recordThisFrame() {
final int TARGET_FPS = 30;
mFrameCount ++;
switch (TARGET_FPS) {
case 60:
return true;
case 30:
return (mFrameCount & 0x01) == 0;
case 24:
// want 2 out of every 5 frames
int mod = mFrameCount % 5;
return mod == 0 || mod == 2;
default:
return true;
}
}
public void onDrawFrame(GL10 gl){
//record this frame
if (this.recorder.isRecording() && this.recordThisFrame()) {
saveRenderState();
// switch to recorder state
this.recorder.makeCurrent();
super.onDrawFrame(gl);
this.recorder.getProjectionMatrix(mProjectionMatrix);
this.recorder.setViewport();
this.recorder.swapBuffers();
restoreRenderState();
}
}
public void onSurfaceCreated(GL10 paramGL10, EGLConfig paramEGLConfig){
// now repeat it for the game recorder
if (this.recorder.isRecording()) {
Log.d(TAG, "configuring GL for recorder");
saveRenderState();
this.recorder.firstTimeSetup();
super.onSurfaceCreated(paramGL10, paramEGLConfig);
this.recorder.makeCurrent();
//glSetup();
restoreRenderState();
mFrameCount = 0;
}
if (EXTRA_CHECK) Util.checkGlError("onSurfaceCreated end");
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
/*
* We want the viewport to be proportional to the arena size. That way a 10x10
* object in arena coordinates will look square on the screen, and our round ball
* will look round.
*
* If we wanted to fill the entire screen with our game, we would want to adjust the
* size of the arena itself, not just stretch it to fit the boundaries. This can have
* subtle effects on gameplay, e.g. the time it takes the ball to travel from the top
* to the bottom of the screen will be different on a device with a 16:9 display than on
* a 4:3 display. Other games might address this differently, e.g. a side-scroller
* could display a bit more of the level on the left and right.
*
* We do want to fill as much space as we can, so we should either be pressed up against
* the left/right edges or top/bottom.
*
* Our game plays best in portrait mode. We could force the app to run in portrait
* mode (by setting a value in AndroidManifest, or by setting the projection to rotate
* the world to match the longest screen dimension), but that's annoying, especially
* on devices that don't rotate easily (e.g. plasma TVs).
*/
super.onSurfaceChanged(unused, width, height);
if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged start");
float arenaRatio = ARENA_HEIGHT / ARENA_WIDTH;
int x, y, viewWidth, viewHeight;
if (height > (int) (width * arenaRatio)) {
// limited by narrow width; restrict height
viewWidth = width;
viewHeight = (int) (width * arenaRatio);
} else {
// limited by short height; restrict width
viewHeight = height;
viewWidth = (int) (height / arenaRatio);
}
x = (width - viewWidth) / 2;
y = (height - viewHeight) / 2;
Log.d(TAG, "onSurfaceChanged w=" + width + " h=" + height);
Log.d(TAG, " --> x=" + x + " y=" + y + " gw=" + viewWidth + " gh=" + viewHeight);
GLES20.glViewport(x, y, viewWidth, viewHeight);
mViewportXoff = x;
mViewportYoff = y;
mViewportWidth = viewWidth;
mViewportHeight = viewHeight;
// Create an orthographic projection that maps the desired arena size to the viewport
// dimensions.
//
// If we reversed {0, ARENA_HEIGHT} to {ARENA_HEIGHT, 0}, we'd have (0,0) in the
// upper-left corner instead of the bottom left, which is more familiar for 2D
// graphics work. It might cause brain ache if we want to mix in 3D elements though.
Matrix.orthoM(mProjectionMatrix, 0, 0, ARENA_WIDTH,
0, ARENA_HEIGHT, -1, 1);
Log.d(TAG, "onSurfaceChangedEnd 1 w=" + width + " h=" + height);
if (EXTRA_CHECK) Util.checkGlError("onSurfaceChanged end");
Log.d(TAG, "onSurfaceEnded w=" + width + " h=" + height);
}
public void pause(){
super.pause();
this.recorder.gamePaused();
}
/**
* Saves the current projection matrix and EGL state.
*/
public void saveRenderState() {
System.arraycopy(mProjectionMatrix, 0, mSavedMatrix, 0, mProjectionMatrix.length);
mSavedEglDisplay = EGL14.eglGetCurrentDisplay();
mSavedEglDrawSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
mSavedEglReadSurface = EGL14.eglGetCurrentSurface(EGL14.EGL_READ);
mSavedEglContext = EGL14.eglGetCurrentContext();
}
/**
* Saves the current projection matrix and EGL state.
*/
public void restoreRenderState() {
// switch back to previous state
if (!EGL14.eglMakeCurrent(mSavedEglDisplay, mSavedEglDrawSurface, mSavedEglReadSurface,
mSavedEglContext)) {
throw new RuntimeException("eglMakeCurrent failed");
}
System.arraycopy(mSavedMatrix, 0, mProjectionMatrix, 0, mProjectionMatrix.length);
}
}
And then, i create a CustomUnityPlayerActivity to call this class
package com.example.screenrecorder;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import com.unity3d.player.UnityPlayerActivity;
public class CustomUnityActivity extends UnityPlayerActivity {
private CustomUnityPlayer mUnityPlayer;
private GameRecorder mRecorder;
#Override
protected void onCreate(Bundle paramBundle){
Log.e("ScreenRecord","oncreate");
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(paramBundle);
this.mUnityPlayer = new CustomUnityPlayer(this);
if (this.mUnityPlayer.getSettings().getBoolean("hide_status_bar", true))
getWindow().setFlags(1024, 1024);
int glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1);
boolean trueColor8888 = false;
mUnityPlayer.init(glesMode, trueColor8888);
View playerView = mUnityPlayer.getView();
setContentView(playerView);
playerView.requestFocus();
this.mRecorder = GameRecorder.getInstance();
this.mRecorder.prepareEncoder(this);
}
public void beginRecord(){
Log.e("ScreenRecord","start record");
this.mUnityPlayer.saveRenderState();
this.mRecorder.firstTimeSetup();
this.mRecorder.setStartRecord(true);
this.mRecorder.makeCurrent();
this.mUnityPlayer.restoreRenderState();
}
public void endRecord(){
Log.e("ScreenRecord","end record");
this.mRecorder.endRecord();
this.mRecorder.setStartRecord(false);
//this.mTransView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public boolean isRecording(){
return this.mRecorder.isRecording();
}
protected void onDestroy()
{
super.onDestroy();
this.mUnityPlayer.quit();
}
protected void onPause()
{
super.onPause();
this.mUnityPlayer.pause();
}
protected void onResume()
{
super.onResume();
this.mUnityPlayer.resume();
}
public void onConfigurationChanged(Configuration paramConfiguration)
{
super.onConfigurationChanged(paramConfiguration);
this.mUnityPlayer.configurationChanged(paramConfiguration);
}
public void onWindowFocusChanged(boolean paramBoolean)
{
super.onWindowFocusChanged(paramBoolean);
this.mUnityPlayer.windowFocusChanged(paramBoolean);
}
public boolean onKeyDown(int paramInt, KeyEvent paramKeyEvent)
{
return this.mUnityPlayer.onKeyDown(paramInt, paramKeyEvent);
}
public boolean onKeyUp(int paramInt, KeyEvent paramKeyEvent)
{
return this.mUnityPlayer.onKeyUp(paramInt, paramKeyEvent);
}
}
My problem is that a video file is created successful but my game can't render anything.I read on Android Media Codec sample site and recognize that each frame would be render twice (once for the display, once for the video) but I can't do this in Unity.Whenever I try to call super.onDrawFrame(gl) twice in onDrawFrame method , my game will be crashed.
Any solution for my problem? Any help will be greatly appreciated!
Thanks and best regard!
Huy Tran
Finally I use a FrameBufferObject (FBO) to render offscreen and get its binding texture to blit twice:
Render to video surface
Redraw to device screen
You can find more detail about this solution by reference to my another question use FBO to record Unity gamescreen
Kamcord plug-in can help you : http://www.kamcord.com/

Android thread wait until visible

I've made a custom pie chart view that I want to animate starting when the pie chart is visible. Currently what I have is the pie chart animating but by the time you can actually see it on the screen the animation is half over. This is what I have:
public class SinglePieChart extends SurfaceView implements SurfaceHolder.Callback {
// Chart setting variables
private int emptyCircleCol, strokeColor, number, total;
// Paint for drawing custom view
private Paint circlePaint;
private RectF rect;
private Context context;
private AnimThread animThread;
private SurfaceHolder holder;
// animation variables
private float speed;
private float current = 0.0f;
private boolean percentsCalculated = false;
private float degree;
private int viewWidth, viewHeight;
public SinglePieChart(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
context = ctx;
// Paint object for drawing in doDraw
circlePaint = new Paint();
circlePaint.setStyle(Style.STROKE);
circlePaint.setStrokeWidth(3);
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
rect = new RectF();
//get the attributes specified in attrs.xml using the name we included
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.DashboardChartSmall, 0, 0);
try {
//get the colors specified using the names in attrs.xml
emptyCircleCol = a.getColor(R.styleable.DashboardChartSmall_smCircleColor, 0xFF65676E); // light gray is default
strokeColor = a.getColor(R.styleable.DashboardChartSmall_smColor, 0xFF39B54A); // green is default
// Default number values
total = a.getInteger(R.styleable.DashboardChartSmall_smTotal, 1);
number = a.getInteger(R.styleable.DashboardChartSmall_smNumber, 0);
} finally {
a.recycle();
}
this.setZOrderOnTop(true);
holder = getHolder();
holder.setFormat(PixelFormat.TRANSPARENT);
holder.addCallback(this);
}
protected void calculateValues() {
degree = 360 * number / total;
percentsCalculated = true;
speed = 10 * number / total;
viewWidth = this.getMeasuredWidth();
viewHeight = this.getMeasuredHeight();
float top, left, bottom, right;
if (viewWidth < viewHeight) {
left = 4;
right = viewWidth - 4;
top = ((viewHeight - viewWidth) / 2) + 4;
bottom = viewHeight - top;
} else {
top = 4;
bottom = viewHeight - 4;
left = ((viewWidth - viewHeight) / 2) + 4;
right = viewWidth - left;
}
rect.set(left, top, right, bottom);
}
protected void doDraw(Canvas canvas) {
if (total == 0) {
// Number values are not ready
animThread.setRunning(false);
return;
}
if (!percentsCalculated) {
calculateValues();
}
// set the paint color using the circle color specified
float last = current;
float start = -90;
circlePaint.setColor(strokeColor);
canvas.drawArc(rect, start, (last > degree) ? degree : last, false, circlePaint);
start += (last > number) ? number : last;
last = (last < number) ? 0 : last - number;
circlePaint.setColor(emptyCircleCol);
if (current > 360) {
current = 360;
}
canvas.drawArc(rect, start, 360 - current, false, circlePaint);
current += speed;
if (last > 0 || number == 0) {
// we're done
animThread.setRunning(false);
}
}
public void setNumbers(int num, int tot) {
number = num;
total = tot;
invalidate();
requestLayout();
}
public void setColor(int col) {
strokeColor = col;
}
public void redraw() {
calculateValues();
animThread.setRunning(true);
invalidate();
requestLayout();
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
animThread = new AnimThread(holder, context, this);
animThread.setRunning(true);
animThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
animThread.setRunning(false);
boolean retry = true;
while(retry) {
try {
animThread.join();
retry = false;
} catch(Exception e) {
Log.v("Exception Occured", e.getMessage());
}
}
}
public class AnimThread extends Thread {
boolean mRun;
Canvas mcanvas;
SurfaceHolder surfaceHolder;
Context context;
SinglePieChart msurfacePanel;
public AnimThread(SurfaceHolder sholder, Context ctx, SinglePieChart spanel) {
surfaceHolder = sholder;
context = ctx;
mRun = false;
msurfacePanel = spanel;
}
void setRunning(boolean bRun) {
mRun = bRun;
}
#Override
public void run() {
super.run();
while (mRun) {
mcanvas = surfaceHolder.lockCanvas();
if (mcanvas != null) {
msurfacePanel.doDraw(mcanvas);
surfaceHolder.unlockCanvasAndPost(mcanvas);
}
}
}
}
}
Also if you see any programming errors, memory leaks, poor performing code, please let me know. I'm new to Android.
Here is the layout that uses the SinglePieChart class:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.davidscoville.vokab.views.elements.SinglePieChart
android:id="#+id/smallPieChart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="#+id/dashSmNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:textSize="25sp"
android:textColor="#FFFFFF" />
</RelativeLayout>
<TextView
android:id="#+id/dashSmLabel"
android:layout_width="match_parent"
android:layout_height="20dp"
android:textSize="14sp"
android:gravity="center"
android:textColor="#FFFFFF" />
</merge>
Alright I'm going with the my pie chart won't automatically animate and it will have a new function that the Activity will trigger to start animating once it's ready. I wish there was an easier way...
Alternatively you can use the animation framework(or nine old androids if you want to support older apis). This will allow you to animate properties on your view, in your case the start and current variables.
I'd set this to happen during onAttachedToWindow.
Note if you aren't doing a lot of other things in this pie chart a surfaceview might be overkill for your needs.

Categories

Resources