Android Game example, Direction of Missile
적기가 아군기를 향해 미사일을 발사하면 발사 방향으로 미사일을 회전하고 아군기를 향해 날아간다.
벡터와 삼각함수를 사용하는 예
GameState.java (float형을 지원하기위해 int를 float로 변경한 부분이 있음)
package game.framework;
import java.util.ArrayList;
import java.util.Random;
import android.graphics.*;
import android.util.*;
import android.view.KeyEvent;
import android.view.MotionEvent;
public class GameState implements IState {
Background background;
BackgroundClouds clouds;
Airplane airplane;
GameKeyPad keyPad;
Random rd;
ArrayList<EnemyPlane> enemyList;
long enemyCreationTime;
ExplosionPool expPool;
public GameState() {
keyPad = new GameKeyPad();
airplane = new Airplane();
airplane.init();
airplane.inuse = true;
background = new Background();
clouds = new BackgroundClouds();
expPool = ExplosionPool.getInstance();
rd = new Random();
enemyList = AppManager.getInstance().enemyList;
enemyCreationTime = System.currentTimeMillis()-3000;
}
@Override
public void init() { }
@Override
public void destroy() { }
@Override
public void update() {
createEnemy();
background.update();
clouds.update();
airplane.update();
EnemyMisPool.getInstance().update();
for(int i=0;i<AppManager.getInstance().enemyList.size();i++) {
if(!(AppManager.getInstance().enemyList.get(i).inuse)) continue;
AppManager.getInstance().enemyList.get(i).update();
}
collisionCheck();
collisionCheck2();
ExplosionPool.getInstance().update();
}
@Override
public void render(Canvas canvas) {
background.draw(canvas);
clouds.draw(canvas);
airplane.draw(canvas);
EnemyMisPool.getInstance().draw(canvas);
for(int i=0;i<AppManager.getInstance().enemyList.size();i++) {
if(!AppManager.getInstance().enemyList.get(i).inuse) continue;
AppManager.getInstance().enemyList.get(i).draw(canvas);
}
ExplosionPool.getInstance().draw(canvas);
keyPad.draw(canvas);
}
/* 아군의 미사일과 적기의 충돌검사 */
public void collisionCheck() {
ArrayList<EnemyPlane> enemyList = AppManager.getInstance().enemyList;
ArrayList<Missile> misList = airplane.misList;
int enemyCnt = enemyList.size();
for(int i=0;i<misList.size();i++) {
if(!misList.get(i).inuse) continue;
for(int k=0;k<enemyCnt;k++) {
if(!enemyList.get(k).inuse) continue;
if(misList.get(i).rect.intersect(enemyList.get(k).rect)) {
// 충돌탐지/충돌위치에 폭발애니 출력/적기, 미사일 안보이게 처리
SpriteAnimation explosion = expPool.get();
explosion.setPosition((float)(misList.get(i).x-explosion.spriteWidth/2),
(float)(misList.get(i).y-explosion.spriteHeight/2-20));
misList.get(i).inuse = false;
enemyList.get(k).inuse = false;
//isCollided = true;
break;
}
}
}
}
/* 적기의 미사일과 아군기의 충돌검사 */
public void collisionCheck2() {
if(!airplane.inuse)return;
ArrayList<EnemyPlane> enemyList = AppManager.getInstance().enemyList;
for(int i=0;i<enemyList.size();i++) {
if(!enemyList.get(i).inuse) continue;
ArrayList<Missile> misList = EnemyMisPool.getInstance().pool;
for(int k=0;k<misList.size();k++) {
if(!misList.get(k).inuse) continue;
if(misList.get(k).rect.intersect(airplane.rect)) {
Log.i("충돌검사", "아군기 피격");
/* 충돌탐지/충돌위치에 폭발애니 출력/비행기, 미사일 안보이게 처리 */
SpriteAnimation explosion = expPool.get();
explosion.setPosition(airplane.x, airplane.y);
misList.get(k).inuse = false;
airplane.inuse = false;
//isCollided = true;
break;
}
}
}
}
public void createEnemy(){
long currentTime = System.currentTimeMillis();
if((currentTime-enemyCreationTime)<3000) return;
enemyCreationTime = currentTime;
if(rd.nextInt(2)==0) {
int len = enemyList.size();
EnemyPlane ep = null;
for(int i=0;i<len;i++){
if(!enemyList.get(i).inuse){
ep = enemyList.get(i);
Log.i("createEnemy", "재활용");
break;
}
}
if(ep==null){
ep = new EnemyPlane();
Log.i("createEnemy", "적기 새로생성");
}
ep.setPosition(0, 0);
ep.inuse = true;
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return keyPad.onTouchEvent(event);
}
}
Airplane.java
package game.framework;
import java.util.*;
import android.graphics.*;
import android.util.Log;
public class Airplane extends GraphicObject {
int speedX, speedY;
ArrayList<Missile> misList = new ArrayList<Missile>();
Rect rect = new Rect();
boolean inuse;
public Airplane() {
super(AppManager.getInstance().getBitmap(R.drawable.mustang_transparent));
}
public Airplane(Bitmap bitmap) {
super(bitmap);
}
public void init() {
AppManager.getInstance().setAirplane(this);
}
public void setSpeed(int speedX, int speedY) {
this.speedX = speedX;
this.speedY = speedY;
}
public void update() {
if(inuse) {
if(this.x==0 && this.y==0) return;
x += speedX;
y += speedY;
rect.left = (int)x;
rect.top = (int)y;
rect.bottom = bitmap.getHeight()+(int)y;
rect.right = bitmap.getWidth()+(int)x;
}
for(int i=0;i<misList.size();i++) {
if(misList.get(i).inuse) misList.get(i).update();
}
}
public void draw(Canvas canvas) {
if(inuse) {
if(this.x==0 && this.y==0) {
this.x = canvas.getWidth()/2-bitmap.getWidth()/2;
this.y = canvas.getHeight()-bitmap.getHeight();
}
}
for(int i=0;i<misList.size();i++) {
if(misList.get(i).inuse) misList.get(i).draw(canvas);
}
if(!inuse) return;
canvas.drawBitmap(bitmap, x, y, null);
}
public void fireMissile() {
Missile misToFire = null;
for(int i=0;i<misList.size();i++) {
if( ! misList.get(i).inuse) {
misToFire = misList.get(i);
//Log.i("미사일선택", "미사일 재사용");
break;
}
}
if(misToFire==null) {
misToFire = new Missile();
//Log.i("미사일선택", "미사일 새로 생성");
misList.add(misToFire);
}
misToFire.setPosition(this.x+(bitmap.getWidth()/2)-(misToFire.bitmap.getWidth()/2), this.y);
misToFire.setSpeed(0, -4);
misToFire.inuse = true;
}
}
EnemyPlane.java
package game.framework;
import java.util.*;
import android.graphics.Canvas;
import android.util.Log;
public class EnemyPlane extends Airplane {
long prevFireTime;
Random rd;
public EnemyPlane() {
super(AppManager.getInstance().getBitmap(R.drawable.enemy_transparent));
setSpeed(0, 2);
AppManager.getInstance().enemyList.add(this);
prevFireTime = System.currentTimeMillis();
rd = new Random();
y = 0;
}
@Override
public void update() {
if(inuse) {
if(this.x==0 && this.y==0) return;
x += speedX;
y += speedY;
rect.left = (int)x;
rect.top = (int)y;
rect.bottom = bitmap.getHeight()+(int)y;
rect.right = bitmap.getWidth()+(int)x;
fireMissile();
}
}
@Override
public void draw(Canvas canvas) {
if(inuse) {
if(this.x==0 && this.y==0) {
this.x = rd.nextInt(canvas.getWidth()-bitmap.getWidth());
this.y = -(bitmap.getHeight());
}
}
if(!inuse) return;
if(y>canvas.getHeight()) inuse = false;
canvas.drawBitmap(bitmap, x, y, null);
//Log.i("적기", "draw()");
}
@Override
public void fireMissile() {
long currentTime = System.currentTimeMillis();
if(!(currentTime-prevFireTime > 3000) ) return;
if(rd.nextInt(30)!=0) return;
Missile misToFire = null;
//EnemyMisPool.getInstance().pool;
for(int i=0;i<EnemyMisPool.getInstance().pool.size();i++) {
if( ! EnemyMisPool.getInstance().pool.get(i).inuse) {
misToFire = EnemyMisPool.getInstance().pool.get(i);
Log.i("미사일선택", "미사일 재사용");
break;
}
}
if(misToFire==null) {
misToFire = new Missile(AppManager.getInstance().getBitmap(R.drawable.enemy_missile));
Log.i("미사일선택", "미사일 새로 생성");
EnemyMisPool.getInstance().pool.add(misToFire);
}
misToFire.setPosition(this.x+(bitmap.getWidth()/2)-(misToFire.bitmap.getWidth()/2), this.y);
Vector2D misVec = missileVector();
//Log.i("미사일 사선벡터", ""+misVec.x+","+misVec.y);
misToFire.setSpeed(misVec.x, misVec.y);
misToFire.inuse = true;
prevFireTime = currentTime;
}
private Vector2D missileVector(){
Airplane ap = AppManager.getInstance().getAirplane();
Vector2D apVec = new Vector2D(ap.x, ap.y);
Vector2D eneVec = new Vector2D(this.x, this.y);
return apVec.sub(eneVec).normalize().mul(4);
}
}
Missile.java
package game.framework;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Canvas;
import android.graphics.Rect;
public class Missile extends GraphicObject {
float speedX, speedY;
int bitmapHeight;
boolean inuse;
Rect rect = new Rect();
public Missile() {
super(AppManager.getInstance().getBitmap(R.drawable.missile));
bitmapHeight = bitmap.getHeight();
}
public Missile(Bitmap bitmap) {
super(bitmap);
bitmapHeight = bitmap.getHeight();
}
public void setSpeed(float speedX, float speedY) {
this.speedX = speedX;
this.speedY = speedY;
}
public void update() {
if(!inuse) return;
x += this.speedX;
y += this.speedY;
rect.left = (int)x;
rect.top = (int)y;
rect.bottom = bitmap.getHeight()+(int)y;
rect.right = bitmap.getWidth()+(int)x;
if(y< -bitmapHeight) {
inuse = false;
}
}
public void draw(Canvas canvas) {
if(!inuse) return;
if(y>canvas.getHeight()) inuse = false;
canvas.save();
double ang = Math.atan2(speedY, speedX);
double deg = ang*180/Math.PI;
canvas.rotate((float)deg-90, x, y);
canvas.drawBitmap(bitmap, x, y, null);
canvas.restore();
}
}
GraphicObject.java
package game.framework;
import android.graphics.*;
public class GraphicObject {
protected Bitmap bitmap;
protected float x, y;
public GraphicObject(Bitmap bitmap) {
this.bitmap = bitmap;
}
public void setPosition(float x, float y){
this.x = x;
this.y = y;
}
public void draw(Canvas canvas){
canvas.drawBitmap(bitmap, x, y, null);
}
}
SpriteAnimation.java
package game.framework;
import android.graphics.*;
import android.util.Log;
public class SpriteAnimation extends GraphicObject {
Rect srcRect = new Rect();
Rect dstRect = new Rect();
int msPerFrame;
int cntOfFrames;
int currentFrame;
long prevTime;
int spriteWidth;
int spriteHeight;
int spriteRows, spriteCols;
Paint paint;
public SpriteAnimation(Bitmap bitmap) {
super(bitmap);
}
public void initSpriteData(int rows, int cols, int width, int height, int fps, int cntOfFrames) {
spriteRows = rows;
spriteCols = cols;
spriteWidth = width;
spriteHeight = height;
srcRect.top = 0;
srcRect.bottom = spriteHeight;
srcRect.left = 0;
srcRect.right = spriteWidth;
msPerFrame = 1000/fps;
this.cntOfFrames = cntOfFrames;
paint = new Paint();
paint.setAlpha(255);
}
@Override
public void draw(Canvas canvas) {
dstRect.left = (int)this.x;
dstRect.top = (int)this.y;
dstRect.right = (int)this.x+spriteWidth;
dstRect.bottom = (int)this.y+spriteHeight;
canvas.drawBitmap(bitmap, srcRect, dstRect, paint);
//Log.i("스프라이트 애니", "draw()호출됨");
}
public void update(long currentTime) {
if(currentTime > prevTime + msPerFrame) {
prevTime = currentTime;
currentFrame++;
if(currentFrame >= cntOfFrames) {
currentFrame = 0;
}
}
// 다중행의 Sprite sheet에 대응하기 위해 추가된 부분
srcRect.left = currentFrame%spriteCols*spriteWidth;
srcRect.right = srcRect.left + spriteWidth;
int rowNum = currentFrame/spriteCols;
srcRect.top = rowNum*spriteHeight;
srcRect.bottom = srcRect.top + spriteHeight;
//Log.i("스프라이트 애니", "update()호출됨");
}
}
Vector2D.java
package game.framework;
import android.graphics.PointF;
public class Vector2D extends PointF {
public Vector2D(float x, float y) {
super(x,y);
}
public Vector2D sub(Vector2D vec) {
return new Vector2D(this.x - vec.x, this.y - vec.y);
}
public float dot(Vector2D vec) {
return x*vec.x + y*vec.y;
}
public Vector2D normalize(){
return new Vector2D(x/length(), y/length());
}
public Vector2D mul(float scalar) {
return new Vector2D(x*scalar, y*scalar);
}
}