おまたせしました。
前回に引き続きシューティングゲーム開発の続編です。

前回までは
・機体表示
・機体から弾発射
・機体操作のコントローラ
・機体からの弾一旦停止ボタン
・敵を1機表示
・当たり判定

今回は以下の機能の実装を行います。
・敵の攻撃実装
・衝突時のエフェクト
・敵の追加

ちょっと色々処理が入り組んできたので、機能を分けて説明が難しいので
説明が分かりづらいかも知れませんがご了承ください。

MainScene.java_メンバ変数

[java]
private List<Enemy> enemyList = new ArrayList<Enemy>();

private PhysicsHandler physicsHandler1;
private PhysicsHandler physicsHandler2;

private boolean isMainLeftMuteki = false;
private boolean isMainRightMuteki = false;
[/java]
enemyListですが、前回までListの型をSpriteにしてましたが、敵毎に動きを変えれるようにするため
Listに変更してます。

physicsHandler1は前回までは、showMainLeftSpriteメソッドで定義していましたが、自機クラッシュ後にコントローラへ再設定できるように
メンバ変数にしました。

isMainLeftMuteki/isMainRightMutekiは、自機クラッシュ後に復活した途端にクラッシュする可能性があるので、そうならないように無敵時間を判定するためのフラグです。

MainScene.java_敵機の定義メソッド

[java]
private void entryEnemy(int stage) {
TimerHandler timerHandler = new TimerHandler(5, false, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
final Sprite enemySprite = ResourceUtil.getInstance(getBaseActivity()).getSprite("enemy1.png");

attachChild(enemySprite);

final Enemy enemy = new Enemy(enemySprite, 400, -50, getBaseActivity());
enemyList.add(enemy);

// ※1.敵機攻撃のTimer
TimerHandler timerHandler = new TimerHandler(3f, true, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {

// ※5.クラッシュした敵の、TimerHandlerをスキップする
if(!enemyList.contains(enemy)){
return;
}

// ※4.クラッシュした敵の、Enemyクラス削除処理
if(enemy.isCrash()){
if(0 < enemy.getWeponSpriteList().size()){
return;
}
enemyList.remove(enemy);

return;
}

// ※2.敵の攻撃実装
final Rectangle rect = new Rectangle(enemySprite.getX() + enemySprite.getWidth(), enemySprite.getY(), 5, 5, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect);
enemy.setWeponType(rect);
sortChildren();
}
});
registerUpdateHandler(timerHandler);
}
});
registerUpdateHandler(timerHandler);




}
[/java]

Enemy.java_弾の軌道定義

[java]
private float moveWeponX = 0f;
private float moveWeponY = 1f;

private List<Rectangle> weponSpriteList = new ArrayList<Rectangle>();

// ※3.弾の軌道を設定
public void setWeponType(Rectangle rect){
rect.setZIndex(-10);
rect.setColor(CommonUtil.convertRGB(200), CommonUtil.convertRGB(200), CommonUtil.convertRGB(200));
rect.setPosition(enemySprite.getX() + enemySprite.getWidth() / 2, enemySprite.getY());

weponSpriteList.add(rect);
int randam = (int) (Math.random() * 10);
if(5 < randam){
int randam2 = (int) (Math.random() * 10);

if(5 < randam2){

}
}
}
[/java]
※1.敵機の弾発射タイマーの定義
敵機のSpriteを設定後、3秒毎したら弾を発射処理を実行します。

※2.敵機の弾をEnemyクラスに設定
5×5の四角い画像(弾)を生成して画面に設定します。
>enemy.setWeponType(rect);
この処理でEnemyクラスに弾の設定をする。

※3.敵機の弾をweponSpriteListに保持する。(moveWeponX/moveWeponY)
(まだ実装してませんが、今後弾毎に軌道を変えれるようにする。)
randam値の分岐を入れてますが、これは真っ直ぐ下に攻撃するのか、自機の左or右に攻撃するのかの軌道を変更するための分岐です。

※4.敵機のクラッシュ処理が行われていた場合の処理
isCrashがtrueの場合に、敵機の弾が全て削除されていた場合に、Enemyクラスを
処理対象のListから除外します。
弾の残を確認しないでEnemyを削除すると、敵機の弾はEnemyクラスが保持しているので、弾に対しての処理ができなくなり弾が画面上にとまったまま残った状態となります。
敵機がクラッシュした後も発射した弾は同じ軌道をたどって動いてほしいので
弾が画面外にでる or 自機にあたるなどによって全て残っていない場合にのみEnemyクラスを削除します。

※5.Enemyクラスが完全に削除されている場合は、TimerHandlerをスキップします。
本当はTimerHandlerをとめたいのですが、ちょっとやり方がわかんないのでスキップにしました。

MainScene.java_自機のSprite表示の設定

[java]
private void showMainLeftSprite() {
if (null == mainLeftSprite) {
mainLeftSprite = getBaseActivity().getResourceUtil()
.getAnimatedSprite("main1.png", 1, 2);
mainLeftSprite.setZIndex(mainZIndex);
mainLeftSprite.setPosition(200, 450);

mainLeftSprite.setZIndex(controlAreaZIndex – 10);
attachChild(mainLeftSprite);
sortChildren();

physicsHandler1 = new PhysicsHandler(mainLeftSprite);
mainLeftSprite.registerUpdateHandler(physicsHandler1);

}
}

private void showMainRightSprite() {
if (null == mainRightSprite) {
mainRightSprite = getBaseActivity().getResourceUtil()
.getAnimatedSprite("main2.png", 1, 2);
mainRightSprite.setZIndex(mainZIndex);
mainRightSprite.setPosition(600, 450);
mainRightSprite.setZIndex(controlAreaZIndex – 10);

attachChild(mainRightSprite);
sortChildren();

physicsHandler2 = new PhysicsHandler(mainRightSprite);
mainRightSprite.registerUpdateHandler(physicsHandler2);

}
}

private void drawAnalogControls() {

showMainLeftSprite();
showMainRightSprite();




}
[/java]
drawAnalogControlsで定義していたのを外だししてます。(showMainLeftSprite/showMainLeftSprite)
自機がクラッシュしたあとに、自機Spriteを初期化して、再設定するので処理を共通化するため。

MainScene.java_当たり判定ロジック追加

[java]
TimerHandler timerHandler = new TimerHandler(1 / 60f, true, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
List<Rectangle> removeLeftWepon = new ArrayList<Rectangle>();
for (Rectangle rect : weponLeftList) {
if (rect.getY() < -10) {
removeLeftWepon.add(rect);
rect.detachSelf();
rect.dispose();
} else {
rect.setPosition(rect.getX(), rect.getY() – 3);
if (isEnemyCrash(rect, true)) {
removeLeftWepon.add(rect);
rect.detachSelf();
rect.dispose();
}
}

// ※6.味方の弾の当たり判定
if (rect.collidesWith(mainRightSprite)) {
crashRightMain();
}
}
weponLeftList.removeAll(removeLeftWepon);



for (Enemy enemy : enemyList) {
Sprite enemySprite = enemy.getEnemySprite();
if (enemySprite.collidesWith(mainLeftSprite)) {
crashLeftMain();
}
if (enemySprite.collidesWith(mainRightSprite)) {
crashRightMain();
}

// ※7.敵の攻撃の当たり判定
List<Rectangle> replaceEnemyWeponList = new ArrayList<Rectangle>();
for(Rectangle rect : enemy.getWeponSpriteList()) {
rect.setPosition(rect.getX() + enemy.getMoveWeponX(), rect.getY() + enemy.getMoveWeponY());

if(rect.getX() < -10 || rect.getX() > 810 || rect.getY() > 500){
rect.detachSelf();
}else{
if(rect.collidesWith(mainLeftSprite)) {
crashLeftMain();
rect.detachSelf();
}else if(rect.collidesWith(mainRightSprite)){
crashRightMain();
rect.detachSelf();
}else {
replaceEnemyWeponList.add(rect);
}
}
}
enemy.replaceWeponSpriteList(replaceEnemyWeponList);
}




}
});
[/java]
※6.自機(左)の弾に対して、自機(右)のあたり判定
味方の弾も当たったらクラッシュするようにします。

※7.敵の攻撃に対しての自機の当たり判定
メンバ変数に保持しているEnemyのリストをループして
Enemyクラスで保持している弾のリストで更にループします。

>if(rect.getX() < -10 || rect.getX() > 810 || rect.getY() > 500){
弾が画面外に外れた場合、弾のクリア処理をします。

>if(rect.collidesWith(mainLeftSprite)){
>crashLeftMain();
>rect.detachSelf();
弾と自機の当たり判定をして、Spriteが重なっていれば自機のクラッシュ処理をして弾のクリア処理をします。

>enemy.replaceWeponSpriteList(replaceEnemyWeponList);
ループ処理後に、弾が範囲外でも当たってもいない弾のリストでEnemyクラスの弾リストを設定しなおす。

MainScene.java_敵機のクラッシュ処理

[java]
private boolean isEnemyCrash(Rectangle rect, boolean isMainLeftSprite) {

for (Enemy enemy : enemyList) {
// ※9.クラッシュしていたらスキップ
if(enemy.isCrash()){
continue;
}
Sprite enemySprite = enemy.getEnemySprite();
if (enemy.getEnemySprite().collidesWith(rect)) {
enemyCrashSound.play();

// ※8.クラッシュした際のエフェクト処理
final AnimatedSprite crashAnimation = ResourceUtil.getInstance(getBaseActivity())
.getAnimatedSprite("enemyCrash.png", 6, 1);

crashAnimation.setPosition((enemySprite.getX() + enemySprite.getWidth() / 2) – (crashAnimation.getWidth() / 2), (enemySprite.getY() + enemySprite.getHeight() / 2) – (crashAnimation.getHeight() / 2));
crashAnimation.setZIndex(controlAreaZIndex – 5);
attachChild(crashAnimation);
sortChildren();
crashAnimation.animate(100, false);

enemy.setCrash(true);

enemySprite.clearEntityModifiers();
enemySprite.detachSelf();

enemy = null;

registerUpdateHandler(new TimerHandler(0.6f, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
crashAnimation.detachSelf();
}
}));

return true;
}
}
return false;
}
[/java]
※8.クラッシュ用のアニメーション処理
crashAnimationにクラッシュ用の画像を読み込む(縦1、横6の画像)

>crashAnimation.setPosition((enemySprite.getX() + enemySprite.getWidth() / 2) – (crashAnimation.getWidth() / 2), (enemySprite.getY() + enemySprite.getHeight() / 2) – (crashAnimation.getHeight() / 2));
クラッシュエフェクトの表示位置指定。

>crashAnimation.animate(100, false);
100ミリ秒間隔でクラッシュエフェクトの画像を切り替える。
繰り返し設定はfalseにして、一度だけ実行。

>enemy.setCrash(true);
>enemySprite.clearEntityModifiers();
>enemySprite.detachSelf();
Enemyクラスにクラッシュ事をフラグでセット
敵機の移動Modifierをクリア
敵機を画面から消す。
ここではEnemyクラスをメンバ変数のEnemyリストから削除せずに
※4で説明した箇所で削除する。

>registerUpdateHandler(new TimerHandler(0.6f, new ITimerCallback() {
> public void onTimePassed(TimerHandler pTimerHandler) {
> crashAnimation.detachSelf();
> }
>}));
100ミリ秒×6枚の画像のクラッシュアニメーションが終わるのを待つので
0.6秒後にクラッシュアニメーションのクリア処理をする。

※9.既にクラッシュしていたらエフェクトの処理は不要なので※4でEnemyクラスが削除されるまでスキップする。

Mainscene.java_自機のクラッシュ処理

[java]
private void crashLeftMain() {
if (isMainLeftMuteki) {
return;
}
mainCrashSound.play();

// ※10.味方のクラッシュ処理
final AnimatedSprite crashAnimation = ResourceUtil.getInstance(getBaseActivity())
.getAnimatedSprite("enemyCrash.png", 6, 1);

crashAnimation.setPosition((mainLeftSprite.getX() + mainLeftSprite.getWidth() / 2) – (crashAnimation.getWidth() / 2), (mainLeftSprite.getY() + mainLeftSprite.getHeight() / 2) – (crashAnimation.getHeight() / 2));
crashAnimation.setZIndex(controlAreaZIndex – 5);
attachChild(crashAnimation);
sortChildren();
crashAnimation.animate(100, false);

mainLeftSprite.clearEntityModifiers();
mainLeftSprite.detachSelf();
mainLeftSprite = null;

isMainLeftMuteki = true;

registerUpdateHandler(new TimerHandler(0.6f, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
crashAnimation.detachSelf();
}
}));

registerUpdateHandler(new TimerHandler(2.0f, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
showMainLeftSprite();
mainLeftSprite.registerEntityModifier(new MoveModifier(1f, 200, 200, 500, 450));
mainLeftSprite.registerEntityModifier(new LoopEntityModifier(new SequenceEntityModifier(new FadeOutModifier(0.25f), new FadeInModifier(0.25f)), 8));
}
}));

registerUpdateHandler(new TimerHandler(6.0f, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
isMainLeftMuteki = false;
weponMainLeftSprite();
}
}));
}
[/java]
※10.敵機のクラッシュ処理と大体同じです。
>isMainLeftMuteki = true;
一旦クラッシュしたので、すぐに死ぬのを防ぐために無敵状態にするフラグを立てます。
無敵状態の時には、当たり判定をしないようにします。

>registerUpdateHandler(new TimerHandler(2.0f, new ITimerCallback() {
> public void onTimePassed(TimerHandler pTimerHandler) {
> showMainLeftSprite();
> mainLeftSprite.registerEntityModifier(new MoveModifier(1f, 200, 200, 500, 450));
> mainLeftSprite.registerEntityModifier(new LoopEntityModifier(new SequenceEntityModifier(new FadeOutModifier(0.25f), new FadeInModifier(0.25f)), 8));
> }
>}));
2秒後に自機を表示して、最初のポジションを設定します。
更に、0.25毎にフェードイン/フェードアウトのセットを8回繰り返して、点滅状態を作ります。

>registerUpdateHandler(new TimerHandler(6.0f, new ITimerCallback() {
> public void onTimePassed(TimerHandler pTimerHandler) {
> isMainLeftMuteki = false;
> weponMainLeftSprite();
> }
>}));
2秒後に点滅処理を「(0.25を2回)×8= 4秒」行うので
6秒後に無敵状態を解除して、自機の攻撃処理を実行します。

ちょっとごちゃごちゃしてきましたが
これで今回対応予定の
・敵の攻撃実装
・衝突時のエフェクト
・敵の追加
ができました。

これでベースの機能ができましたね!

静止画だとわかりづらいけど、こんな感じ。
vol5_5

残対応としては
・自機ライフ
・ゲームオーバー処理
・ボス実装
・敵のパターン実装(移動・弾)
・一時停止メニュー(リトライ・再開・メニューにもどる)
・アイテム実装
・Fireボタンの必殺技対応
・スコア
・音楽
・背景
・広告表示
・Googleアナリティクス
くらいかな?

まだまだ、やることいっぱいありますね。

このブログを誰か見てくれているかは不明ですが
もうちょっとがんばって完成させたいと思います!

以前までにリリースしているアプリには「Googleアナリティクス」のSDKが入っていないので
ダウンロード数くらいしかリリース後の情報がなかったので
今回はちゃんと解析できるようにしたいと思います!

Andengineなかなか便利ですねー
色々勉強してもっと色んな事できるようになりたいなー

って事で、今回はここまで。
次回をお楽しみにー