Java – improving the movement of aliens in space
I wrote a mini Android game scene inspired by space invaders and moon patrol Aliens can be photographed horizontally (see above)
Aliens can also be shot vertically (see below)
But adding aliens does not "expand". For example, it will be very difficult for 15 Aliens to move in all possible collisions The original space invaders and the moon patrol solved this problem. Is it possible to develop a strategy different from the one I used? The exact movement of aliens doesn't matter, it's just "interesting"
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",120,50)); backgrounds.add(new Background( this.context,"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)); // Draw the background parallax drawBackground(0); // Draw the rest of the game paint.setTextSize(60); paint.setColor(Color.argb(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",context.getPackageName()); int alienResID2 = context.getResources().getIdentifier("object2_hdpi",context.getPackageName()); int alienResID3 = context.getResources().getIdentifier("object1_hdpi",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,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,bg.height); Rect toRect2 = new Rect(0,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,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; } }
to update
I have begun to encapsulate the logic of 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",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 changed my strategy Now I'm drawing a spaceship that will bomb the lunar rover
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,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,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",context.getPackageName()); int alienResID2 = context.getResources().getIdentifier("right_side_hdpi",context.getPackageName()); int alienResID3 = context.getResources().getIdentifier("right_side_hdpi",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",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(),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; } }
Solution
Your biggest mistake seems to be allocating 4 bitmaps in the drawing routine Allocate these bitmaps in oncreate and simply call the global bitmaps you initialized on oncreate() This will solve your problem You can draw them where they are
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 allocate a bunch of memory objects to call the context and extend drawable and a bunch of other work You may have just used the same alien
int alienResID = context.getResources().getIdentifier("right_side_hdpi",context.getPackageName()); int alienResID2 = context.getResources().getIdentifier("right_side_hdpi",context.getPackageName()); int alienResID3 = context.getResources().getIdentifier("right_side_hdpi",context.getPackageName());
Alien ID has not changed 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 as the last tick. Bitmaps are huge. It's slow to get them from resources, and you're doing every tick
} }
Most other things will scrape away here and there for half an hour, but this may get you to the right FPS baseball field
Don't worry, although you have others
Solve most of your problems by making routines faster It's time to say that you did wrong The typical and correct way to do this is to cycle every 17 ms or so The rest of the time was suspended Some mistakes are obvious
Your biggest mistake seems to be allocating 4 bitmaps in the drawing routine
However, the lottery program will only draw You draw what needs to happen on the canvas, that's it You don't allocate anything that you don't exaggerate. You take out the numbers you have and draw what you've loaded in the memory of that location
You are checking and doing collision detection in the drawing program, and assigning a pile of objects to it when they must be thrown into the woodcutter in a moment
You should not create any objects outside of initialization or in special cases where new aliens exist You should not use "new" anywhere in the drawing program Forever
You're using brute force for collision detection, and you don't Find the beautiful acceleration structure you like and use it For an object, it doesn't matter
Don't call some aliens, although it looks more beautiful, you want the original number of alien boundary box Then, you want to keep them in a structure that allows them to be referenced very quickly (you need to use the frame in less than 17 ms) Invoking a bunch of width commands is not very useful, even if they change size only hit@R_804_2419 @Quantity of These methods allow you to provide some good structures for data, such as an array with sorted hit boxes. You can perform binary search on it and find out whether the moving object hits the object by log (n) updating the structure within log (n) time, or some methods for traversing the axis aligned bounding box tree This is the last thing you need, but as long as you keep it simple, you can't have this However, it's actually just your bitmap that makes most of the deceleration there
There are many other basic problems, such as putting a bounding box in an IF statement instead of making two other rectangles But there are other problems, such as making rectangles! Instead of calling a function with a large ass object, you call the drawing with the actual location Simply call the function with a number
You should have a routine to draw for you according to the position of things It should be able to draw everything needed in less than 17 Ms If it doesn't, you won't reach the 60fps you need to hit Therefore, in this case, reduce some things and do better Does the space background need to be a bitmap? Can you draw a bunch of points for the sky and adjust the graphics accordingly Your drawing program never assigns anything Period If you need to allocate what it should be during init Distribution is the bane of your survival
Your touch updates the location of things AI / Physics check also updates the location of things and checks for collusion Draw draws the content only according to the location and content in memory
Run the update location tag in your own thread You only need to handle concurrent bits that read and write the same data It only needs to synchronize the change data reading of the drawing data, so throw these parts in the synchronization block with the same object (touch position update, check position update, and get the position of the drawing routine itself)