I have created an application that holds 7 different mini games. One of these mini games has always run fine, well, it always had some frame issues usually running at 6fps. I ran this game recently and realized the frame rate is now pretty much unplayable. I don't what caused this issue and can anyone recommend a fix to improve the frame rate?
The game uses many different classes to be created, it is a surface view. Copying all the classes here will be a lot of code so I will just post the class that creates the surface.
Any help with improving an application frame rate would be helpful thank you.
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback
{
public static final int WIDTH = 856;
public static final int HEIGHT = 480;
public static final int MOVESPEED = -5;
private long missileStartTime;
private MainThread thread;
private Background bg;
private Player player;
private ArrayList<Missile> missiles;
private Random rand = new Random();
int score = 0;
private final Context context;
public GamePanel(Context context)
{
super(context);
this.context = context;
//add the callback to the surfaceholder to intercept events
getHolder().addCallback(this);
thread = new MainThread(getHolder(), 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;
int counter = 0;
while(retry && counter<1000)
{
counter++;
try{thread.setRunning(false);
thread.join();
retry = false;
}catch(InterruptedException e){e.printStackTrace();}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
bg = new Background(BitmapFactory.decodeResource(getResources(), R.drawable.nightskyresize));
player = new Player(BitmapFactory.decodeResource(getResources(), R.drawable.pilot), 65, 25, 1);
missiles = new ArrayList<Missile>();
missileStartTime = System.nanoTime();
//we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction()==MotionEvent.ACTION_DOWN){
if(!player.getPlaying())
{
player.setPlaying(true);
}
else
{
player.setUp(true);
}
return true;
}
if(event.getAction()==MotionEvent.ACTION_UP)
{
player.setUp(false);
return true;
}
return super.onTouchEvent(event);
}
public void update()
{
if(player.getPlaying()) {
bg.update();
player.update();
//add missiles on timer
long missileElapsed = (System.nanoTime()-missileStartTime)/10000000;
if(missileElapsed >(2000 - player.getScore()/4)){
System.out.println("making missile");
//first missile always goes down the middle
if(missiles.size()==0)
{
missiles.add(new Missile(BitmapFactory.decodeResource(getResources(),R.drawable.
missile),WIDTH + 10, HEIGHT/2, 45, 15, player.getScore(), 1));
}
else
{
missiles.add(new Missile(BitmapFactory.decodeResource(getResources(),R.drawable.missile),
WIDTH+10, (int)(rand.nextDouble()*(HEIGHT)),45,15, player.getScore(),1));
}
//reset timer
missileStartTime = System.nanoTime();
}
//loop through every missile and check collision and remove
for(int i = 0; i<missiles.size();i++)
{
//update missile
missiles.get(i).update();
if(collision(missiles.get(i),player))
{
Intent i1 = new Intent (context, main_menu.class);
context.startActivity(i1);
missiles.remove(i);
player.setPlaying(false);
break;
}
//remove missile if it is way off the screen
if(missiles.get(i).getX()<-100)
{
score++;
missiles.remove(i);
break;
}
}
}
if(score == 100){
Intent i1 = new Intent (context, main_menu.class);
context.startActivity(i1);
}
}
public boolean collision(GameObject a, GameObject b)
{
if(Rect.intersects(a.getRectangle(),b.getRectangle()))
{
return true;
}
return false;
}
#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 savedState = canvas.save();
canvas.scale(scaleFactorX, scaleFactorY);
bg.draw(canvas);
player.draw(canvas);
//draw missiles
for(Missile m: missiles)
{
m.draw(canvas);
}
drawText(canvas);
canvas.restoreToCount(savedState);
}
}
public void drawText(Canvas canvas)
{
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(30);
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
canvas.drawText("DISTANCE: " + (player.getScore()*3), 10, HEIGHT - 10, paint);
}
}
I also got the log to print out the frame rate, this was the result.
Any ideas on the cause of this? the fps is the 0.0, this usually would say around 6.0 or 7.0.
This is an odd question, but where I have music it sounds like there is two versions playing over each other, like the application is running two instances of itself. is this possible?
This is the problem
Related
Im using the MLKit combined with the CameraX Image-Analysis Use Case to build an App, that lets you control a cursor, which gets drawn onto an overlay based on the position of the face (head based pointing essentially).
I have two activities:
Activity 1 previews the camera feed and draws the facial landmarks (in my case, just the nosetip) onto the overlay as well as the cursor. As I said before, the cursor position is being calculated based on the nosetip position in the image -> No problems here
Activity 2 runs a very simplistic game in a separate thread, which spawns obstacles that need to be avoided with the cursor. No live camera feed, "just" the face detection, the face -> cursor screen mapping, a custom gameView and the overlay for the cursor ontop.
My problem:
After some time, the face detector just stops detecting faces (thus the cursor gets "stuck") without any errors and oddly enough, the game incrementally speeds up (but still registers collision with the cursor).
I already followed the instructions for optimizing face detection in real-time by only enabling landmark-mode, enabled tracking and setting the performance mode to fast.
My guess was that running the face detection, mapping the cursor to a point on the screen, drawing onto the overlay for the cursor and having a game running at the same time might be just too much work, but I read somewhere that MLKit runs its detection on its own thread.
It must have something to do with my game, because as already mentioned above, in Activity 1 (Live camera preview + calculating cursor position + drawing landmarks & cursor on the overlay canvas) I never faced (no pun intended) a crash.
My custom GameView:
public class GameView extends SurfaceView implements SurfaceHolder.Callback{
private final int difficultyTime = 5;
private GameThread thread;
private long startTime;
private long timer;
private long difficultyTimer;
private long lastSpawned = 0;
private Paint textPaint;
private Random random;
private Pointer pointer;
private GameManager game;
public GameView(Context context) {
super(context);
init();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GameView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
getHolder().addCallback(this);
thread = new GameThread(getHolder(), this);
game = new GameManager();
setFocusable(true);
textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(70);
Paint collisionPaint = new Paint();
collisionPaint.setColor(Color.BLUE);
Paint pointerPaint = new Paint();
pointerPaint.setColor(Color.GREEN);
startTime = System.currentTimeMillis();
lastSpawned = System.currentTimeMillis();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
random = new Random();
}
public void update() {
timer = (System.currentTimeMillis() - startTime) / 1000;
long currentTimeMS = System.currentTimeMillis();
if(currentTimeMS > lastSpawned + (game.getCooldown() * 1000L)) {
lastSpawned = System.currentTimeMillis();
game.setMayDraw(true);
}
if(checkCollision()) {
gameover();
}
// Raise difficulty
if(difficultyTimer <= System.currentTimeMillis()) {
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
game.advanceDifficulty();
}
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
int bgColor = Color.WHITE;
canvas.drawColor(bgColor);
// Spawn elements
if(game.getMayDraw()) {
game.setMayDraw(false);
spawnObstacles(canvas);
}
// Update existing elements
game.updateObstacles(canvas);
// Draw the timer
drawTimer(canvas);
// Draw difficulty text
drawDifficulty(canvas);
}
private void drawTimer(Canvas canvas) {
canvas.drawText(Long.toString(timer), (getWidth() / 2), 80, textPaint);
invalidate();
}
private void drawDifficulty(Canvas canvas) {
canvas.drawText("Difficulty: " + game.getCurrentDifficultyIndex(), (getWidth() / 2) + 250, 80, textPaint);
}
private void spawnObstacles(Canvas canvas) {
int random_number;
int last_random = 0;
for(int i = 0; i < game.getMAX_SPAWN(); i++) {
do {
random_number = random.nextInt(4) + 1;
}while(random_number == last_random);
switch(random_number) {
case 1:
game.spawnTop(canvas);
break;
case 2:
game.spawnBottom(canvas);
break;
case 3:
game.spawnLeft(canvas);
break;
case 4:
game.spawnRight(canvas);
break;
}
last_random = random_number;
}
}
public void gameover() {
startTime = System.currentTimeMillis();
game.getObstacles().clear();
game.resetDifficulty();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
}
public boolean checkCollision() {
List<Pair<GameManager.Direction, Rect>> collidables = game.getObstacles();
for (Pair<GameManager.Direction, Rect> r : collidables) {
if(intersects(r.second, pointer.getCollisionRect()) || intersects(pointer.getCollisionRect(), r.second)) {
return true;
}
}
return false;
}
public static boolean intersects(Rect rect, Rect otherRect) {
return rect.left <= otherRect.right && otherRect.left <= rect.right
&& rect.top <= otherRect.bottom && otherRect.top <= rect.bottom;
}
#Override
public void surfaceCreated(#NonNull SurfaceHolder surfaceHolder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceChanged(#NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
System.out.println("Surface changed - Format: " + format + " W/H: ("+width +"/" +height+")");
}
#Override
public void surfaceDestroyed(#NonNull SurfaceHolder surfaceHolder) {
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
retry = false;
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
GameThread:
public class GameThread extends Thread{
private final SurfaceHolder surfaceHolder;
private final GameView gameView;
private boolean running;
public static Canvas canvas;
public GameThread(SurfaceHolder surfaceHolder, GameView gameView) {
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
#Override
public void run() {
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount = 0;
int targetFPS = 15;
long targetTime = 1000 / targetFPS;
while (running) {
startTime = System.nanoTime();
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized(surfaceHolder) {
this.gameView.update();
this.gameView.draw(canvas);
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - timeMillis;
try {
if(waitTime > 0) {
this.sleep(waitTime);
}
} catch (Exception e) {
e.printStackTrace();
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if (frameCount == targetFPS) {
double averageFPS = 1000 / ((totalTime / frameCount) / 1000000);
frameCount = 0;
totalTime = 0;
Log.d("GameFPS", "" + averageFPS);
}
}
}
public void setRunning(boolean isRunning) {
running = isRunning;
}
FaceDetector:
public class Detector {
private final FaceDetector faceDetector;
private final GraphicOverlay overlay;
private final Pointer pointer;
private final ClickHandler clickHandler;
private final boolean drawFaces;
public Detector(GraphicOverlay overlay, Context context, boolean drawFaces) {
this.overlay = overlay;
this.drawFaces = drawFaces;
pointer = new Pointer(overlay, context);
clickHandler = new ClickHandler(context, this.pointer);
FaceDetectorOptions options =
new FaceDetectorOptions.Builder()
//.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
/* disable for better performance *///.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.enableTracking()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.build();
faceDetector = FaceDetection.getClient(options);
}
public void detectFace(ImageProxy image) {
if(image == null) {
return;
}
InputImage inputImage = getInputImage(image);
int rotationDegrees = image.getImageInfo().getRotationDegrees();
if(rotationDegrees == 0 || rotationDegrees == 180) {
overlay.setImageSourceInfo(image.getWidth(), image.getHeight(), true);
}else{
overlay.setImageSourceInfo(image.getHeight(), image.getWidth(), true);
}
if(inputImage == null) {
return;
}
Task<List<Face>> result =
this.faceDetector.process(inputImage)
// When detection succeeded
.addOnSuccessListener(
faces -> {
overlay.clear();
if(faces.size() > 0) {
for(Face face: faces) {
if(drawFaces) {
// Adding faces to overlay
FaceGraphic g = new FaceGraphic(overlay, face);
overlay.add(g);
}
// Update Pointer/Cursor
pointer.update(face);
}
}
})
// When detection failed
.addOnFailureListener(
e -> Log.d("Processing failed", e.getMessage()))
// When detection is completed
.addOnCompleteListener(
task -> image.close()
);
}
#SuppressLint("UnsafeOptInUsageError")
private InputImage getInputImage(ImageProxy image) {
int rotation = image.getImageInfo().getRotationDegrees();
Image mediaImage = image.getImage();
if(mediaImage != null) {
return InputImage.fromMediaImage(mediaImage, rotation);
}else{
return null;
}
}
GameActivity:
public class GameActivity extends AppCompatActivity {
private GraphicOverlay mOverlay;
private GameView gameView;
private Camera camera;
private Pointer pointer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_game);
gameView = findViewById(R.id.gameView);
mOverlay = findViewById(overlay_game);
initCamera();
}
private void initCamera() {
camera = new Camera(this, mOverlay);
camera.setDrawFaces(false);
camera.setAnalysisUseCase();
camera.setAnalyzer();
camera.build();
pointer = camera.getDetector().getPointer();
gameView.setPointer(pointer);
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onPause() {
super.onPause();
}
The face detection "crash" can occur anywhere from 1 second to 5 minutes playing the game. I would gladly appreciate any hints or suggestions. Also im fairly new to java and mobile-app development, so please excuse my bad code.
If you need any further information/code i'll happily provide it.
Thanks in advance
I'm making a small Android game in which I need two ImageViews (obstacles) to move, at the same time, from the top of the screen to the bottom of the screen; starting and ending OUTSIDE of the screen. Doing this would be a start.
I've got a timer, and each time the timer ends, it runs again and I would also want two other ImageViews (same as two previous ones) to move in the same way described above.
In other words:
Some random event triggers a timer
Timer hits threshold -> timer resets and two ImageViews start their way down
What I've got kind of works, but with Bitmaps and Y incrementation. The problem is, because the phone increments as fast as it can, the game can get laggy and the speed of the two images going down won't be the same on two different phones.
I know I've got to use Animations, but I can't get what I want each time I try something with them. They usually don't start where I want them to be and weird stuff happens.
Here are some links I already visited:
Start simple Animation top to bottom from a specific position
Move an ImageView to different position in Animated way in Android
move imageView from outside screen
Android continuous moving animation
How to slide an ImageView from left to right smoothly in Android?
Here's some code to show you what I've got:
GAMEVIEW
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
// [...]
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
// [...]
this.obstacles = new ArrayList <> ();
this.handleObstacleTimer = null;
this.runnableObstacleTimer = null;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// [...]
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
this.startObstacleTimer();
GameLogic.gameState = GameState.RUNNING;
// [...]
}
return result;
}
#Override
public void draw (Canvas canvas) {
super.draw(canvas);
if (canvas != null) {
this.background.draw(canvas);
if (! this.obstacles.isEmpty()) {
for (Obstacle obstacle : this.obstacles) {
obstacle.draw(canvas);
}
}
// [...]
}
}
public void createObstacle () {
Bitmap imageObstacle = BitmapFactory.decodeResource(this.getResources(), R.drawable.obstacle);
this.obstacles.add(new Obstacle(imageObstacle, this, this.displayMetrics.widthPixels, this.displayMetrics.heightPixels));
}
public void removeObstacle (Obstacle obstacle) {
this.obstacles.remove(obstacle);
}
private void stopObstacleTimer () {
if (this.handleObstacleTimer != null && this.runnableObstacleTimer != null) {
this.handleObstacleTimer.removeCallbacks(this.runnableObstacleTimer);
this.handleObstacleTimer = null;
}
}
private void startObstacleTimer () {
if (this.handleObstacleTimer == null) {
final Handler handler = new Handler();
this.runnableObstacleTimer = new Runnable() {
#Override
public void run() {
if (GameLogic.gameState == GameState.RUNNING) {
createObstacle();
handler.postDelayed(this, 2700);
}
}
};
handler.postDelayed(this.runnableObstacleTimer, 2700);
this.handleObstacleTimer = handler;
}
}
// [...]
// Called by a custom Thread class
public void update () {
if (! this.obstacles.isEmpty()) {
for (Obstacle obstacle : this.obstacles) {
obstacle.update();
}
}
// [...]
}
}
MAINTHREAD (custom Thread class)
public class MainThread extends Thread {
private SurfaceHolder surfaceHolder;
private GameView gameView;
private GameLogic gameLogic;
private boolean running;
public static Canvas canvas;
public MainThread (SurfaceHolder surfaceHolder, GameView gameView, GameLogic gameLogic) {
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
this.gameLogic = gameLogic;
}
#Override
public void run() {
while (this.running) {
this.canvas = null;
try {
this.canvas = this.surfaceHolder.lockCanvas();
synchronized (this.surfaceHolder) {
this.gameView.update();
this.gameView.draw(this.canvas);
this.gameLogic.update();
}
} catch (Exception e) {
} finally {
if (this.canvas != null) {
try {
this.surfaceHolder.unlockCanvasAndPost(this.canvas);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public void setRunning (boolean isRunning) {
this.running = isRunning;
}
}
OBSTACLE
public class Obstacle {
private static final int GAP_MIN_X = 300;
private static int GAP_MAX_X;
private static final int GAP_WIDTH = 250;
private GameView gameView;
private int displayHeight;
private Bitmap obstacleLeft;
private Bitmap obstacleRight;
private int leftX;
private int rightX;
private int y;
private boolean passed;
public Obstacle (Bitmap image, GameView gameView, int displayWidth, int displayHeight) {
this.gameView = gameView;
this.displayHeight = displayHeight;
this.obstacleLeft = this.flip(image);
this.obstacleRight = image;
GAP_MAX_X = displayWidth - GAP_MIN_X;
final int randomX = new Random().nextInt((GAP_MAX_X - GAP_MIN_X) + 1) + GAP_MIN_X;
this.leftX = randomX - this.obstacleLeft.getWidth() - (GAP_WIDTH / 2);
this.rightX = randomX + (GAP_WIDTH / 2);
this.y = 0 - this.obstacleLeft.getHeight();
this.passed = false;
}
private Bitmap flip (Bitmap image) {
Matrix m = new Matrix();
m.preScale(-1, 1);
Bitmap result = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), m, false);
result.setDensity(DisplayMetrics.DENSITY_DEFAULT);
return result;
}
/**
* Those getters are used for collision
*/
public int getLeftX () {
return this.leftX;
}
public int getRightX () {
return this.rightX;
}
public int getY () {
return this.y;
}
public int getWidth () {
return this.obstacleLeft.getWidth();
}
public int getHeight () {
return this.obstacleLeft.getHeight();
}
public boolean hasPassed () {
return this.passed;
}
public void setAsPassed () {
this.passed = true;
}
public void draw (Canvas canvas) {
canvas.drawBitmap(this.obstacleLeft, this.leftX, this.y, null);
canvas.drawBitmap(this.obstacleRight, this.rightX, this.y, null);
}
public void update () {
if (! (GameLogic.gameState == GameState.GAMEOVER)) {
this.y += 8;
if (this.y > this.displayHeight) {
this.gameView.removeObstacle(this);
}
}
}
}
Basically, what I want is to have the same result. But without lag and with obstacles going at the same speed no matter which phone the game is running on.
If inside your Obstacle.update() function, you change your Y incrementation from the constant integer 8 to something that is proportional to the time interval that the update function is called, it will be the same speed on any device. You could, for example, calculate the variation based on the difference between the time on the last update() call and the current update call
long speed = 100; //you should add this to adjust the speed of the incrementing
long delta = System.currentTimeMillis();
public void update () {
delta = speed*(System.currentTimeMillis() - delta);
if (! (GameLogic.gameState == GameState.GAMEOVER)) {
this.y += delta;
if (this.y > this.displayHeight) {
this.gameView.removeObstacle(this);
}
}
}
This way, you delta variable will ever be something that varies with the time interval between the last call and the current call beacause System.currentTimeMillis() returns the current time in miliseconds
I have an object on my canvas that I need to rotate based on user input. When the user touches the screen, and then moves their finger, the app calculates the angle between the two touch events, and that is the angle I need my Bitmap to face.
Here is a screenshot with a test Bitmap, I need the nose of the plane to face the direction determined by the input, and then update constantly as the input changes.
This GameView class is set at the ContentView for my activity:
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
MainThread thread;
private Player player;
private Point playerPoint;
private float originX;
private float originY;
private float currX;
private float currY;
private float playerAngle;
public GameView(Context context){
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(),this);
setBackgroundColor(getResources().getColor(R.color.background));
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height){}
#Override
public void surfaceCreated(SurfaceHolder holder) {
int width = this.getWidth();
int height = this.getHeight();
playerPoint = new Point(width/2, height*3/5);
player = new Player(this.getContext(), playerPoint,
getResources().getColor(R.color.colorAccent));
thread = new MainThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder){
boolean retry = true;
while(retry){
try{
thread.setRunning(false);
thread.join();
}catch(Exception e){
e.printStackTrace();
}
retry = false;
}
}
#Override
public boolean onTouchEvent(MotionEvent event){
if(event.getAction() == MotionEvent.ACTION_DOWN){
//System.out.println("Down");
originX = event.getX();
originY = event.getY();
}
currX = event.getX();
currY = event.getY();
//return super.onTouchEvent(event);
return true;
}
public float getTouchAngle(){
float dX = currX - originX;
float dY = currY - originY;
double angle = Math.atan2(dX,-dY);
angle *= 180;
angle /= Math.PI;
return (float) angle;
}
public void update(){
playerAngle = getTouchAngle();
//System.out.println(playerAngle);
player.update(playerAngle);
}
#Override
public void draw(Canvas canvas){
super.draw(canvas);
player.draw(canvas);
}
}
I have my MainThread which just calls the update and draw methods for the GameView class:
public class MainThread extends Thread {
public static final int MAX_FPS = 30;
private double averageFPS;
private SurfaceHolder holder;
private GameView gameView;
private boolean running;
public static Canvas canvas;
public void setRunning(boolean b){
this.running = b;
}
public MainThread(SurfaceHolder holder, GameView gameView){
super();
this.holder = holder;
this.gameView = gameView;
//canvas = null;
}
#Override
public void run(){
long startTime;
long timeMillis = 1000/MAX_FPS;
long waitTime;
int frameCount = 0;
long totalTime = 0;
long targetTime = 1000/MAX_FPS;
while(running){
startTime = System.nanoTime();
canvas = null;
try{
canvas = this.holder.lockCanvas();
synchronized (holder){
this.gameView.update();
this.gameView.draw(canvas);
}
}catch(Exception e){
e.printStackTrace();
} finally {
if(canvas !=null){
try{
holder.unlockCanvasAndPost(canvas);
}catch(Exception e){
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime/1000000);
waitTime = targetTime - timeMillis;
try{
if(waitTime > 0){
this.sleep(waitTime);
}
}catch(Exception e){
e.printStackTrace();
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if(frameCount == MAX_FPS){
averageFPS = 1000 / ((totalTime/frameCount)/1000000);
frameCount = 0;
totalTime = 0;
//System.out.println(averageFPS);
}
}
}
}
And then I have my Player class, which is where I have been trying to get the Bitmap rotation to update:
public class Player implements GameObject {
//private Rect rect;
private int color;
private Paint paint;
private float angle;
private Bitmap icon;
private Point pos;
//private Matrix matrix;
public Player(Context context, Point pos, int color){
//this.rect = rect;
this.pos = pos;
this.color = color;
paint = new Paint();
//paint.setColor(color);
//paint.setStyle(Paint.Style.FILL);
paint.setFilterBitmap(true);
icon = BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_action_name);
//matrix = new Matrix();
}
#Override
public void draw(Canvas canvas){
//Matrix matrix = new Matrix();
//matrix.setRotate(angle, 100, 100);
//canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.rotate(-angle,pos.x,pos.y);
System.out.println(angle);
canvas.drawBitmap(icon,pos.x-50,pos.y-50,paint);
//canvas.restore();
}
#Override
public void update(){
}
public void update(float angle){
this.angle = angle;
}
}
I originally tried to perform the rotation using a Matrix, and I had it working at first, but then I made changes and haven't been able to get it to rotate at all since.
I can tell that the angle is getting computed properly, and the angle variable in the Player class is getting updated, because after a touch event, if I switch to a different app then go back into this activity, the bitmap is facing the correct direction. It just isn't updating in realtime anymore. I have no clue what I did.
(all the commented lines are me trying to tweak things to fix it)
I found the issue. After changing up my code several times to try and get my canvas to update, I tried commenting out the setBackgroundColor() method in my GameView class, and it suddenly started working again. Now I just set the background color on the Canvas itself and it works as it should. If anyone could explain why, I'd love to hear more
I got interested on Android, I'm new at it so I created a scrolling background and I want to put a sprite on it I already created a character class and the scroll background. I want to make the sprite move to the right but I'm getting error on my GamePanel.
The error is Non-static method(android.graphics.Canvas) cannot be referenced from a static context, what does it mean and How am I going to fix this and my make the sprite look like running to the right?
Here's my code:
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback {
public static final int WIDTH = 856;
public static final int HEIGHT = 480;
public static int Score =0;
public static int Highscore;
private MainThread thread;
private Background bg;
private Bitmap bmp;
public GamePanel(Context context) {
super(context);
getHolder().addCallback(this);
thread = new MainThread(getHolder(), this);
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();
}catch(InterruptedException e) {
e.printStackTrace();
retry = false;
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
bg = new Background(BitmapFactory.decodeResource(getResources(), R.drawable.gamebg));
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.deer);
bg.setVector(-5);
thread.setRunning(true);
thread.start();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
public void update() {
Score += 2;
if (Score > Highscore) {
Highscore = Score;
}
bg.update();
}
#SuppressLint("MissingSuperCall")
#Override
public void draw(Canvas canvas) {
final float scaleFactorX = (float)getWidth()/WIDTH;
final float scaleFactorY = (float)getHeight()/HEIGHT;
if(canvas !=null) {
final int savedState = canvas.save();
canvas.scale(scaleFactorX, scaleFactorY);
bg.draw(canvas);
canvas.restoreToCount(savedState);
Paint textpaint = new Paint();
textpaint.setTextSize(30);
canvas.drawText("Score:" +String.valueOf(Score), 0, 32, textpaint);
canvas.drawText("High Score: "+String.valueOf(Highscore), 0, 64, textpaint);
}
}
#Override
protected void onDraw(Canvas canvas) {
Character.onDraw(canvas);
}
}
You cannot access non static methods from a static context. So if you want to access a function or an object of the GamePanel class, you need to call this member by using the object instance of GamePanel class.
Just an example:
wrong:
GamePanel.draw(canvas);
correct:
GamePanel gamePanel = new GamePanel(this);
gamePanel.draw(canvas);
Sorry if this question was asked before but I looked around and couldn't really find an answer.
So I'm drawing things onto a SurfaceView using a thread to update any changes made to the screen, and I also have an array of rectangles. How can I draw the rectangles, one after another, and by the end of it, have all of them on the screen? I thought a for loop in the run() method where everything's drawn would've worked, but it just ends up drawing them all at once (which I can understand why). Then, I tried to put a Thread.sleep after every draw in the for loop, but it just waits until after all the rects are drawn before drawing to the canvas. (again, makes sense since the canvas isn't unlocked until after the for loop). If I wanted, say, a two second pause between drawing rects, how could I do that?
Thanks
Edit: Here is the SurfaceView. the pause() and resume() methods are called in the onPause and onResume methods of the activity for which it is the view.
class TestView extends SurfaceView implements Runnable{
SurfaceHolder holder;
Thread t;
Rect[] rectArray;
public TestView(Context context) {
super(context);
holder = getHolder();
rectArray = shapes.getRectArray();
}
#Override
public void run() {
while(true){
if(!holder.getSurface().isValid())
continue;
Paint p = new Paint();
p.setColor(Color.BLACK);
p.setStrokeWidth(10)
Canvas c = holder.lockCanvas(null);
c.drawARGB(255, 255, 255, 255);
for(int i = 0; i<rectArray.length; i++){
c.drawRect(i,p);
}
holder.unlockCanvasAndPost(c);
}
}
public void pause(){
}
public void resume(){
t = new Thread(this);
t.start();
}
}
I'd probably abstract the thread and SurfaceView and do a basic "game loop" so I have full control over what gets drawn when.
Below is the game loop part:
public class GameLoopThread extends Thread
{
private TestView view;
private boolean running = false;
public GameLoopThread(TestView view)
{
this.view = view;
}
public void setRunning(boolean run)
{
running = run;
}
#SuppressLint("WrongCall")
#Override
public void run()
{
long startTime = System.currentTimeMillis();
while (running)
{
Canvas c = null;
//call update with the number of milliseconds that have passed since the last loop iteration
view.update(System.currentTimeMillis() - startTime);
startTime = System.currentTimeMillis();
try
{
c = view.getHolder().lockCanvas();
synchronized (view.getHolder())
{
//call onDraw so my surface draws the rectangles
view.onDraw(c);
}
}
finally
{
if (c != null)
{
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
}
Here is my new surface view:
public class TestView extends SurfaceView
{
private long totalElapsedTime = 0;
private Rect[] rectArray;
private int rectCount = 0;
private Paint p;
private GameLoopThread gameLoopThread;
public TestView(Context context)
{
super(context);
rectArray = shapes.getRectArray();
p = new Paint();
p.setColor(Color.BLACK);
p.setStrokeWidth(10)
gameLoopThread = new GameLoopThread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback()
{
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry)
{
try
{
gameLoopThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
}
public void update(long elapsedTime)
{
totalElapsedTime += elapsedTime;
//increase the rectCount every second
rectCount = totalElapsedTime / 1000;
if(rectCount > rectArray.length)
{
rectCount = rectArray.length;
}
}
#Override
public void onDraw(Canvas canvas)
{
canvas.drawARGB(255, 255, 255, 255);
//draw my rectangles here
for(int i = 0; i < rectCount; i++)
{
canvas.drawRect(i,p);
}
}