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);
Related
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
Here is my code Below
public class GameController extends SurfaceView implements SurfaceHolder.Callback {
public static int WIDTH =800;
public static int HEIGHT =399;
private GameThread gameThread;
private ArrayList<Background> backgrounds = new ArrayList<>();
private Bitmap[] bgsrc ;
public GameController(Context context) {
super(context);
getHolder().addCallback(this);
setFocusable(true);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
bgsrc = new Bitmap[5];
for (int i=0;i<bgsrc.length;i++){
bgsrc[i] = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.mountain_background_v2), i*800,0,800,399);
}
for (int i=bgsrc.length-1;i>=0;i--){
if (i==0 || i==2) continue;
backgrounds.add(new Background(bgsrc[i],-10+i*2 ));
}
//create GameThread Object and start Thread
gameThread = new GameThread(getHolder(),this);
gameThread.setRunning(true);
gameThread.start();
}
#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{
gameThread.setRunning(false);
gameThread.join();retry = false;
gameThread = null;
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public void update(){
for (Background background: backgrounds){
background.update();
}
}
#Override
public void draw(Canvas canvas){
super.draw(canvas);
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
if (canvas!=null){
final int saveCount = canvas.save();
canvas.scale(scaleFactorX,scaleFactorY);
for (Background background: backgrounds){
background.draw(canvas);
}
canvas.restoreToCount(saveCount);
}
}
}
when I Draw bitmap on Canvas From Arraylist , my drawing very slow where on GameThread Class I initialized FPS(Frame per second) = 60, But After Scaling Canvas its become slow ,I already Tried scaling bitmap before drawing , but still same performance. without array i mean with single Image drawing good, How can i solve this and get exact speed what i want .. plz help me .. Thanks..
The extra calls and calculations in the draw() causes a significant frame rate reduction. I would prefer to do all required scaling of bitmaps once, at the initialization stage, and keep the higher frame rate.
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
I made a line via the drawLine method that starts from the bottom and goes to half the screen size.
So far so good. Now I wanted to "tilt" the line to the left or right when the screen is pressed.
Something like this in the picture:
And when pressed again have it tilt back to the other side and so on.
The problem I have now is that when I tilt the line, its "trail" is visible, because I call the drawLine method multiple times.
This is my code:
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private MainThread thread;
private Paint paint;
private GameLine gameLine;
public MainGamePanel(Context context){
super(context);
getHolder().addCallback(this);
setFocusable(true);
paint = new Paint();
paint.setStrokeWidth(50);
paint.setColor(Color.WHITE);
gameLine = new GameLine(getWidth()/2,getHeight(),getWidth()/2, getHeight()/2,paint);
thread = new MainThread(getHolder(), this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
gameLine = new GameLine(getWidth()/2,getHeight(),getWidth()/2, getHeight()/2,paint);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
...}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
gameLine.setTapped(true);
}
return super.onTouchEvent(event);
}
public void render(Canvas canvas) {
gameLine.drawTheLine(canvas);
}
public void update(){
gameLine.update();
}}
public class MainThread extends Thread {
private boolean running;
private MainGamePanel gamePanel;
private SurfaceHolder surfaceHolder;
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel){
super();
this.gamePanel=gamePanel;
this.surfaceHolder = surfaceHolder;
}
public void setRunning(boolean running){
this.running = running;
}
#Override
public void run(){
Canvas canvas;
while (running){
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
this.gamePanel.update();
this.gamePanel.render(canvas);
}
}finally {
if(canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}}
public class GameLine {
private float startX;
private float startY;
private float stopX;
private float stopY;
private Paint paint;
private boolean tapped = false;
public GameLine(float startX, float startY, float stopX, float stopY, Paint paint) {
Normal Constructor
}
public boolean getTapped(){
return tapped;
}
public void setTapped(boolean tapped){
this.tapped = tapped;
}
public void update(){
if(tapped){
this.stopX -=1;
}
}
public void drawTheLine(Canvas canvas){
canvas.drawLine(startX, startY,stopX, stopY,paint);
}}
I'm looking for any ideas. Did I attempt this the wrong way?
How can I make the trail invisible?
I'm grateful for any help. Thank you.
In order to eliminate the trail, you need to throw away the old canvas (to be more precise the old bitmap) and create a new one.
In your example i would suggest:
#Override
public void run(){
Canvas canvas;
while (running){
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
this.gamePanel.update();
canvas = gameLine.drawTheLine();
}
}finally {
if(canvas != null){
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
and
public Canvas drawTheLine(){
Canvas canvas = new Canvas();
canvas.drawLine(startX, startY,stopX, stopY,paint);
return 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);
}
}