I've looked at several examples of people creating tile maps, and I am unable to get the tile position where my mouse is pointed at.
I am using a spritebatch and GameTile[][] to create the map. Keep in mind that the tiles themselves are isometric and not actually a square.
The method renderMap() is where the map is actually is being rendered. createMap() just sets the initial GameTiles for an empty map.
The map is able to be dragged and zoomed in and out using Ortho camera.
Zooming out gives me an issue as well, the tiles seem to be shifted over on click
public class MapEditor implements GameScene {
private GameContext context;
private SpriteBatch batch;
private OrthographicCamera camera;
public static GameTile[][] tiles; //GameTile.WIDTH = 64 & GameTile.HEIGHT =48
public static final int MAP_WIDTH = 20;
public static final int MAP_HEIGHT = 36;
public MapEditor(GameContext context) {
this.context = context;
tiles = new GameTile[MAP_WIDTH][MAP_HEIGHT];
}
#Override
public void create() {
renderer = new ShapeRenderer();
this.batch = new SpriteBatch();
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
public void createMap() {
// Create the sea tiles
for (int x = 0; x < MAP_WIDTH; x++) {
for (int y = 0; y < MAP_HEIGHT; y++) {
if (y < 3 || y > 32) {
if(tiles[x][y] == null) {
tiles[x][y] = safezone;
}
}
else {
if(tiles[x][y] == null) {
tiles[x][y] = cell;
}
}
}
}
}
#Override
public void update(){
// update the camera
camera.update();
}
#Override
public void render() {
batch.setProjectionMatrix(camera.combined);
batch.begin();
Gdx.gl.glViewport(0,0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
renderMap();
batch.end();
}
public int getTileX(float x, float y) {
/*
* getRegionWidth() = TILE_WIDTH_HALF
* getRegionHeight() = TILE_HEIGHT_HALF
* these are the ones being added to worldCoords.x/y
*/
Vector3 worldCoords = camera.unproject(new Vector3(x, y, 0));
return (int)((TILE_WIDTH_HALF * ((-TILE_HEIGHT_HALF + (worldCoords.y + TILE_HEIGHT_HALF)) /
TILE_HEIGHT_HALF) + (worldCoords.x + TILE_WIDTH_HALF)) / TILE_WIDTH_HALF) / 2;
}
public int getTileY(float x, float y) {
/*
* getRegionWidth() = TILE_WIDTH_HALF
* getRegionHeight() = TILE_HEIGHT_HALF
* these are the ones being added to worldCoords.x/y
*/
Vector3 worldCoords = camera.unproject(new Vector3(x, y, 0));
return (int)(((-TILE_HEIGHT_HALF * (TILE_WIDTH_HALF + (worldCoords.x + TILE_WIDTH_HALF)) /
TILE_WIDTH_HALF) + (worldCoords.y + TILE_HEIGHT_HALF)) / TILE_HEIGHT_HALF) / 2;
}
#Override
public boolean handleClick(float x, float y, int button) {
int tileX = getTileX(x,y);
int tileY = getTileY(x,y);
System.out.println("Tile:"+tileX + ","+tileY);
}
private void renderMap() {
for (int i = 0; i < tiles.length; i++) {
for(int j = 0; j < tiles[i].length; j++) {
TextureRegion region = tiles[i][j].getRegion();
int x = (i * GameTile.TILE_WIDTH / 2) - (j * GameTile.TILE_WIDTH / 2) - region.getRegionWidth() / 2;
int y = (i * GameTile.TILE_HEIGHT / 2) + (j * GameTile.TILE_HEIGHT / 2) - region.getRegionHeight() / 2;
if (canDraw(x, y, GameTile.TILE_WIDTH, GameTile.TILE_HEIGHT)) {
batch.draw(region, x, y);
}
}
}
}
Actual tile before doing anything to it;
Actual:
Desired:
Converting Cartesian coordinates to isometric is (sort of) done like this:
float isometricX = cartesianX - cartesianY;
float isometricY = (cartesianX + cartesianY) * 0.5f;
The formula needs to be scaled by the height-to-width ratio of the tiles as well and I think that is where it's going wrong in your code.
Given an unprojected worldMousePosition you can get the coordinates and tile coordinates like this:
float r = (float) TILE_HEIGHT / (float) TILE_WIDTH;
float mapx = (worldMousePosition.x / TILE_HEIGHT + worldMousePosition.y / (TILE_HEIGHT * r)) * r;
float mapy = (worldMousePosition.y / (TILE_HEIGHT * r) - (worldMousePosition.x / TILE_HEIGHT)) * r;
worldPosition = new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
int tileX = (int) worldPosition.x;
int tileY = (int) worldPosition.y;
Full source code for the example above:
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
public class SandboxGame extends Game {
public static final int TILE_NONE = -1;
public static final int MAP_WIDTH = 20;
public static final int MAP_HEIGHT = 36;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 48;
private SpriteBatch batch;
private OrthographicCamera camera;
private BitmapFont font;
private Vector3 unprojectVector = new Vector3();
private Vector2 worldMousePosition = new Vector2();
private Vector2 worldPosition = new Vector2();
private Texture[] textures;
private int[][] tiles = new int[MAP_WIDTH][MAP_HEIGHT];
#Override
public void create() {
batch = new SpriteBatch();
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
font = new BitmapFont(Gdx.files.internal("default.fnt"), Gdx.files.internal("default.png"), false);
textures = new Texture[] {
new Texture(Gdx.files.internal("tile.png"))
};
for(int x = 0; x < MAP_WIDTH; ++x) {
for(int y = 0; y < MAP_HEIGHT; ++y) {
int rnd = MathUtils.random(10);
if (rnd < 1)
tiles[x][y] = TILE_NONE;
else
tiles[x][y] = 0;
}
}
}
#Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
float scrollSpeed = 64;
float zoomSpeed = 2;
float delta = Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.A))
camera.position.x -= delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.D))
camera.position.x += delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.W))
camera.position.y += delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.S))
camera.position.y -= delta * scrollSpeed;
if (Gdx.input.isKeyPressed(Input.Keys.Q))
camera.zoom = Math.min(camera.zoom + zoomSpeed * delta, 8.0f);
if (Gdx.input.isKeyPressed(Input.Keys.E))
camera.zoom = Math.max(camera.zoom - zoomSpeed * delta, 0.5f);
camera.update();
int mx = Gdx.input.getX();
int my = Gdx.input.getY();
camera.unproject(unprojectVector.set(mx, my, 0.0f));
worldMousePosition.set(unprojectVector.x, unprojectVector.y);
float r = (float) TILE_HEIGHT / (float) TILE_WIDTH;
float mapx = (worldMousePosition.x / TILE_HEIGHT + worldMousePosition.y / (TILE_HEIGHT * r)) * r;
float mapy = (worldMousePosition.y / (TILE_HEIGHT * r) - (worldMousePosition.x / TILE_HEIGHT)) * r;
worldPosition = new Vector2(mapx - 0.5f, mapy + 0.5f); // -.5/+.5 because the drawing isn't aligned to the tile, it's aligned to the image
int tileX = (int) worldPosition.x;
int tileY = (int) worldPosition.y;
batch.setProjectionMatrix(camera.combined);
batch.begin();
for (int col = MAP_WIDTH - 1; col >= 0; --col) {
for (int row = MAP_HEIGHT - 1; row >= 0; --row) {
if (tiles[col][row] != TILE_NONE) {
Texture texture = textures[tiles[col][row]];
int x = (col * TILE_WIDTH / 2) - (row * TILE_WIDTH / 2);
int y = (col * TILE_HEIGHT / 2) + (row * TILE_HEIGHT / 2);
batch.setColor(col == tileX && row == tileY ? Color.GRAY : Color.WHITE);
batch.draw(texture, x, y);
}
}
}
if (Gdx.input.isKeyPressed(Input.Keys.SPACE)) {
for (int col = MAP_WIDTH - 1; col >= 0; --col) {
for (int row = MAP_HEIGHT - 1; row >= 0; --row) {
int x = (col * TILE_WIDTH / 2) - (row * TILE_WIDTH / 2);
int y = (col * TILE_HEIGHT / 2) + (row * TILE_HEIGHT / 2);
font.draw(batch, String.format("(%d, %d)", col, row), x, y);
}
}
}
String str = String.format("World position (%.2f, %.2f), Tile (%d, %d)", worldPosition.x, worldPosition.y, (int)worldPosition.x, (int)worldPosition.y);
font.draw(batch, str, worldMousePosition.x, worldMousePosition.y);
batch.end();
}
}
I cant respond to bornander's post, but my tweak would be at
int tileX = (int) Math.Floor(worldPosition.x);
int tileY = (int) Math.Floor(worldPosition.y);
Where simple (int) cast will provide wrong position around 0 with negative values, if there are tiles, while using Math.Floor will work as intended.
I want to animate multiple paths in sequence, for example, I have
Path[] line and Path[] circle
so when I do for example
line[0].moveTo(x, y);
graphPath[j].lineTo(x, y);
I want to animate this part first and when
finish I want to add circle in same x,y postion
circle[0].addCircle(x, y, 10, Path.Direction.CW);
what I have done so far inside onDraw()
#Override
protected void onDraw(Canvas canvas) {
for (int j = 0; j < 2; j++) {
if (dataPoints2[j] != null) {
float x = leftPadding;
float y = height * getDataPoint(0, j) + topPadding;
graphPath[j].moveTo(x, y);
circlePath[j].addCircle(x, y, 10, Path.Direction.CW);
for (int i = 1; i < dataPoints2[j].size(); i++) {
x = width * (((float) i ) / dataPoints2[j].size()) + leftPadding;
y = height * getDataPoint(i, j) + topPadding;
graphPath[j].lineTo(x, y);
circlePath[j].addCircle(x, y, 10, Path.Direction.CW);
}
}
}
PathMeasure measure = new PathMeasure(graphPath[0], false);
length = measure.getLength();
ObjectAnimator animator = ObjectAnimator.ofFloat(Chart.this, "phase", 1.0f, 0.0f);
animator.setDuration(8000);
animator.start();
for (int j = 0; j < graphPath.length; j++) {
canvas.drawPath(graphPath[j], linePaint[j]);
canvas.drawPath(circlePath[j], circlePaint[j]);
}
}
public void setPhase(float phase){
linePaint[0].setPathEffect(createPathEffect(length, phase, 0.0f));
invalidate();
}
private static PathEffect createPathEffect(float pathLength, float phase, float offset)
{
return new DashPathEffect(new float[] { pathLength, pathLength },
Math.max(phase * pathLength, offset));
}
this will draw the circle then animate the line connecting the circle points
what I need is to animate the line and draw circle when the line reaches every x,y points
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 just started working with LibGDX and can't figure out how to hide an image when it collides with an object. In my game some dots come from the top of the screen and meet the dot at the bottom. When they meet the dots should hide that isn't happening.
This is the main Game Class
public class GameScreen implements Screen,InputProcessor {
final AmazingDot game;
//setting the height and width variables
private int height;
private int width;
private static int touchCounter;
//setting the two dots variables
private Texture playerDotImage;
private Texture gameDotImage;
private Texture gameDotImage1;
private Rectangle playerDotRectangle;
private Map<Rectangle,Texture> gameDotMap;
//storing the time of last dot in nano seconds
private long lastDotTime;
public GameScreen(final AmazingDot gam){
this.game = gam;
Gdx.input.setInputProcessor(this);
//getting the height and width of the user's screen
height = Gdx.graphics.getHeight();
width = Gdx.graphics.getWidth();
touchCounter =0;
//loading the images in the variables
playerDotImage = new Texture(Gdx.files.internal("images/dot2.png"));
gameDotImage = new Texture(Gdx.files.internal("images/dot1.png"));
gameDotImage1 = new Texture(Gdx.files.internal("images/dot2.png"));
//placing the player dot in the middle of the screen
playerDotRectangle = new Rectangle();
playerDotRectangle.x = width/ 2 - 64 / 2;
playerDotRectangle.y = 20;
playerDotRectangle.width = 64;
playerDotRectangle.height = 64;
gameDotMap = new ConcurrentHashMap<Rectangle, Texture>();
populateDots();
}
private void populateDots(){
Rectangle dots = new Rectangle();
dots.x = randomLocation();
dots.y = height;
dots.width = 64;
dots.height = 64;
Random rand = new Random();
int n = rand.nextInt(2) + 1;
if(n==1){
gameDotMap.put(dots,gameDotImage1);
}
else{
gameDotMap.put(dots,gameDotImage);
}
lastDotTime = TimeUtils.nanoTime();
}
private int randomLocation(){
int[] locations = new int[3];
locations[0]=0;
locations[1]=width/2-64/2;
locations[2]=width-64;
Random generator = new Random();
int randomIndex = generator.nextInt(locations.length);
return locations[randomIndex];
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
game.batch.begin();
game.batch.draw(playerDotImage,playerDotRectangle.x,playerDotRectangle.y,playerDotRectangle.width,playerDotRectangle.height);
for(Map.Entry<Rectangle,Texture> dots : gameDotMap.entrySet()){
game.batch.draw(dots.getValue(),dots.getKey().x,dots.getKey().y);
}
game.batch.end();
// check if we need to create a new dot
if(TimeUtils.nanoTime() - lastDotTime > 1000000000) populateDots();
for(Rectangle dot : gameDotMap.keySet()){
int gameSpeed = 400;
int xSpeed = calXSpeed(gameSpeed);
dot.y = dot.y - gameSpeed * Gdx.graphics.getDeltaTime();
if(dot.x <width/2-64/2){
dot.x = dot.x + xSpeed*Gdx.graphics.getDeltaTime();
}
else if(dot.x>width/2-64/2){
dot.x = dot.x - xSpeed*Gdx.graphics.getDeltaTime();
}
if(dot.y + 64 < 0) gameDotMap.remove(dot);
if(dot.overlaps(playerDotRectangle)) {
//this is where I am trying to remove the map object on collision
gameDotMap.remove(dot);
}
}
}
private int calXSpeed(int gameSpeed){
int seconds = height/gameSpeed;
int distance = width/2-64/2;
int speed = distance/seconds;
return speed;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
touchCounter++;
if(touchCounter % 2==0){
playerDotImage = new Texture(Gdx.files.internal("images/dot2.png"));
}
else{
playerDotImage = new Texture(Gdx.files.internal("images/dot1.png"));
}
return true;
}
#Override
public void dispose() {
playerDotImage.dispose();
gameDotImage.dispose();
gameDotImage1.dispose();
}
}
EDIT
As you can see in the above image when the moving dot reaches the stationary dot, it should disappear. But here in my code the dot just moves past the stationary dot. I am using rectangles(LibGdx) to detect whether the dots overlap each other or not.
Define you Map as an ArrayMap like so:
private ArrayMap<Rectangle, Texture> gameDotMap;
Then you can rewrite you render method like this:
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
game.batch.begin();
game.batch.draw(playerDotImage, playerDotRectangle.x, playerDotRectangle.y, playerDotRectangle.width, playerDotRectangle.height);
for (int i = 0; i < gameDotMap.size; i++) {
game.batch.draw(gameDotMap.getValueAt(i), gameDotMap.getKeyAt(i).x, gameDotMap.getKeyAt(i).y);
}
game.batch.end();
// check if we need to create a new dot
if (TimeUtils.nanoTime() - lastDotTime > 1000000000) {
populateDots();
}
for (Iterator<ObjectMap.Entry<Rectangle, Texture>> iter = gameDotMap.iterator(); iter.hasNext();) {
ObjectMap.Entry<Rectangle, Texture> entry = iter.next();
Rectangle dot = entry.key;
int gameSpeed = 400;
int xSpeed = calXSpeed(gameSpeed);
dot.y = dot.y - gameSpeed * Gdx.graphics.getDeltaTime();
if (dot.x < width / 2 - 64 / 2) {
dot.x = dot.x + xSpeed * Gdx.graphics.getDeltaTime();
} else if (dot.x > width / 2 - 64 / 2) {
dot.x = dot.x - xSpeed * Gdx.graphics.getDeltaTime();
}
if (dot.y + 64 < 0) {
iter.remove();
}
if (dot.overlaps(playerDotRectangle)) {
//this is where I am trying to remove the map object on collision
iter.remove();
}
}
}
LibGDX has specific types for collections, they are recommended over the JDK's collections.
I'm writing a Raytracer in Java, I've gotten to the point where I can create objects, rays, test for intersections and then colour pixels. I've also got some basic anti aliasing done. My problem is that if a create a sphere, which should be in the centre of the world (i.e 0.0, 0.0, 0.0) and then draw the image, I end up with a picture like this.
When the red circle should be in the middle of the image.
Main method
public static void main(String[] args) {
System.out.println("Rendering...");
long start = System.nanoTime();
// Setting up the size of the image to be rendered
world = new World(1920, 1080, 1.0);
image = new Image("image.png");
sampler = new SimpleSampler(4);
projector = new OrthographicProjector();
// Main loop of program, goes through each pixel in image and assigns a colour value
for (int y = 0; y < world.viewPlane.height; y++) {
for (int x = 0; x < world.viewPlane.width; x++) {
// Render pixel colour
trace(x, y);
}
}
image.saveImage("PNG");
long end = System.nanoTime();
System.out.print("Loop Time = " + ((end - start)/1000000000.0f));
}
Trace method
public static void trace(int x, int y) {
Colour colour = new Colour();
//int colour = RayTracer.world.backgroundColour.toInteger();
for (int col = 0; col < sampler.samples; col++) {
for (int row = 0; row < sampler.samples; row++) {
Point2D point = sampler.sample(row, col, x, y);
Ray ray = projector.createRay(point);
double min = Double.MAX_VALUE;
Colour tempColour = new Colour();
for (int i = 0; i < world.worldObjects.size(); i++) {
double temp = world.worldObjects.get(i).intersect(ray);
if (temp != 0 && temp < min) {
min = temp;
tempColour = world.worldObjects.get(i).colour;
}
}
colour.add(tempColour);
}
}
colour.divide(sampler.samples*sampler.samples);
image.buffer.setRGB(x, y, colour.toInteger());
}
World.java
public class World {
public ViewPlane viewPlane;
public ArrayList<Renderable> worldObjects;
public Colour backgroundColour;
public World(int width, int height, double size) {
viewPlane = new ViewPlane(width, height, size);
backgroundColour = new Colour(0.0f, 0.0f, 0.0f);
worldObjects = new ArrayList<Renderable>();
worldObjects.add(new Sphere(new Point3D(0.0, 0.0, 0.0), 50, new Colour(1.0f, 0.0f, 0.0f)));
//worldObjects.add(new Sphere(new Point3D(-150.0, 0.0, 0.0), 50, new Colour(1.0f, 0.0f, 0.0f)));
//worldObjects.add(new Sphere(new Point3D(0.0, -540.0, 0.0), 50, new Colour(0.0f, 1.0f, 0.0f)));
}
}
SimpleSampler.java
public class SimpleSampler extends Sampler {
public SimpleSampler(int samples) {
this.samples = samples;
}
public Point2D sample(int row, int col, int x, int y) {
Point2D point = new Point2D(
x - RayTracer.world.viewPlane.width / 2 + (col + 0.5) / samples,
y - RayTracer.world.viewPlane.width / 2 + (row + 0.5) / samples);
return point;
}
}
OrthographicProjector.java
public class OrthographicProjector extends Projector{
public Ray createRay(Point2D point) {
Ray ray = new Ray();
ray.origin = new Point3D(
RayTracer.world.viewPlane.size * point.x,
RayTracer.world.viewPlane.size * point.y,
100);
ray.direction = new Vector3D(0.0, 0.0, -1.0);
return ray;
}
}
I have a feeling that somewhere along the way I've mixed an x with a y and this has rotated the image, but I haven't been able to track down the problem. If you would like to see any more of my code I would be happy to show it.
In SimpleSampler.java:
Point2D point = new Point2D(
x - RayTracer.world.viewPlane.width / 2 + (col + 0.5) / samples,
y - RayTracer.world.viewPlane.width / 2 + (row + 0.5) / samples);
You use width for both coordinates. Maybe you should use width and height.