I am trying to draw a grid over an image using the underlaying colours of the image to fill the circles. But some pixels are not getting the correct colour.
In this case the circles are drawn white but they should not be drawn white...
See my code below:
import processing.pdf.*;
PImage img;
color background = color(255);
void setup() {
size(1038, 525);
ellipseMode(CORNER);
noStroke();
//img = loadImage("noise2.jpg");
//img = loadImage("air.png");
img = loadImage("accidents.png");
image(img, 0, 0, width, height);
visualGrid(20, 0.4, false);
}
//void draw() {
// fill(noise.get(mouseX, mouseY));
// rect(width - 100, height - 100, 100, 100);
//}
void visualGrid(int circleSize, float fillSmoothing, boolean debug) {
float halfCircle = circleSize / 2.0;
int amountX = floor(width / circleSize);
int amountY = floor(height / circleSize);
amountY += floor(amountY * 0.1);
float offsetX = (width - (amountX * circleSize + halfCircle)) / 2 + halfCircle;
float offsetY = (height - amountY * circleSize + amountY * circleSize * 0.1) / 2;
for (int x = 0; x < amountX; x++) {
for (int y = 0; y < amountY; y++) {
float styledOffsetX = (y % 2 == 0) ? offsetX - halfCircle : offsetX;
float xpos = x * circleSize + styledOffsetX;
float ypos = circleSize * 0.9 * y + offsetY;
int sectionSize = round(circleSize * fillSmoothing);
float sectionOffset = (circleSize - sectionSize) / 2;
color c = getAvgImgColor(img.get(round(xpos + sectionOffset), round(ypos + sectionOffset), sectionSize, sectionSize));
//fill(noise.get(round(xpos), round(ypos)));
if(debug) {
stroke(255, 0, 255);
strokeWeight(1);
}
fill(c);
ellipse(xpos, ypos, circleSize, circleSize);
if(debug) {
noStroke();
fill(255, 0, 255);
rect(round(xpos + sectionOffset), round(ypos + sectionOffset), sectionSize, sectionSize);
}
}
}
}
color getAvgImgColor(PImage section) {
section.loadPixels();
int avgR = 0, avgG = 0, avgB = 0;
int totalPixels = section.pixels.length;
for (int i = 0; i < totalPixels; i++) {
color pixel = section.pixels[i];
//if(pixel == background) continue;
avgR += red(pixel);
avgG += green(pixel);
avgB += blue(pixel);
}
return color(
round(avgR / totalPixels),
round(avgG / totalPixels),
round(avgB / totalPixels)
);
}
This is what i get when drawing my grid on the image in question:
As you can see in the circled area not all circles should be filled with white... This happens in more places than just the circled are just compare this image with the one below.
I will upload the original image below, so you can use it to debug.
There's a mismatch between the dimensions of your sketch (1038 x 525) and the image you're sampling (2076 x 1048) which might explain the misalignment.
If size(2076, 1048) isn't an option try resizing the image once it's loaded in setup():
...
img = loadImage("accidents.png");
img.resize(width, height);
...
I code a mini Android game scenario inspired by Space Invaders and Moon Patrol. It is possible to shoot an alien horizontally (see above).
It is also possible to shoot an alien vertically (see below).
But adding aliens doesn't "scale", it will be very difficult to add for instance 15 aliens moving with respect to all possible collisions. The original space invaders and moon patrol solved this, is it possible to develop a different strategy than the one I am using? The exact movement of aliens is not important, only that it is "fun".
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
import java.util.List;
public class ParallaxView extends SurfaceView implements Runnable {
List<Background> backgrounds;
private volatile boolean running;
private Thread gameThread = null;
// For drawing
private Paint paint;
private Canvas canvas;
private SurfaceHolder ourHolder;
// Holds a reference to the Activity
Context context;
// Control the fps
long fps = 60;
// Screen resolution
int screenWidth;
int screenHeight;
private void update() {
// Update all the background positions
for (Background bg : backgrounds) {
bg.update(fps);
}
}
ParallaxView(Context context, int screenWidth, int screenHeight) {
super(context);
this.context = context;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// Initialize our drawing objects
ourHolder = getHolder();
paint = new Paint();
// Initialize our array list
backgrounds = new ArrayList<>();
//load the background data into the Background objects and
// place them in our GameObject arraylist
backgrounds.add(new Background(
this.context,
screenWidth,
screenHeight,
"bg", 0, 120, 50));
backgrounds.add(new Background(
this.context,
screenWidth,
screenHeight,
"grass", 70, 110, 200));
// Add more backgrounds here
}
#Override
public void run() {
while (running) {
long startFrameTime = System.currentTimeMillis();
update();
if (j > 2000) {
j = -50;
k = 0;
}
if (o > 2000) {
o = -50;
l = 0;
}
draw();
// Calculate the fps this frame
long timeThisFrame = System.currentTimeMillis() - startFrameTime;
if (timeThisFrame >= 1) {
fps = 1000 / timeThisFrame;
}
}
}
int numberOfshots = 1;
int[] i = new int[200];
int j = 0;
int k = 0;
int l = 0;
int m = 0;
int o = 0;
boolean down = true;
long lastTurn = System.currentTimeMillis();
int xbuggy = 0;
int xbuggy2 = 0;
boolean down2 = true;
long lastTurn2 = System.currentTimeMillis();
long lastTurn3 = System.currentTimeMillis();
boolean jump = false;
boolean shoot = false;
int ind = 0;
private void draw() {
if (ourHolder.getSurface().isValid()) {
//First we lock the area of memory we will be drawing to
canvas = ourHolder.lockCanvas();
if (jump) {
xbuggy = xbuggy + 4;
}
if (shoot) {
xbuggy2 = xbuggy2 + 4;
}
if (System.currentTimeMillis() - lastTurn3 >= 1000) {
// Change direction here
jump = false;
lastTurn3 = System.currentTimeMillis();
xbuggy = 0;
}
//draw a background color
canvas.drawColor(Color.argb(255, 0, 0, 0));
// Draw the background parallax
drawBackground(0);
// Draw the rest of the game
paint.setTextSize(60);
paint.setColor(Color.argb(255, 255, 255, 255));
//canvas.drawText("MOONPATROL3000", 350, screenHeight / 100 * 5, paint);
int resID = context.getResources().getIdentifier("vehicle",
"drawable", context.getPackageName());
int alienResID = context.getResources().getIdentifier("object3_hdpi",
"drawable", context.getPackageName());
int alienResID2 = context.getResources().getIdentifier("object2_hdpi",
"drawable", context.getPackageName());
int alienResID3 = context.getResources().getIdentifier("object1_hdpi",
"drawable", context.getPackageName());
// Load the bitmap using the id
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resID);
Bitmap alienbitmap = BitmapFactory.decodeResource(context.getResources(), alienResID);
Bitmap alienbitmap2 = BitmapFactory.decodeResource(context.getResources(), alienResID2);
Bitmap alienbitmap3 = BitmapFactory.decodeResource(context.getResources(), alienResID3);
//paint.setTextSize(220);
for (int i1 = 0; i1 < numberOfshots; i1++) {
// if horizontal missile hits alien 0
if (java.lang.Math.abs(j - i[i1]) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(k +150+ screenHeight / 100 * 45 - (float) (screenHeight * 0.61)) * 2 < (alienbitmap.getHeight() + 60)) {
//y1[i2] = -random.nextInt(1000); // reset to new vertical position
//score += 1;
//onScoreListener.onScore(score);
Log.d("missile", "missile hit! ");
j=-200;
}
// if vertical missile hits alien 0
if (java.lang.Math.abs(j - 185) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(j + 150 + screenHeight / 100 * 45 - (screenHeight / 100 * 95 - i[i1] - xbuggy2)) * 2 < (alienbitmap.getHeight() + 60)) {
//y1[i2] = -random.nextInt(1000); // reset to new vertical position
//score += 1;
//onScoreListener.onScore(score);
Log.d("missile", "missile hit! ");
j=-200;
}
// if horizontal missile hits alien 1, right now this won't happen
if (java.lang.Math.abs(j - i[i1]) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(k +150+ screenHeight / 100 * 45 - (float) (screenHeight * 0.61)) * 2 < (alienbitmap.getHeight() + 60)) {
//y1[i2] = -random.nextInt(1000); // reset to new vertical position
//score += 1;
//onScoreListener.onScore(score);
Log.d("missile", "missile hit! ");
j=-200;
}
// if vertical missile hits alien 1
if (java.lang.Math.abs(o + 10 - 185) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(l + screenHeight / 100 * 25 - (screenHeight / 100 * 95 - i[i1] - xbuggy2)) * 2 < (alienbitmap.getHeight() + 60)) {
//y1[i2] = -random.nextInt(1000); // reset to new vertical position
//score += 1;
//onScoreListener.onScore(score);
Log.d("missile", "missile hit! ");
o=-200;
}
canvas.drawText("o", i[i1], (float) (screenHeight * 0.61), paint);
canvas.drawText("o", 185, screenHeight / 100 * 95 - i[i1] - xbuggy2, paint);
if (i1 == numberOfshots - 1 && i[i1] > screenWidth) {
if (numberOfshots > 0) numberOfshots--;
if (ind > 0) ind--;
}
}
if (System.currentTimeMillis() - lastTurn >= 2000) {
// Change direction here
down = !down;
lastTurn = System.currentTimeMillis();
}
if (System.currentTimeMillis() - lastTurn2 >= 7000) {
// Change direction here
down2 = !down2;
lastTurn2 = System.currentTimeMillis();
}
canvas.drawBitmap(alienbitmap, j, k +150+ screenHeight / 100 * 45, paint);
canvas.drawBitmap(alienbitmap2, o + 10, l + screenHeight / 100 * 25, paint);
//canvas.drawBitmap(alienbitmap3, j+20, k+screenHeight / 100 * 5, paint);
drawBackground(1);
canvas.drawBitmap(bitmap, 50, (float) (screenHeight * 0.5) - xbuggy, paint);
// Draw the foreground parallax
for (int n = 0; n < numberOfshots; n++)
i[n] = i[n] + 20;
j = j + 10;
o = o + 7;
if (!down)
k=k+2;
else
k=k-2;
if (!down2)
l++;
else
l--;
// Unlock and draw the scene
ourHolder.unlockCanvasAndPost(canvas);
}
}
// Clean up our thread if the game is stopped
public void pause() {
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// Error
}
}
// Make a new thread and start it
// Execution moves to our run method
public void resume() {
running = true;
gameThread = new Thread(this);
gameThread.start();
}
private void drawBackground(int position) {
// Make a copy of the relevant background
Background bg = backgrounds.get(position);
// define what portion of images to capture and
// what coordinates of screen to draw them at
// For the regular bitmap
Rect fromRect1 = new Rect(0, 0, bg.width - bg.xClip, bg.height);
Rect toRect1 = new Rect(bg.xClip, bg.startY, bg.width, bg.endY);
// For the reversed background
Rect fromRect2 = new Rect(bg.width - bg.xClip, 0, bg.width, bg.height);
Rect toRect2 = new Rect(0, bg.startY, bg.xClip, bg.endY);
//draw the two background bitmaps
if (!bg.reversedFirst) {
canvas.drawBitmap(bg.bitmap, fromRect1, toRect1, paint);
canvas.drawBitmap(bg.bitmapReversed, fromRect2, toRect2, paint);
} else {
canvas.drawBitmap(bg.bitmap, fromRect2, toRect2, paint);
canvas.drawBitmap(bg.bitmapReversed, fromRect1, toRect1, paint);
}
}
// Because we call this from onTouchEvent, this code will be executed for both
// normal touch events and for when the system calls this using Accessibility
#Override
public boolean performClick() {
super.performClick();
launchMissile();
return true;
}
private void launchMissile() {
i[ind] = 350;
ind++;
xbuggy2 = 0;
shoot = true;
}
// event listener for when the user touches the screen
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean gameOver = false;
//if (paused) {
// paused = false;
//}
int action = MotionEventCompat.getActionMasked(event);
int coordX = (int) event.getX();
int coordY = (int) event.getY();
Log.d("coordY", "coordY " + coordY);
if (coordX < 220 && xbuggy == 0 && action == MotionEvent.ACTION_MOVE) {
jump = true;
shoot = false;
lastTurn3 = System.currentTimeMillis();
return true; // do nothing
}
if (coordX > 219 && action == MotionEvent.ACTION_DOWN) {
numberOfshots++;
performClick();
return true;
}
return true;
}
}
Update
I have started to encapsulate the logic for the aliens according to the following.
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class Alien {
public Alien(){}
public Alien(Context context, String name) {
setAlienResID(context.getResources().getIdentifier("object3_hdpi",
"drawable", context.getPackageName()));
setAlienbitmap(BitmapFactory.decodeResource(context.getResources(), this.getAlienResID()));
}
public int getAlienResID() {
return alienResID;
}
public void setAlienResID(int alienResID) {
this.alienResID = alienResID;
}
public Bitmap getAlienbitmap() {
return alienbitmap;
}
public void setAlienbitmap(Bitmap alienbitmap) {
this.alienbitmap = alienbitmap;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
int alienResID;
Bitmap alienbitmap;
int width;
int height;
}
public class AttackingAlien extends Alien {
public AttackingAlien(Context context, String name) {
super(context, name);
}
}
Update 2
I have changed the strategy. Now I am drawing a spaceship which is going to bomb the moon buggy.
The relevant code is
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.MotionEventCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
import java.util.List;
public class ParallaxView extends SurfaceView implements Runnable {
List<Background> backgrounds;
private volatile boolean running;
private Thread gameThread = null;
// For drawing
private Paint paint;
private Canvas canvas;
private SurfaceHolder ourHolder;
// Holds a reference to the Activity
Context context;
// Control the fps
long fps = 60;
// Screen resolution
int screenWidth;
int screenHeight;
private void update() {
// Update all the background positions
for (Background bg : backgrounds) {
bg.update(fps);
}
}
ParallaxView(Context context, int screenWidth, int screenHeight) {
super(context);
this.context = context;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// Initialize our drawing objects
ourHolder = getHolder();
paint = new Paint();
// Initialize our array list
backgrounds = new ArrayList<>();
//load the background data into the Background objects and
// place them in our GameObject arraylist
backgrounds.add(new Background(
this.context,
screenWidth,
screenHeight,
"bg", 0, 120, 50));
backgrounds.add(new Background(
this.context,
screenWidth,
screenHeight,
"grass", 70, 110, 200));
// Add more backgrounds here
}
#Override
public void run() {
while (running) {
long startFrameTime = System.currentTimeMillis();
update();
if (j > 2000) {
j = -50;
k = 0;
}
if (o > 2000) {
o = -50;
l = 0;
}
draw();
// Calculate the fps this frame
long timeThisFrame = System.currentTimeMillis() - startFrameTime;
if (timeThisFrame >= 1) {
fps = 1000 / timeThisFrame;
}
}
}
int numberOfshots = 1;
int[] i = new int[200];
int j = 0;
int k = 0;
int l = 0;
int m = 0;
int o = 0;
boolean down = true;
long lastTurn = System.currentTimeMillis();
int xbuggy = 0;
int xbuggy2 = 0;
boolean down2 = true;
long lastTurn2 = System.currentTimeMillis();
long lastTurn3 = System.currentTimeMillis();
long lastTurn4 = System.currentTimeMillis();
boolean jump = false;
boolean shoot = false;
int ind = 0;
int numberOfAlienshots = 1;
int missileOffSetY = 0;
private void draw() {
if (ourHolder.getSurface().isValid()) {
//First we lock the area of memory we will be drawing to
canvas = ourHolder.lockCanvas();
if (jump) {
xbuggy = xbuggy + 4;
}
if (shoot) {
xbuggy2 = xbuggy2 + 4;
}
if (System.currentTimeMillis() - lastTurn4 >= 2000) {
// Change direction here
//jump = false;
lastTurn4 = System.currentTimeMillis();
missileOffSetY = 0;
}
if (System.currentTimeMillis() - lastTurn3 >= 1000) {
// Change direction here
jump = false;
lastTurn3 = System.currentTimeMillis();
xbuggy = 0;
}
//draw a background color
canvas.drawColor(Color.argb(255, 0, 0, 0));
// Draw the background parallax
drawBackground(0);
// Draw the rest of the game
paint.setTextSize(60);
paint.setColor(Color.argb(255, 255, 255, 255));
//canvas.drawText("MOONPATROL3000", 350, screenHeight / 100 * 5, paint);
int resID = context.getResources().getIdentifier("vehicle",
"drawable", context.getPackageName());
Alien alien1 = new AttackingAlien(context, "right_side_hdpi");
Alien alien2 = new AttackingAlien(context, "object2_hdpi");
Alien alien3 = new AttackingAlien(context, "object1_hdpi");
int alienResID = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
int alienResID2 = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
int alienResID3 = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
// Load the bitmap using the id
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resID);
Bitmap alienbitmap = BitmapFactory.decodeResource(context.getResources(), alienResID);
Bitmap alienbitmap2 = BitmapFactory.decodeResource(context.getResources(), alienResID2);
Bitmap alienbitmap3 = BitmapFactory.decodeResource(context.getResources(), alienResID3);
//paint.setTextSize(220);
//for (int i1 = 0; i1 < numberOfAlienshots; i1++) {
if (missileOffSetY < 300) {
canvas.drawText("|", o + 10 + alienbitmap2.getWidth() / 2, l + screenHeight / 100 * 25 + 75 + missileOffSetY, paint);
missileOffSetY = missileOffSetY + 10;
}
for (int i1 = 0; i1 < numberOfshots; i1++) {
// if horizontal missile hits alien 0
if (java.lang.Math.abs(j - i[i1]) * 2 < (alien1.getWidth() + 60) && java.lang.Math.abs(k +150+ screenHeight / 100 * 45 - (float) (screenHeight * 0.61)) * 2 < (alien1.getHeight() + 60)) {
//y1[i2] = -random.nextInt(1000); // reset to new vertical position
//score += 1;
//onScoreListener.onScore(score);
Log.d("missile", "missile hit! ");
j=-200;
}
// if vertical missile hits alien 0
if (java.lang.Math.abs(j - 185) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(j + 150 + screenHeight / 100 * 45 - (screenHeight / 100 * 95 - i[i1] - xbuggy2)) * 2 < (alienbitmap.getHeight() + 60)) {
j=-200;
}
// if horizontal missile hits alien 1, right now this won't happen
if (java.lang.Math.abs(j - i[i1]) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(k +150+ screenHeight / 100 * 45 - (float) (screenHeight * 0.61)) * 2 < (alienbitmap.getHeight() + 60)) {
j=-200;
}
// if vertical missile hits alien 1
if (java.lang.Math.abs(o + 10 - 185) * 2 < (alienbitmap.getWidth() + 60) && java.lang.Math.abs(l + screenHeight / 100 * 25 - (screenHeight / 100 * 95 - i[i1] - xbuggy2)) * 2 < (alienbitmap.getHeight() + 60)) {
o=-200;
}
canvas.drawText("o", i[i1], (float) (screenHeight * 0.61), paint);
canvas.drawText("o", 185, screenHeight / 100 * 95 - i[i1] - xbuggy2, paint);
if (i1 == numberOfshots - 1 && i[i1] > screenWidth) {
if (numberOfshots > 0) numberOfshots--;
if (ind > 0) ind--;
}
}
if (System.currentTimeMillis() - lastTurn >= 2000) {
// Change direction here
down = !down;
lastTurn = System.currentTimeMillis();
}
if (System.currentTimeMillis() - lastTurn2 >= 7000) {
// Change direction here
down2 = !down2;
lastTurn2 = System.currentTimeMillis();
}
// canvas.drawBitmap(alien1.getAlienbitmap(), j, k +150+ screenHeight / 100 * 45, paint);
canvas.drawBitmap(alienbitmap2, o + 10, l + screenHeight / 100 * 25, paint);
//canvas.drawBitmap(alienbitmap3, j+20, k+screenHeight / 100 * 5, paint);
drawBackground(1);
canvas.drawBitmap(bitmap, 50, (float) (screenHeight * 0.5) - xbuggy, paint);
// Draw the foreground parallax
for (int n = 0; n < numberOfshots; n++)
i[n] = i[n] + 20;
j = j + 10;
o = o + 7;
if (!down)
k=k+2;
else
k=k-2;
if (!down2)
l++;
else
l--;
// Unlock and draw the scene
ourHolder.unlockCanvasAndPost(canvas);
}
}
// Clean up our thread if the game is stopped
public void pause() {
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// Error
}
}
// Make a new thread and start it
// Execution moves to our run method
public void resume() {
running = true;
gameThread = new Thread(this);
gameThread.start();
}
private void drawBackground(int position) {
// Make a copy of the relevant background
Background bg = backgrounds.get(position);
// define what portion of images to capture and
// what coordinates of screen to draw them at
// For the regular bitmap
Rect fromRect1 = new Rect(0, 0, bg.width - bg.xClip, bg.height);
Rect toRect1 = new Rect(bg.xClip, bg.startY, bg.width, bg.endY);
// For the reversed background
Rect fromRect2 = new Rect(bg.width - bg.xClip, 0, bg.width, bg.height);
Rect toRect2 = new Rect(0, bg.startY, bg.xClip, bg.endY);
//draw the two background bitmaps
if (!bg.reversedFirst) {
canvas.drawBitmap(bg.bitmap, fromRect1, toRect1, paint);
canvas.drawBitmap(bg.bitmapReversed, fromRect2, toRect2, paint);
} else {
canvas.drawBitmap(bg.bitmap, fromRect2, toRect2, paint);
canvas.drawBitmap(bg.bitmapReversed, fromRect1, toRect1, paint);
}
}
// Because we call this from onTouchEvent, this code will be executed for both
// normal touch events and for when the system calls this using Accessibility
#Override
public boolean performClick() {
super.performClick();
launchMissile();
return true;
}
private void launchMissile() {
i[ind] = 350; // what does it do?
ind++;
xbuggy2 = 0;
shoot = true;
}
// event listener for when the user touches the screen
#Override
public boolean onTouchEvent(MotionEvent event) {
boolean gameOver = false;
//if (paused) {
// paused = false;
//}
int action = MotionEventCompat.getActionMasked(event);
int coordX = (int) event.getX();
int coordY = (int) event.getY();
Log.d("coordY", "coordY " + coordY);
if (coordX < 220 && xbuggy == 0 && action == MotionEvent.ACTION_MOVE) {
jump = true;
shoot = false;
lastTurn3 = System.currentTimeMillis();
return true; // do nothing
}
if (coordX > 219 && action == MotionEvent.ACTION_DOWN) {
numberOfshots++;
performClick();
return true;
}
return true;
}
}
Your biggest error seems to be allocating 4 bitmaps in the draw routine. Allocate those bitmaps in the onCreate and simply call the global bitmaps, that you initialized onCreate(). That will fix your problem. You can draw them in their locations.
private void draw() {
Alien alien1 = new AttackingAlien(context, "right_side_hdpi");
Alien alien2 = new AttackingAlien(context, "object2_hdpi");
Alien alien3 = new AttackingAlien(context, "object1_hdpi");
You allocated a bunch of memory objects invoking the context and expanding the drawable and a bunch of other work. You could have just used the same aliens from last tick.
int alienResID = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
int alienResID2 = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
int alienResID3 = context.getResources().getIdentifier("right_side_hdpi",
"drawable", context.getPackageName());
The aliens IDs didn't change from the previous tick.
// Load the bitmap using the id
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resID);
Bitmap alienbitmap = BitmapFactory.decodeResource(context.getResources(), alienResID);
Bitmap alienbitmap2 = BitmapFactory.decodeResource(context.getResources(), alienResID2);
Bitmap alienbitmap3 = BitmapFactory.decodeResource(context.getResources(), alienResID3);
These are the same bitmaps from last tick and bitmaps are giant and getting them from resources is slow, and you are doing this EVERY tick.
}
}
Most of the other stuff is going to shave off half a ms here and there, but this here is going to maybe get you up to the right FPS ballpark.
Don't worry though you have others.
Much of your problem is solved by making the routines faster. This is a good time to say, you're doing it wrong. The typical and correct way to do this is to have a loop ticks every 17ms or so. And for the rest of the time is paused. Some errors are glaring.
Your biggest error seems to be allocating 4 bitmaps in the draw routine.
But, the draw routine only draws. You draw the stuff that needs to happen on your canvas and that's it. You don't allocate anything you don't inflate anything, you take the numbers you have for where stuff is, and draw the stuff you already have loaded in the memory in that location.
You are ticking and doing the collision detection, in the draw routine, and allocating a bunch of objects for this when they have to be thrown in the woodchipper in a split second anyway.
You should not create any objects outside your initialization or in those special cases when a new alien exists. You shouldn't ever use a "new" anywhere in a draw routine. Ever.
You are using brute force for collision detection, eventually don't. Find a nice acceleration structure you like and use that. For 1 object it won't matter though.
Don't call some alien class, while it looks prettier, you want raw numbers of the bounding boxes of the aliens. You want then to keep them in some sort of structure that allows referencing them very quickly (You need the frame in less than 17ms). Calling a bunch of width commands isn't really that helpful, even if they changed size just change the number for the hitbox. These methods allow you some nice structures for the data like having a sorted array of hitboxes you can binary search into them and find whether the moved object hits the object in a log(n) time with log(n) updates to the structure, or some various methods for traversing an Axis Aligned Bound Box Tree. This is something you eventually will need but it might work without this so long as you're keeping it simple. Though, truthfully it's just your bitmaps there doing most of the slowing.
There's a lot of other basic issues like putting the bounding box inside the if statement rather than making two additional rectangles. But there's other issues like making rectangles at all! You call the draw with the actual location rather than some allocate a big ass couple objects to call a function with. Just call the function with the numbers.
You should have a routine that does the drawing for you based on the locations of the stuff. It should be able to draw everything it needs to draw in less then 17ms. If it can't you won't ever hit the 60fps you need to hit. So in that case draw less stuff and do a better job of it. Does that space background need to be a bitmap? Could you just draw a bunch of points for the sky, and adjust the graphics accordingly. Your draw routine never allocates anything. Period. If you need to allocate something it should have been during the init. Allocations are the bane of your existence.
Your touches update the position of the stuff. The AI/Physics tick also updates the positions of things and checks for collusion. The draw only draws the stuff based on the positions and stuff in memory.
Run the update location tick in its own thread. You only need to cope with the concurrency bits where you would be reading and writing the same data. It just needs to synchronize the changing data reading of the data for the draw, so toss those sections (position update in touch, position update in tick, and getting the position for the draw routine itself) in a synchronized block with the same object.
I wanted to draw a grid of quads with an animated colour property but ended up with code that is too slow just to create a basic structure of an image, not to mention to animate it. Also, the image is drawing, not as it finished to compute, but in the process by parts. How can I fix it? Here's onDraw(Canvas canvas) method:
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
final int height = canvas.getHeight();
final int width = canvas.getWidth();
Bitmap.Config conf = Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(width, height, conf);
new Thread(() -> {
int cells_amount = 0;
float cells_vertical = 0;
float cells_horizontal = 0;
cells_vertical = (float) height / CELL_SIZE;
int y = 0;
if (cells_vertical % 1 > 0) {
y = Math.round(CELL_SIZE * -(cells_vertical % 1));
cells_vertical = (int) (cells_vertical + 1);
}
cells_horizontal = (float) width / CELL_SIZE;
int x = 0;
if (cells_horizontal % 1 > 0) {
x = Math.round(CELL_SIZE * -(cells_horizontal % 1));
cells_horizontal = (int) (cells_horizontal + 1);
}
cells_amount = (int) (cells_horizontal * cells_vertical);
Canvas c = new Canvas(bitmap);
c.drawColor(Color.BLACK);
int x_preserved = x;
Paint textPaint = new Paint();
textPaint.setColor(Color.parseColor("#EEEEEE"));
for (int i = 0; i < cells_amount; i++) {
Rect rect = new Rect(x, y, x + CELL_SIZE, y + CELL_SIZE);
Paint framePaint = new Paint();
framePaint.setColor(Color.parseColor("#1F1F1F"));
framePaint.setStyle(Paint.Style.STROKE);
framePaint.setStrokeWidth(1);
c.drawRect(rect, framePaint);
if (random.nextBoolean()) {
String output = String.format(Locale.getDefault(), "%d", "+");
int cx = (int) (x + (CELL_SIZE / 2) - (textPaint.measureText(output) / 2));
int cy = (int) ((y + (CELL_SIZE / 2)) - ((textPaint.descent() + textPaint.ascent()) / 2));
c.drawText(output, cx, cy, textPaint);
}
if (i % cells_horizontal == 0 && i >= cells_horizontal) {
y += CELL_SIZE;
x = x_preserved;
} else {
x += CELL_SIZE;
}
}
}).start();
canvas.drawBitmap(bitmap, 0, 0, null);
}
I have struggled with this problem for several days now, and I just can't solve the problem myself.
I have a Minecraft 2D sort of game. The Blocks are divided into Chunks of 64 * 64 Blocks.
I have this draw function:
#Override
protected void gameDraw(Graphics2D g) {
g.drawImage(Texture.getImage("background"), 0, 0, 800, 600, 0, 0, 800, 600, null);
int pixelXMin = (int) (screenX);
int pixelYMin = (int) (screenY);
int pixelXMax = (int) (canvas.getWidth() + screenX);
int pixelYMax = (int) (canvas.getHeight() + screenY);
int blockXMin = pixelXMin / Block.BLOCK_PIXEL_WIDTH;
int blockYMin = pixelYMin / Block.BLOCK_PIXEL_HEIGHT;
int blockXMax = pixelXMax / Block.BLOCK_PIXEL_WIDTH;
int blockYMax = pixelYMax / Block.BLOCK_PIXEL_HEIGHT;
int diffX = pixelXMin % Block.BLOCK_PIXEL_WIDTH;
int diffY = pixelYMin % Block.BLOCK_PIXEL_HEIGHT;
for (int bx = blockXMin; bx < blockXMax; bx++) {
for (int by = blockYMin; by < blockYMax; by++) {
int chunkNum = bx / Chunk.COLUMNS_IN_CHUNK;
int blockX = bx % Chunk.COLUMNS_IN_CHUNK;
int blockY = by % Chunk.COLUMNS_IN_CHUNK;
Chunk chunk = level.loadChunk(chunkNum);
Block block = chunk.getBlock(blockX, blockY);
if (block == null) { continue; }
BlockData blockData = block.getBlockData();
if (blockData.getType().getTextureName().equals("")) { continue; }
int blockPosX = canvas.getWidth() - (bx * Block.BLOCK_PIXEL_WIDTH + diffX);
int blockPosY = canvas.getHeight() - (by * Block.BLOCK_PIXEL_HEIGHT + diffY);
BufferedImage image = Texture.getImage(blockData.getType().getName());
g.drawImage(image, blockPosX, blockPosY, Block.BLOCK_PIXEL_WIDTH, Block.BLOCK_PIXEL_HEIGHT, null);
}
}
}
Here i tried to calculate the maximum and minimum number of pixels on the screen with the variables screenX and screenY who serve as "camera position". Then I loop through every block that is within these pixels.
The rest is sort of self explanatory.
Now for the problem:
As you can see does the game draw nicely, but when you change the values of screenX and screenY, the "box" of blocks move realtive to the screen, but the blocks stay at the same location.
How can I solve this? Thank you!
Link to dropbox download for .jar file and full sourcecode. gameDraw() is in CubeExplorer.java
https://www.dropbox.com/sh/madienlwmhjt2wh/AADA83aKa00j5UFowhotfmmaa?dl=0
SOLUTION
Here is my solution:
#Override
protected void gameDraw(Graphics2D g) {
g.drawImage(Texture.getImage("background"), 0, 0, 800, 600, 0, 0, 800, 600, null);
int x = (int) (screenX / Block.BLOCK_PIXEL_WIDTH);
int y = (int) (screenY / Block.BLOCK_PIXEL_HEIGHT);
int blockXMin = x;
int blockYMin = y;
int blockXMax = x + canvas.getWidth() / Block.BLOCK_PIXEL_WIDTH + 2;
int blockYMax = y + canvas.getWidth() / Block.BLOCK_PIXEL_WIDTH + 2;
System.out.println(x + "," + y + " " + blockXMax + "," + blockYMax);
for(int blockX = blockXMin; blockX < blockXMax; blockX++) {
for(int blockY = blockYMin; blockY < blockYMax; blockY++) {
int chunkX = blockX / Chunk.COLUMNS_IN_CHUNK;
Chunk chunk = level.loadChunk(chunkX);
int blockChunkPosX = blockX % Chunk.COLUMNS_IN_CHUNK;
int blockChunkPosY = blockY % Chunk.ROWS_IN_CHUNK;
Block block = chunk.getBlock(blockChunkPosX, blockChunkPosY);
if (block == null) { continue; }
BlockData blockData = block.getBlockData();
if (blockData.getType().getTextureName().equals("")) { continue; }
int blockScreenLocX = (int) ((int) + blockX * Block.BLOCK_PIXEL_WIDTH - screenX);
int blockScreenLocY = 600 - (int) ((int) + blockY * Block.BLOCK_PIXEL_HEIGHT - screenY);
BufferedImage image = Texture.getImage(blockData.getType().getName());
g.drawImage(image, blockScreenLocX, blockScreenLocY, Block.BLOCK_PIXEL_WIDTH, Block.BLOCK_PIXEL_HEIGHT, null);
}
}
}
I have created a Tetris game on android and I don't think the animation is as good as it could be. When blocks are moving fast (100ms between the ticks), it's a pretty obvious flicker in the animation, as if there's a very short moment where you can see traces of the previous position of the moving block.
Here is the animation loop, implemented in a separate thread as an extension of SurfaceView:
public void run()
{
while(running)
{
if(surfaceHolder.getSurface().isValid())
{
sleep(1);
canvas = surfaceHolder.lockCanvas();
canvas.drawColor(Color.BLACK); //reset canvas
int width = canvas.getWidth();
int squareSpaceW = (width/4)*3;
int squareSize = squareSpaceW/10;
squareSpaceW = squareSize*10;
int squareSpaceH = squareSpaceW*2;
//Draw lines on the play field
canvas.drawLine(squareSpaceW, 0, squareSpaceW, squareSpaceH, whitePaint);
canvas.drawLine(0, squareSpaceH, squareSpaceW, squareSpaceH, whitePaint);
//draw next piece
int nxtPieceSize = board.getNextPiece().getBlocks().size() -1;
while(nxtPieceSize >= 0)
{
rectPaint.setColor(Color.parseColor(board.getNextPiece().getBlocks().get(nxtPieceSize).getColor()));
int x = board.getNextPiece().getCoordList().get(nxtPieceSize).getX();
int y = board.getNextPiece().getCoordList().get(nxtPieceSize).getY();
nxtPieceSize--;
rect.set(x*(squareSize/2) + (squareSpaceW/17)*16, y*(squareSize/2) + (squareSize/2), x*(squareSize/2) + (squareSize/2) + (squareSpaceW/17)*16, y*(squareSize/2)+ (squareSize/2) + (squareSize/2));
canvas.drawRect(rect, rectPaint);
}
//draw text
canvas.drawText("Score: " + Integer.toString(board.getScore()), width - (width/8), (width/8) + (squareSize), whitePaint);
canvas.drawText("Level: " + Integer.toString(board.getLevel()), width - (width/8), (width/8) + (squareSize*2), whitePaint);
//draw the blocks
for(int x = 0; x < board.getWidth(); x++)
{
for(int y = 0; y < board.getHeight(); y++)
{
tempBlock = board.getBlock(x, y);
if(tempBlock != null)
{
rectPaint.setColor(Color.parseColor(tempBlock.getColor()));
rect.set(x*squareSize, y*squareSize, x*squareSize + squareSize, y*squareSize+ squareSize);
canvas.drawRect(rect, rectPaint);
}
}
}
//draw pause icon
rect.set(squareSize/2,squareSize/2, (squareSize/4)*3, squareSize + squareSize/2);
canvas.drawRect(rect, whitePaint);
rect.set(squareSize ,squareSize/2, squareSize + squareSize/4, squareSize + squareSize/2);
canvas.drawRect(rect, whitePaint);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Hope I have given the information you need. Sorry if the code is a bit messy...