2機同時操作のシューティングゲーム

前回までは
・機体表示
・機体から弾発射
・機体操作のコントローラ
です。

今回は
・機体からの弾一旦停止ボタン
・敵を1機表示
・当たり判定
をやっていきたいと思います。

機体からの弾一旦停止ボタン

この機能がなんで必要かというと
今回のシューティングは味方の攻撃でも当たったらアウトにしようとしているので
自動で弾の撃ちっぱなしだと不便だと思うので実装します。
[java]
MainScene.java

private boolean weponLeftFlg = true;
private boolean weponRightFlg = true;

@Override
public void init(){

・・・省略

// 左側の弾停止ボタン ※1
weponLeftButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOnButton.png", "weponOnButton.png");
weponLeftButton.setPosition(75 – weponLeftButton.getWidth() / 2, 240);
attachChild(weponLeftButton);
registerTouchArea(weponLeftButton);
weponLeftButton.setOnClickListener(new ButtonSprite.OnClickListener() {
public void onClick(ButtonSprite pButtonSprite,
float pTouchAreaLocalX, float pTouchAreaLocalY) {
if(weponLeftFlg) {
weponLeftButton.detachSelf();
weponLeftButton.dispose();
weponLeftButton = null;

weponLeftButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOffButton.png", "weponOffButton.png");
weponLeftButton.setPosition(75 – weponLeftButton.getWidth() / 2, 240);
attachChild(weponLeftButton);
registerTouchArea(weponLeftButton);

weponLeftFlg = false;
}else{
weponLeftButton.detachSelf();
weponLeftButton.dispose();
weponLeftButton = null;

weponLeftButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOnButton.png", "weponOnButton.png");
weponLeftButton.setPosition(75 – weponLeftButton.getWidth() / 2, 240);
attachChild(weponLeftButton);
registerTouchArea(weponLeftButton);

weponLeftFlg = true;
}
}
});

// 右側の弾停止ボタン
weponRightButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOnButton.png", "weponOffButton.png");
weponRightButton.setPosition(725 – weponRightButton.getWidth() / 2, 240);
attachChild(weponRightButton);
registerTouchArea(weponRightButton);
weponRightButton.setOnClickListener(new ButtonSprite.OnClickListener() {
public void onClick(ButtonSprite pButtonSprite,
float pTouchAreaLocalX, float pTouchAreaLocalY) {
if(weponRightFlg) {
weponRightButton.detachSelf();
weponRightButton.dispose();
weponRightButton = null;

weponRightButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOffButton.png", "weponOffButton.png");
weponRightButton.setPosition(725 – weponRightButton.getWidth() / 2, 240);
attachChild(weponRightButton);
registerTouchArea(weponRightButton);

weponRightFlg = false;
}else{
weponRightButton.detachSelf();
weponRightButton.dispose();
weponRightButton = null;

weponRightButton = ResourceUtil
.getInstance(getBaseActivity())
.getButtonSprite("weponOnButton.png", "weponOnButton.png");
weponRightButton.setPosition(725 – weponRightButton.getWidth() / 2, 240);
attachChild(weponRightButton);
registerTouchArea(weponRightButton);

weponRightFlg = true;
}
}
});
}

// ※2
private void weponMainLeftSprite(){
mainLeftSprite.registerEntityModifier(new SequenceEntityModifier(
new DelayModifier(0.5f, new IEntityModifier.IEntityModifierListener() {
@Override
public void onModifierFinished(
IModifier<IEntity> pModifier, IEntity pItem) {

if(!weponLeftFlg){
weponMainLeftSprite();
return;
}
final Rectangle rect = new Rectangle(mainLeftSprite.getX() + mainLeftSprite.getWidth(), mainLeftSprite.getY(), 5, 5, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect);

rect.setColor(CommonUtil.convertRGB(200), CommonUtil.convertRGB(200), CommonUtil.convertRGB(200));
rect.setPosition(mainLeftSprite.getX() + mainLeftSprite.getWidth() / 2, mainLeftSprite.getY());
rect.setZIndex(10);

weponLeftList.add(rect);

weponMainLeftSprite();
return;
}

@Override
public void onModifierStarted(IModifier<IEntity> pModifier,
IEntity pItem) {
}
})));
}

private void weponMainRightSprite(){
mainRightSprite.registerEntityModifier(new SequenceEntityModifier(
new DelayModifier(0.5f, new IEntityModifier.IEntityModifierListener() {
@Override
public void onModifierFinished(
IModifier<IEntity> pModifier, IEntity pItem) {
if(!weponRightFlg){
weponMainRightSprite();
return;
}

final Rectangle rect = new Rectangle(mainRightSprite.getX() + mainRightSprite.getWidth(), mainRightSprite.getY(), 5, 5, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect);

rect.setColor(CommonUtil.convertRGB(200),CommonUtil. convertRGB(200), CommonUtil.convertRGB(200));
rect.setPosition(mainRightSprite.getX() + mainRightSprite.getWidth() / 2, mainRightSprite.getY());
rect.setZIndex(10);

weponRightList.add(rect);

weponMainRightSprite();
return;
}

@Override
public void onModifierStarted(IModifier<IEntity> pModifier,
IEntity pItem) {
}
})));
}

[/java]

まず※1ですが
停止ボタンの画像を読み込んで、画像がタッチされた場合に「ON」「OFF」を切り替えるように設定します。
下記で画像のタッチを検知して、フラグを見て画像を切り替えます。
[java]
weponLeftButton.setOnClickListener(new ButtonSprite.OnClickListener() {
public void onClick(ButtonSprite pButtonSprite,
float pTouchAreaLocalX, float pTouchAreaLocalY) {
[/java]
(右も同様)

次に※2で
弾を発射するために再帰的に呼び出している処理で
フラグを見て、ボタンの状態が「ON」か「OFF」かを判断して
「OFF」の場合には、弾を発射しない分岐に入れてスキップして再帰的に呼び出します。
[java]
if(!weponLeftFlg){
weponMainLeftSprite();
return;
}
[/java]

これだけ。これで弾が発射されたままになったり、停止したりします。

ついでに必殺のボタンだけ配置しちゃいましょう。
[java]
fireLeftButton = ResourceUtil
.getInstance(getBaseActivity()).getSprite("fireButton.png");
fireLeftButton.setPosition(75 – fireLeftButton.getWidth() / 2, 50);
attachChild(fireLeftButton);
registerTouchArea(fireLeftButton);

fireRightButton = ResourceUtil
.getInstance(getBaseActivity()).getSprite("fireButton.png");
fireRightButton.setPosition(725 – fireRightButton.getWidth() / 2, 50);
attachChild(fireRightButton);
registerTouchArea(fireRightButton);
[/java]

敵機の表示

次はいよいよ敵を表示します!
[java]
MainScene.java

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

@Override
public void init(){

・・・省略

// 敵をセット
entryEnemy(stage);
}

// ※3
private void entryEnemy(int stage){
TimerHandler timerHandler = new TimerHandler(5, false, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
Sprite enemy1 = ResourceUtil.getInstance(getBaseActivity()).getSprite("enemy1.png");
attachChild(enemy1);

enemyList.add(new Enemy(enemy1));
}
});
registerUpdateHandler(timerHandler);

}

Enemy.java

private Sprite enemySprite;

public Enemy(Sprite sprite){
this.enemySprite = sprite;
setMoveType(0);
}

// ※4
public void setMoveType(int type){
enemySprite.setColor(CommonUtil.convertRGB(200), CommonUtil.convertRGB(200), CommonUtil.convertRGB(200));
enemySprite.setPosition(400, -50);
enemySprite.setZIndex(10);

enemySprite.registerEntityModifier(new SequenceEntityModifier(
new MoveModifier(2f, enemySprite.getX(),enemySprite.getX(), enemySprite.getY(),50)
,new DelayModifier(1f)
,new LoopEntityModifier(
new SequenceEntityModifier(
new MoveByModifier(1f, 100, 0)
,new MoveByModifier(1f, – 100, 0)
,new MoveByModifier(1f, – 100, 0)
,new MoveByModifier(1f, 100, 0)))
));
}
}

[/java]

敵の表示はinit処理で呼びだして
登場する敵の情報は最初に全て定義しちゃおうと思います。
※3、ここではゲーム開始後5秒後に敵が登場するようにしています。
敵の画像のSpriteを生成してEnemyクラスのコントラクタに渡します。
(今後敵を追加するときは、ここに何秒後に敵を追加するか指定します。)

※4、EnemyクラスでSpriteを保持します。
setMoveTypeメソッドで敵の移動を制御します。
・new SequenceEntityModifier(
 以降のModifierを順番に実行する
・new MoveModifier(2f, enemySprite.getX(),enemySprite.getX(), enemySprite.getY(),50)
 対象のSpriteを2秒かけて、現時点X軸に0、Y軸に+50移動する。
・,new DelayModifier(1f)
 1秒待機
・,new LoopEntityModifier(
 次のModifierを繰り返し処理をする。
・new SequenceEntityModifier(
 以降のModifierを順番に実行する
・new MoveByModifier(1f, 100, 0)
 対象のSpriteを1秒かけて、現時点X軸に+100、Y軸に0移動する。
・,new MoveByModifier(1f, – 100, 0)
 対象のSpriteを1秒かけて、現時点X軸に-100、Y軸に0移動する。
・,new MoveByModifier(1f, – 100, 0)
 対象のSpriteを1秒かけて、現時点X軸に-100、Y軸に0移動する。
・,new MoveByModifier(1f, 100, 0)))
 対象のSpriteを1秒かけて、現時点X軸に+100、Y軸に0移動する。

上記の処理で、上から出てきて、ずっと右左に移動する動きをします。
setMoveTypeメソッドの引数にtypeがあるのは、いろんな敵の動きを実装して引数によって動きを変えたいからです。
今後この引数は使用する予定です。

あたり判定

最後に、当たり判定の実装
(今回は判定のみで、クラッシュ時のエフェクトは実装しません。)

[java]
MainScene.java

private Sound enemyCrashSound;
private Sound mainCrashSound;

※音の初期化
@Override
public void prepareSoundAndMusic() {
// 効果音をロード
try {
enemyCrashSound = SoundFactory.createSoundFromAsset(
getBaseActivity().getSoundManager(), getBaseActivity(),
"enemyCrash.wav");
mainCrashSound = SoundFactory.createSoundFromAsset(
getBaseActivity().getSoundManager(), getBaseActivity(),
"mainCrash.mp3");
} catch (IOException e) {
e.printStackTrace();
}
}

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);
// ※5
if(isEnemyCrash(rect, true)){
removeLeftWepon.add(rect);
rect.detachSelf();
rect.dispose();
}
}
}
weponLeftList.removeAll(removeLeftWepon);

List<Rectangle> removeRightWepon = new ArrayList<Rectangle>();
for(Rectangle rect : weponRightList){
if(rect.getY() < -10){
removeRightWepon.add(rect);
rect.detachSelf();
rect.dispose();
}else {
rect.setPosition(rect.getX(), rect.getY() – 3);
if (isEnemyCrash(rect, false)){

}
}
}
weponRightList.removeAll(removeRightWepon);

// ※6
for(Sprite enemy : enemyList){
if(enemy.collidesWith(mainLeftSprite)){
crashMain(mainLeftSprite);
}
if(enemy.collidesWith(mainRightSprite)){
crashMain(mainRightSprite);
}
}

if(mainLeftSprite.collidesWith(mainRightSprite)){
crashMain(mainLeftSprite);
crashMain(mainRightSprite);
}
}
});

// ※7
private boolean isEnemyCrash(Rectangle rect, boolean isMainLeftSprite){

for(Sprite enemy : enemyList){
if(enemy.collidesWith(rect)){
enemyCrashSound.play();
return true;
}
}
return false;
}

// ※8
private void crashMain(Sprite mainSprite){
mainCrashSound.play();
}
[/java]

※5、※7で機体からの、弾と敵の当たり判定をしてます。
弾Spriteと敵Spriteを「collidesWith」で判定
trueの場合はSprite同士が重なっているので衝突と判断しします。
今回までは、衝突音と当たった弾の消去をしてます。

※6、※8で機体と敵、機体と機体の当たり判定をしてます。
同じように判定したいSprite同士で「collidesWith」処理をして
trueの場合に衝突音を実装してます。

静止画じゃ分かりづらいですが、現在のゲーム画像はこんな感じです。
↓↓↓
vol5_3

vol5_4

敵がでてきたことで、少しだけシューティングっぽくなりましたね。

次回以降は
・敵の攻撃実装
・衝突時のエフェクト
・敵の追加
をしたいと思います。

ちょっと処理が冗長だったり、うまく分割できていないように感じますが
それは追々余裕があればやりたいと思います。

でわでわ、つづきをお楽しみに。