Drawing shapes one after another on Android's Canvas - java

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);
}
}

Related

Programmatically move ImageView(s) from top to bottom in a loop, starting and ending outside of screen

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

DrawBitmap in Canvas Is Slow

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.

Trying to create a moving sprite on a scrolling background

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);

Why can't I multiply an object?

I'm working with Android (Java), and what I want is to take a class, and make 3 instances out of it, but with some variables set to random.
public class MainActivity extends Activity{
Player ourView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ourView = new Player(this);
setContentView(ourView);
}
protected void onPause() {
super.onPause();
ourView.pause();
}
protected void onResume() {
super.onResume();
ourView.resume();
}
}
The other class...
public class Player extends SurfaceView implements Runnable {
Canvas canvas = new Canvas();
SurfaceHolder ourHolder;
Thread ourThread = null;
boolean isRunning = true;
public Player(Context context) {
super(context);
ourHolder = getHolder();
ourThread = new Thread(this);
ourThread.start();
}
public void pause() {
isRunning = false;
while(true){
try{
ourThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
ourThread = null;
}
public void resume() {
isRunning = true;
}
public void run() {
while(isRunning) {
if(!ourHolder.getSurface().isValid())
continue;
canvas = ourHolder.lockCanvas();
canvas.drawRGB(30, 30, 200);
Enemy[] enemy = new Enemy[3];
for(int i = 0; i<enemy.length; i++){
enemy[i] = new Enemy();
enemy[i].draw();
}
ourHolder.unlockCanvasAndPost(canvas);
}
}
}
And the "Enemy" class:
public class Enemy{
Canvas canvas = new Canvas();
float x = (float) (Math.random()*200);
float y = (float) (Math.random()*200);
public void draw(){
Bitmap bitmap = (Bitmap) BitmapFactory.decodeResource(getContext(), R.drawable.ic_launcher);
canvas.drawBitmap(bitmap, x, y, null);
}
}
I can see that there are 3 bitmaps created, which is cool. But they don't stay still, it's like if the for loop never stops even though the condition is no longer satisfied.
I've asked the question before but I couldn't find any answer. Hope you can help me, thanks.
You are creating a new array of 3 enemies every cycle of the while loop, once the while loop completes execution, the enemy array will leave the scope and be destroyed and a new array will be created. If you want to keep these enemies you will need to store them outside of the scope of the loop.
** EDIT ** - Try this
public class Player extends SurfaceView implements Runnable {
Canvas canvas = new Canvas();
SurfaceHolder ourHolder;
Thread ourThread = null;
boolean isRunning = true;
Enemy[] enemy;
public Player(Context context) {
super(context);
ourHolder = getHolder();
ourThread = new Thread(this);
ourThread.start();
enemy = new Enemy[3];
for(int i = 0; i<enemy.length; i++){
enemy[i] = new Enemy();
}
}
public void pause() {
isRunning = false;
while(true){
try{
ourThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
ourThread = null;
}
public void resume() {
isRunning = true;
}
public void run() {
while(isRunning) {
if(!ourHolder.getSurface().isValid())
continue;
canvas = ourHolder.lockCanvas();
canvas.drawRGB(30, 30, 200);
for(int i = 0; i<enemy.length; i++){
enemy[i].draw();
}
ourHolder.unlockCanvasAndPost(canvas);
}
}
}
exactly as you said, it is like if the for loop never stops.
because that for loop is in while(isrunning) loop that never stops and triggers your for loop over and over.
you need to reorganize your code.

Adding a new enemy every x amount of seconds - Android Game Dev

I have been following along with the tutorials from:
http://www.edu4java.com/en/androidgame/androidgame.html
Where I stand now is that I am able to populate the screen with a variety of sprites and each character will begin at a random position and they will walk in a random direction.
My objective is to add a new sprite every X amount of seconds but I am not entirely sure how to do this.
Currently my GameView is set up like this:
public class GameView extends SurfaceView {
private GameLoopThread gameLoopThread;
private List<Sprite> sprites = new ArrayList<Sprite>();
private List<tempSprite> temps = new ArrayList<tempSprite>();
private long lastClick;
private Bitmap bmpBlood;
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
getHolder().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) {
createSprites();
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
bmpBlood = BitmapFactory.decodeResource(getResources(), R.drawable.image);
}
private void createSprites() {
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
sprites.add(createSprite(R.drawable.image));
}
private Sprite createSprite(int resouce) {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), resouce);
return new Sprite(this, bmp);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
for (int i = temps.size() - 1; i >= 0; i--) {
temps.get(i).onDraw(canvas);
}
for (Sprite sprite : sprites) {
sprite.onDraw(canvas);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (System.currentTimeMillis() - lastClick > 300) {
lastClick = System.currentTimeMillis();
float x = event.getX();
float y = event.getY();
synchronized (getHolder()) {
for (int i = sprites.size() - 1; i >= 0; i--) {
Sprite sprite = sprites.get(i);
if (sprite.isCollision(x, y)) {
sprites.remove(sprite);
temps.add(new tempSprite(temps, this, x, y, bmpBlood));
break;
}
}
}
}
return true;
}
}
`
As you can see, my sprites are being created in createSprites() - however, I am unsure how to create a time to increase the number of sprites.
Could someone please help as I am not sure how to add this into the game loop.
Thanks.
You can use a Thread for creation of new sprites and in a loop, add Thread.sleep(1000) line between invoke method declaration and adding new sprite to sprites ArrayList. Just like:
private void createSprites(int x) {
new Thread(new Runnable() {
public void run() {
for(int i = 0; i < x; i++) {
sprites.add(createSprite(R.drawable.image));
try
{
Thread.sleep(1000); //Waits for 1 second
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}).start();
}
where x is the variable of seconds. So this will create X sprites as a second passes, while your game is still working on background.
Create another thread, timer task, or handler and have that run periodically. Make sure you synchronize your collection properly and only call re draw on the GUI thread.

Categories