Browsing: シューティング

シューティングゲーム開発Vol5_自機ライフ&ゲームオーバー&敵の弾道



さて、今回は自機のライフを設定して
ライフがなくなったらゲームオーバーとなる処理を実装します。

あと、敵のミサイルの弾道が直線だけなので、たまに自機のいる位置に攻撃するような弾道とします。

自機ライフ&ゲームオーバー処理

ライフ表示用のメソッドを用意します。
[java]
MainScene.java

private int mainLeftLife = 3;
private int mainRightLife = 3;

private Text mainLeftLifeText;
private Text mainRightLifeText;

@Override
public void init() {

font = FontFactory.create(getBaseActivity().getFontManager(), getBaseActivity().getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32, true, Color.WHITE);
font.load();

showLife(true);

・・・省略・・・
}

private void showLife(boolean initFlag){
if(initFlag){
Sprite leftLifeImage = getBaseActivity().getResourceUtil().getSprite("life.png");
leftLifeImage.setPosition(50 – (leftLifeImage.getWidth() / 2), 50 – (leftLifeImage.getHeight() / 2));
leftLifeImage.setZIndex(controlAreaZIndex + 10);
attachChild(leftLifeImage);

Sprite rightLifeImage = getBaseActivity().getResourceUtil().getSprite("life.png");
rightLifeImage.setPosition(700 – (rightLifeImage.getWidth() / 2), 50 – (rightLifeImage.getHeight() / 2));
rightLifeImage.setZIndex(controlAreaZIndex + 10);
attachChild(rightLifeImage);
}

if(null != mainLeftLifeText) {
mainLeftLifeText.detachSelf();
}
mainLeftLifeText = new Text(0, 0, font, " x " + String.valueOf(mainLeftLife), new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
mainLeftLifeText.setPosition(80 – (mainLeftLifeText.getWidth() / 2), 50 – (mainLeftLifeText.getHeight() / 2));
mainLeftLifeText.setZIndex(controlAreaZIndex + 10);
mainLeftLifeText.setScale(0.7f);
mainLeftLifeText.setColor(CommonUtil.convertRGB(255), CommonUtil.convertRGB(255), CommonUtil.convertRGB(255));
attachChild(mainLeftLifeText);

if(null != mainRightLifeText) {
mainRightLifeText.detachSelf();
}
mainRightLifeText = new Text(0, 0, font, " x " + String.valueOf(mainRightLife), new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
mainRightLifeText.setPosition(730 – (mainLeftLifeText.getWidth() / 2), 50 – (mainLeftLifeText.getHeight() / 2));
mainRightLifeText.setZIndex(controlAreaZIndex + 10);
mainRightLifeText.setScale(0.7f);
mainRightLifeText.setColor(CommonUtil.convertRGB(255), CommonUtil.convertRGB(255), CommonUtil.convertRGB(255));
attachChild(mainRightLifeText);

}
[/java]

val5_6

ライフ画像の横に「x3」のようにライフが表示されました。
単に画像と文字を画面に表示しているだけです。
引数がtrueの場合だけ、ライフ画像の表示をします。

[java]
MainScene.java

private void crashLeftMain() {

if(0 == mainLeftLife){
showGameOver();
return;
}

mainLeftLife = mainLeftLife – 1;
showLife(false);

・・・省略・・・
}

private void showGameOver() {

// 敵スプライトのModifireをクリアする。
for(Enemy enemy : enemyList){
enemy.getEnemySprite().clearEntityModifiers();
}

// ゲームオーバー画面の背景画像
Text text = new Text(0, 0, font, "ゲームオーバー", new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
text.setPosition(camera.getWidth() / 2 – text.getWidth() / 2, -text.getHeight() -20);
text.setScale(2f);
text.setColor(CommonUtil.convertRGB(255), CommonUtil.convertRGB(255), CommonUtil.convertRGB(255));
attachChild(text);

sortChildren();

text.registerEntityModifier(new SequenceEntityModifier(
new DelayModifier(1.0f), new MoveModifier(3.0f,
text.getX(), text.getX(), text.getY(), camera.getHeight() / 2 – text.getHeight() / 2,
EaseQuadOut.getInstance())));
}
[/java]
自機がクラッシュした際に、showLifeを呼び出して再度ライフ文字の表示を行います。
ライフが0になった場合にshowGameOverを呼び出します。

ゲームオーバーの文字を表示します。

val5_7

敵のミサイルの弾道

現在はEnemyクラスにミサイルの弾道を一つだけ保持していて
ミサイル毎の弾道を定義できないので、ミサイルと弾道をセットで
保持できるようにクラスを生成します。

[java]
package org.geex.twinspacehero.enemy;

import java.awt.Rectangle;

public class EnemyWepon {

private Rectangle weponSprite;

private float moveWeponX = 0f;
private float moveWeponY = 0f;

public Rectangle getWeponSprite() {
return weponSprite;
}

public void setWeponSprite(Rectangle weponSprite) {
this.weponSprite = weponSprite;
}

public float getMoveWeponX() {
return moveWeponX;
}

public void setMoveWeponX(float moveWeponX) {
this.moveWeponX = moveWeponX;
}

public float getMoveWeponY() {
return moveWeponY;
}

public void setMoveWeponY(float moveWeponY) {
this.moveWeponY = moveWeponY;
}
}
[/java]
weponSprite:ミサイルの画像
moveWeponX:X座標の弾道
moveWeponY:Y座標の弾道

[java]
Enemy.java

private List<EnemyWepon> weponList = new ArrayList<EnemyWepon>();

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() + enemySprite.getHeight());

Sprite targetMainSprite;

float x = 0;
float y = 1;
int randam = (int) (Math.random() * 10);
// ※1
if(5 < randam){
int randam2 = (int) (Math.random() * 10);

if(5 < randam2){
targetMainSprite = mainLeftSprite;
}else{
targetMainSprite = mainRightSprite;
}

// ※2
float[] firstXY = {rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight()};
float[] secondXY = {targetMainSprite.getX() + targetMainSprite.getWidth() / 2, targetMainSprite.getY()};

double angle = getAngleByTwoPosition(firstXY, secondXY);

// 移動距離と角度からx方向、y方向の移動量を求める
double distance = getDistanceTwoPosition(firstXY, secondXY) * 0.02f;

x = -(float) (distance * Math
.cos(angle * Math.PI / 180.0));
y = -(float) (distance * Math
.sin(angle * Math.PI / 180.0));
}

EnemyWepon enemyWepon = new EnemyWepon();
enemyWepon.setWeponSprite(rect);
enemyWepon.setMoveWeponX((x / y));
enemyWepon.setMoveWeponY(1);

weponList.add(enemyWepon);
}
[/java]
※1.半分の確立で、自機に向かってミサイルを発射するようにします。
更に半分の確立で、どちらの自機に対してミサイルを発射するかを決めています。

※2.よくやる計算ですね。
2点の座標から角度を元に移動距離を求めます。

>enemyWepon.setMoveWeponX((x / y));
>enemyWepon.setMoveWeponY(1);
更に、Y軸を1固定とするので、計算結果をxをY:1に対応する値に計算します。

上記のEnemyの変更箇所に合わせて、MainSceneを変更すれば、
敵のミサイル毎に弾道を設定できるようになります。

val5_8

次回は、ボスとゲームクリア、できればアイテムの実装などをしたいと思います。

{ Add a Comment }

ブロック崩し&シューティングゲームVol1_物理エンジン(重力制御)




今回はAndengineの物理演算用のライブラリを使用してみようと思います。
これを使用すると、物体が重力の影響を受けて落下・加速、バウンドしたりする動きを特別な処理をする必要なく行ってくれます。

物理エンジンを適用するSceneを作成

まずは、Sceneに重力エリアの設定をします。
[java]
MainScene.Java

private PhysicsWorld physicsWorld;

@Override
public void init() {
this.physicsWorld = new PhysicsWorld(new Vector2(0, 0), false);

this.registerUpdateHandler(this.physicsWorld);
}
[/java]
これだけ。
PhysicsWorldのインスタンスを生成して、registerUpdateHandlerにセットするだけです。
>new PhysicsWorld(new Vector2(0, 0), false);
ここでセットしているVector2の値で重力を設定できます。
例えば
・Vector2(0, 9.8f)
 Y軸に9.8設定をすると、地球と同じような重力が働きます。
・さらにVector2(2.0f, 9.8f)
 上記のようにすると、x軸にも重力が加わり、Y軸に落下しつつも、X軸にも動くので”風”のような力が加わるような動きができます。

※今回は、無重力にしたいため、X・Y軸ともに0を指定しています。

上下左右に壁を作成

壁がないと、重力の影響をうけて動いた物体が画面端まで移動しても止まってくれないため、画面外にいってしまいます。
なので、壁を作成します。
[java]
MainScene.Java

@Override
public void init() {
・・・省略

final VertexBufferObjectManager vertexBufferObjectManager = getBaseActivity().getVertexBufferObjectManager();
final Rectangle ground = new Rectangle(0, camera.getHeight() – 2, camera.getWidth(), 2, vertexBufferObjectManager);
final Rectangle roof = new Rectangle(0, 0, camera.getWidth(), 2, vertexBufferObjectManager);
final Rectangle left = new Rectangle(0, 0, 2, camera.getHeight(), vertexBufferObjectManager);
final Rectangle right = new Rectangle(camera.getWidth() – 2, 0, 2, camera.getHeight(), vertexBufferObjectManager);

final FixtureDef wallFixtureDef = PhysicsFactory.createFixtureDef(0, 0.8f, 0.1f);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, left, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, right, BodyDef.BodyType.StaticBody, wallFixtureDef);

this.attachChild(ground);
this.attachChild(roof);
this.attachChild(left);
this.attachChild(right);
}
[/java]
>final Rectangle ground = new Rectangle(0, camera.getHeight() – 2, camera.getWidth(), 2, vertexBufferObjectManager);
これで画面下部の地面に当たる部分の線を生成します。
引数1:X軸のポジション
引数2:Y軸のポジション
引数3:図形のX軸の長さ
引数4:図形のY軸の長さ
引数5:よく分からないのでおまじない的な。。

>PhysicsFactory.createBoxBody(this.physicsWorld, ground, BodyDef.BodyType.StaticBody, wallFixtureDef);
これを設定しないとattachChildで画面に表示させたとしても、物体との衝突が発生しないですり抜けてしまいます。
引数1:生成したメンバ変数に保持しているPhysicsWorld
引数2:対象の物体(RectangleでもSpriteでもOK)
引数3:図形に持たせる特性。
 ・BodyDef.BodyType.StaticBody:重力の影響を受けないで画面上に固定で表示される。今回は壁は動かないのでこれを使用します。
 ・BodyDef.BodyType.KinematicBody:重力の影響は受けないが、プログラム上の制御により移動する。(コントローラによる操作や、一定速度で動く物体)
 ・BodyDef.BodyType.DynamicBody:重力の影響を受けて移動する。
引数4:物体の密度、弾力性、摩擦の設定。
 ・PhysicsFactory.createFixtureDef(0, 0.8f, 0.1f)で生成した値を設定する。
 
>this.attachChild(ground);
画面上にセットする。
これをしないと画面に表示されません。

重力の影響を受ける物体を作成

重力エリアと壁を作成したので、次は画面に重力の影響を受ける物体を表示させます。
壁の作成した手順と同じように物体を作成して、それを設定します。
今回はボールの絵を使用したSpriteを使用します。
[java]
MainScene.Java

private Body ballBody;
private Sprite ballSprite;

@Override
public void init() {
・・・省略

// ボールの設定
addBall();
}

private void addBall() {

ballSprite = ResourceUtil.getInstance(getBaseActivity()).getSprite("ball.png");
ballSprite.setPosition(camera.getWidth() / 2, camera.getHeight() – camera.getHeight() / 3);

FixtureDef ballFixtureDef = PhysicsFactory.createFixtureDef(1, 0.8f, 0.2f);
ballBody = PhysicsFactory.createCircleBody(this.physicsWorld, ballSprite, BodyDef.BodyType.DynamicBody, ballFixtureDef);

this.physicsWorld.registerPhysicsConnector(new PhysicsConnector(ballSprite, ballBody, true, true));

this.attachChild(ballSprite);
}
[/java]
ボールは重力の影響を受け、移動・加速・バウンドをしてほしいので「BodyDef.BodyType.DynamicBody」を使用します。
※「PhysicsFactory.createCircleBody」で物体の形状を設定します。ボールは丸いので「createCircleBody」を使用します。
四角の場合は「createBoxBody」を使用します。
この時点でアプリを起動すると、「PhysicsWorld」に重力値をセットしている場合は、その力により
ボールが移動します。
壁までいくとぶつかってバウンドすると思います。

画面タッチ&スライドで移動処理

今回は無重力状態とするので、ボールに対して、別の物体からの衝突で力を加えるようにします。
更に、その物体を画面のタッチした時点の座標と移動させている地点の座標で移動処理を行います。
(前回のシューティングではコントローラを使用しましたが、今回は画面タッチでも移動です。)
[java]
MainScene.Java

private Body playerBody;
private AnimatedSprite playerSprite;

private float downX = 0;
private float downY = 0;

@Override
public void init() {

・・・省略

// プレイヤーの設定
addPlayer();

this.setOnSceneTouchListener(new IOnSceneTouchListener() {
@Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_DOWN) {
downX = pSceneTouchEvent.getX();
downY = pSceneTouchEvent.getY();
}
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
float[] firstXY = {downX, downY};
float[] secondXY = {pSceneTouchEvent.getX(), pSceneTouchEvent.getY()};

float[] startXY = {0, 0};
float[] endXY = {secondXY[0] – firstXY[0], secondXY[1] – firstXY[1]};

if (endXY[0] > 150) {
endXY[0] = 150;
}
if (endXY[0] < -150) {
endXY[0] = -150;
}
if (endXY[1] > 150) {
endXY[1] = 150;
}
if (endXY[1] < -150) {
endXY[1] = -150;
}

double angle = getAngleByTwoPosition(startXY, endXY);

// 移動距離と角度からx方向、y方向の移動量を求める
double distance = getDistanceTwoPosition(startXY, endXY) * 0.02f;

float x = -(float) (distance * Math
.cos(angle * Math.PI / 180.0));
float y = -(float) (distance * Math
.sin(angle * Math.PI / 180.0));

movePlayer(x, y);
}
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_UP) {
stopPlayer(0, 0);
}
return true;
}
});
}

private void addPlayer() {

final FixtureDef fixtureDef = PhysicsFactory.createFixtureDef(2, 0.2f, 0f);
playerSprite = ResourceUtil.getInstance(getBaseActivity()).getAnimatedSprite("main.png", 1, 2);
playerSprite.setPosition(camera.getWidth() / 2, camera.getHeight() – camera.getHeight() / 4);
playerSprite.setRotation(playerSprite.getRotation() + 90);
playerBody = PhysicsFactory.createCircleBody(this.physicsWorld, playerSprite, BodyDef.BodyType.KinematicBody, fixtureDef);

this.physicsWorld.registerPhysicsConnector(new PhysicsConnector(playerSprite, playerBody, true, false));

this.attachChild(playerSprite);
}

public void movePlayer(float x, float y){
final Body body = playerBody;

Vector2 velocity = null;
if (null != playerSprite) {
if(x > 0 && camera.getWidth() – 2 < playerSprite.getX() + playerSprite.getWidth()){
x = 0;
}
if(x < 0 && playerSprite.getX() < 2){
x = 0;
}
if(y > 0 && camera.getHeight() – 2 < playerSprite.getY() + playerSprite.getHeight()){
y = 0;
}
if(y < 0 && playerSprite.getY() < 2){
y = 0;
}

velocity = Vector2Pool.obtain(x * 5, y * 5);
}

body.setLinearVelocity(velocity);
Vector2Pool.recycle(velocity);

final float rotationInRad = (float)Math.atan2(-x, y);
playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);

playerSprite.setRotation(MathUtils.radToDeg(rotationInRad – 100));
}

public void stopPlayer(float x, float y){
final Body body = playerBody;

Vector2 velocity = null;
if (null != playerSprite) {
velocity = Vector2Pool.obtain(x, y);
}

body.setLinearVelocity(velocity);
Vector2Pool.recycle(velocity);

final float rotationInRad = (float)Math.atan2(-x, y);
playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);

playerSprite.setRotation(MathUtils.radToDeg(rotationInRad + 90));
}

// 2点間の角度を求める
private static double getAngleByTwoPosition(float[] start, float[] end) {
double result = 0;

float xDistance = end[0] – start[0];
float yDistance = end[1] – start[1];

result = Math.atan2((double) yDistance, (double) xDistance) * 180
/ Math.PI;
result += 180;

return result;
}

// 2点間の移動距離を求める
private static double getDistanceTwoPosition(float[] start, float[] end){
float xDistance = end[0] – start[0];
float yDistance = end[1] – start[1];

double distance = Math.sqrt(Math.pow(xDistance, 2)
+ Math.pow(yDistance, 2));

return distance;
}
[/java]
>addPlayer()
ボールと同じようにSpriteを設定します。
画面操作により移動させたいので「BodyDef.BodyType.KinematicBody」を利用します。

>this.setOnSceneTouchListener(new IOnSceneTouchListener()
画面のタップイベントのリスナーを登録します。
・画面がタッチされた時の処理
 【MotionEvent.ACTION_DOWN】:タッチした時点の座標を保持します。
 
・画面がタッチ後移動された時の処理
 【MotionEvent.ACTION_MOVE】:
 >if (endXY[0] > 150) {
 タッチした時点とタッチ後に移動された地点の差が150以上であれば、150固定とする。
 ※これは移動対象のPlayerが異様に早く移動しないようにするため、移動距離判定の値の上限を設けてます。
 
 >getAngleByTwoPosition
 移動距離を算出するための角度を算出します。
 
 >getDistanceTwoPosition
 2点間の座標から移動距離を算出します。
 
 >movePlayer
 算出したX,Yの移動量を元にPlayerを移動します。
 
 >if(x > 0 && camera.getWidth() – 2 < playerSprite.getX() + playerSprite.getWidth()){  端まで来た時に壁をすり抜けないように、x軸が端まできたらx軸の移動量を0とします。  ※Y軸も同じ。    >velocity = Vector2Pool.obtain(x * 5, y * 5);
端まできてない場合は、移動量を元に移動(*5の部分は実際の画面の移動をみて調整する。)

>final float rotationInRad = (float)Math.atan2(-x, y);
>playerBody.setTransform(playerBody.getWorldCenter(), rotationInRad);
>playerSprite.setRotation(MathUtils.radToDeg(rotationInRad + 100));
移動する方向から画像の向きを変更するための角度を求めてセットします。

・画面から指が離されたときの処理MotionEvent.ACTION_UP
 この処理をしないと指を離しても直前の移動距離にしたがって壁を突き抜けて移動してしまった。。。
 ので、移動を終了するための処理を実装。
 >stopPlayer
 「movePlayer」と同じような感じですが、移動量を{0,0}にして移動をとめます。

まとめ

これで、無重力状態の画面で、ボールがPlayerの衝突により跳ね飛ばされる動きの実装ができました。
まだ、ボールの跳ね返り具合や、移動速度などの問題があるので、そこら辺は今後微調整します。

vol6_1

vol6_2

{ Add a Comment }

シューティングゲーム開発_Vol4



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

前回までは
・機体表示
・機体から弾発射
・機体操作のコントローラ
・機体からの弾一旦停止ボタン
・敵を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なかなか便利ですねー
色々勉強してもっと色んな事できるようになりたいなー

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

{ Add a Comment }

シューティングゲーム開発_Vol3



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

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

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

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

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

{ Add a Comment }

ブロック崩し&シューティングゲーム案



私は最近スマホアプリ開発に興味をもっていて
会社の勉強会や家でこっそりアプリを作っているわけですが。

よけろ!ききいっぱつ!:会社の勉強会で開発したアプリ
よけろ!かんいっぱつ!:家で開発したアプリ

上記二つは既に開発が完了しリリースもしています。

んで、次に「自分で2機操作するシューティング」を”会社の勉強会”で開発しようと考えています。

私は飽きっぽいので、一つのアプリだけの開発に時間がかかっていると
モチベーションが下がってきてしまうので
会社の勉強会のアプリと家で作るアプリの2本立てで進めて行こうと思ってます。

次に開発する”会社の勉強会”のアプリは決まっているので
家で作るアプリは何にしようかと考えていました。

考えた結果、、

「ブロック崩しとシューティングを混ぜたゲームにしよう!」

と思いついたので、
「自分で2機操作するシューティング」
と平行して
「ブロック崩し&シューティング」
を進めて行こうと思います!

ざっくりとした仕様ですが下記を考えています。

——
仮タイトル:バトルサッカープレイヤー
仮仕様:
・横画面
・ゲームテーマ:サッカー、シューティング、ブロック崩し
・サッカー選手を操作する。
 操作方法は、タップした時点とドラッグしている箇所の位置関係から、サッカー選手を上下左右に操作。
・敵は、野球・バスケ・テニスなどのプレイヤー(上部に出現)
 インベーダーのように横移動しながら、ボールで攻撃してくる。
・サッカー選手はサッカーボールを操作する。
 サッカーボールをブロック崩しのように跳ね返して操作
・アイテムの実装
 ボールや移動スピードがあがったり、大きくなったり、ボールが増えたりする。ライフのアイテムなど。
・必殺ボタン
・敵を全て倒したら後からどんどんでてくる。
——

基本的な仕様はこんな感じで、割と今持っている知識とAndengineを使用すればできそうかなと考えています。

不安要素は
・キャラデザイン
・サッカー選手にサッカーボールが当たって跳ね返す際の角度

「キャラデザイン」は今は自分でデザインできないので、フリー素材から探そうと思いますが
アニメーションになっているものがほぼ無いのと、イメージに合う画像があるかが問題ですね。
誰か無料でキャラデザインしてくれないかなー
自分ができるようになるのが一番いいんだけど。。。

「サッカー選手にサッカーボールが当たって跳ね返す際の角度」
これはAndengineの物理演算系のライブラリ(AndEnginePhysicsBox2DExtension?)を使えばできるかな。
当たった位置によって跳ね返す角度が想定どおりになればいいんだけど。。
まぁ、これは実装してみてから考えますか。

と、こんな感じで考えてます。

物理演算系のライブラリは使えるようになると、色んな発想が生まれそうですね。
落ちもの系とか、積み重ね系とかとか。

「自分で2機操作するシューティング」/「ブロック崩し&シューティング」
と二つともシューティング系ですが、操作方法・実装方法はかなり違うと思うので
飽きずにやれるといいなー。

まだまだ色々作りたいアプリあるけど、時間とスキルが追いついて行かないですね。。。

また、随時状況の報告をしていきます。

{ Add a Comment }

シューティングゲーム開発_Vol2



前回の記事を見直したら、ソース記述しただけで
特に説明を記載してませんでした。。

すみません。

攻撃実装

今回は2機のミサイルと、ミサイルの移動を実装します。

■MainScene
[java]
@Override
public void init(){

・・・省略・・・

weponMainLeftSprite();
weponMainRightSprite();
registerUpdateHandler(timerHandler);

}

// ※2
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);
}
}
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);
}
}
weponRightList.removeAll(removeRightWepon);
}
});

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

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

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

weponLeftList.add(rect);

weponMainLeftSprite();
}

@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) {

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

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

weponRightList.add(rect);

weponMainRightSprite();
}

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

private float convertRGB(int val){
return val / 255f;
}

[/java]

■説明!!
※1
initメソッドからweponMainLeftSprite / weponMainRightSpriteを呼び出します。
weponMainLeftSpriteメソッドで、
・mainLeftSprite.registerEntityModifier(Spriteへの変更処理を設定します。)
・SequenceEntityModifier(指定した変更処理を順番に処理するように設定します。)
・DelayModifier(0.5f, new IEntityModifier.IEntityModifierListener(0.5秒たってからメソッド内の処理をするように設定します。)

// 5dp四方の画像を描画します。
final Rectangle rect = new Rectangle(mainLeftSprite.getX() + mainLeftSprite.getWidth(), mainLeftSprite.getY(), 5, 5, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect);

// 色を指定
rect.setColor(convertRGB(200), convertRGB(200), convertRGB(200));

// ポジションの指定をします。
// 【mainLeftSprite.getX() + mainLeftSprite.getWidth() / 2】でSpriteのX座標の中央値を指定します。
rect.setPosition(mainLeftSprite.getX() + mainLeftSprite.getWidth() / 2, mainLeftSprite.getY());
rect.setZIndex(10);

// 機体のミサイルをリストで保持します。(ミサイルの画像を移動するのに使用します。)
weponLeftList.add(rect);

// 再帰的に呼び出して常に0.5秒事にミサイルを発射するようにする。
weponMainLeftSprite();

※2
initメソッドからtimerHandlerをregisterUpdateHandlerにセットして毎秒60回(1 / 60f)呼び出すようにします。
ここのメソッドで敵(未実装)の移動やミサイルの移動、あたり判定を行う予定です。

[java]
// 削除用のリストを用意(for文で回している最中に要素を削除するとエラーとなるので削除対象を保持する)
List<Rectangle> removeLeftWepon = new ArrayList<Rectangle>();

// 左機のミサイルを移動する。画面から外れたミサイルは削除する。
for(Rectangle rect : weponLeftList){
// 画面上部から-10のところまでミサイルがきたら不要なので、削除用リストに保持
if(rect.getY() < -10){
removeLeftWepon.add(rect);
// 画像をクリア
rect.detachSelf();
rect.dispose();
}else {
// 画像を移動する。上方向に-3の速度で移動
rect.setPosition(rect.getX(), rect.getY() – 3);
}
}
// 削除対象をリストから除去
weponLeftList.removeAll(removeLeftWepon);
[/java]

■上記の処理をすると2機からミサイルが発射されます!

こんな感じ
wepon

次回は自動で発射されるミサイルの一時停止と必殺ボタンを実装したいと思います。

{ Add a Comment }

シューティングゲーム開発_Vol1



傾きセンサーを使用した2つのゲームは、ほぼ作り終えたので。
次はシューティングゲームを作ってみようと思います!

タイトルは
【ツインスペースヒーロー!!】

Andengineの「AnalogOnScreenControl」を2つ配置して

二つの機体を同時に操作するシューティングです。
今回はコントローラの配置します。
※ここで記載する内容は「AndEngineでつくるAndroid 2Dゲーム」の参考書の
プロジェクト構成を前提としています。

[java]
public class MainActivity extends MultiSceneActivity implements
SensorEventListener {

// 画面のサイズ。
private int CAMERA_WIDTH = 800;
private int CAMERA_HEIGHT = 480;

public static Camera camera;

public EngineOptions onCreateEngineOptions() {
this.camera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);

EngineOptions eo = new EngineOptions(true,
ScreenOrientation.LANDSCAPE_FIXED,
new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), camera);

// ふたつコントローラを使用するのでマルチタッチの設定をする。
eo.getTouchOptions().setNeedsMultiTouch(true);
}
}

public class MainScene extends KeyListenScene implements
ButtonSprite.OnClickListener {
private BitmapTextureAtlas mOnScreenControlTexture;
private ITextureRegion mOnScreenControlBaseTextureRegion;
private ITextureRegion mOnScreenControlKnobTextureRegion;

private AnimatedSprite mainLeftSprite;
private AnimatedSprite mainRightSprite;

@Override
public void init(){
// 背景設定
setBackground(new Background(convertRGB(0), convertRGB(0), convertRGB(0)));

// 左側のコントローラエリア作成
final Rectangle rect = new Rectangle(0, 0, 150, 480, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect);

rect.setColor(convertRGB(50), convertRGB(50), convertRGB(50));
rect.setPosition(0, 0);
rect.setZIndex(controlAreaZIndex);
sortChildren();

// 右側のコントローラエリア作成
final Rectangle rect2 = new Rectangle(700, 0, 150, 480, getBaseActivity().getVertexBufferObjectManager());
attachChild(rect2);

rect2.setColor(convertRGB(50), convertRGB(50), convertRGB(50));
rect2.setPosition(650, 0);
rect2.setZIndex(controlAreaZIndex);
sortChildren();

// コントローラの画像登録
this.mOnScreenControlTexture = new BitmapTextureAtlas(getBaseActivity().getTextureManager(), 256, 128, TextureOptions.BILINEAR);
this.mOnScreenControlBaseTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mOnScreenControlTexture, getBaseActivity(), "onscreen_control_base.png", 0, 0);
this.mOnScreenControlKnobTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mOnScreenControlTexture, getBaseActivity(), "onscreen_control_knob.png", 128, 0);
this.mOnScreenControlTexture.load();

// コントローラ描画メソッド
drawAnalogControls();
}

// コントローラとコントローラの操作対象の機体を描画
private void drawAnalogControls(){

// 左の機体を描画
mainLeftSprite = getBaseActivity().getResourceUtil().getAnimatedSprite("main1.png", 1, 2);
mainLeftSprite.setZIndex(mainZIndex);
mainLeftSprite.setPosition(200, 450);

attachChild(mainLeftSprite);

// 左の機体操作用のハンドラー
final PhysicsHandler physicsHandler1 = new PhysicsHandler(mainLeftSprite);
mainLeftSprite.registerUpdateHandler(physicsHandler1);

// 右の機体を描画
mainRightSprite = getBaseActivity().getResourceUtil().getAnimatedSprite("main2.png",1,2);
mainRightSprite.setZIndex(mainZIndex);
mainRightSprite.setPosition(600, 450);

attachChild(mainRightSprite);

// 右の機体操作用のハンドラー
final PhysicsHandler physicsHandler2 = new PhysicsHandler(mainRightSprite);
mainRightSprite.registerUpdateHandler(physicsHandler2);

sortChildren();

// leftControl
final float x1 = 20;
final float y1 = getBaseActivity().getEngine().getCamera().getHeight() – this.mOnScreenControlBaseTextureRegion.getHeight() – 30;
final AnalogOnScreenControl leftControl = new AnalogOnScreenControl(x1, y1, MainActivity.camera, this.mOnScreenControlBaseTextureRegion, this.mOnScreenControlKnobTextureRegion, 0.1f, getBaseActivity().getVertexBufferObjectManager(), new AnalogOnScreenControl.IAnalogOnScreenControlListener() {
@Override
public void onControlChange(final BaseOnScreenControl pBaseOnScreenControl, final float pValueX, final float pValueY) {

// 機体の移動制限
System.out.println("mainLeftSprite:" + mainLeftSprite.getX() + "/" + mainLeftSprite.getY());
boolean backMove = false;
if(mainLeftSprite.getX() &lt; 155) {
physicsHandler1.setVelocity(50, 0);
backMove = true;
}
if(645 &lt; mainLeftSprite.getX() + mainLeftSprite.getWidth()){
physicsHandler1.setVelocity(-50, 0);
backMove = true;
}
if(mainLeftSprite.getY() &lt; 5) {
physicsHandler1.setVelocity(0, 50);
backMove = true;
}
if(475 &lt; mainLeftSprite.getY() + mainLeftSprite.getHeight()){
physicsHandler1.setVelocity(0, -50);
backMove = true;
}

// 機体の移動
if(!backMove){
physicsHandler1.setVelocity(pValueX * 100, pValueY * 100);
}
}

@Override
public void onControlClick(final AnalogOnScreenControl pAnalogOnScreenControl) {
/* Nothing. */
}
});
leftControl.getControlBase().setBlendFunction(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
leftControl.getControlBase().setAlpha(0.5f);

this.setChildScene(leftControl);

// rightControl
final float y2 = y1;
final float x2 = getBaseActivity().getEngine().getCamera().getWidth() – this.mOnScreenControlBaseTextureRegion.getWidth() – 20;
final AnalogOnScreenControl rightControl = new AnalogOnScreenControl(x2, y2, MainActivity.camera, this.mOnScreenControlBaseTextureRegion, this.mOnScreenControlKnobTextureRegion, 0.1f, getBaseActivity().getVertexBufferObjectManager(), new AnalogOnScreenControl.IAnalogOnScreenControlListener() {
@Override
public void onControlChange(final BaseOnScreenControl pBaseOnScreenControl, final float pValueX, final float pValueY) {
System.out.println("mainRightSprite:" + mainRightSprite.getX() + "/" + mainRightSprite.getY());
boolean backMove = false;
if(mainRightSprite.getX() &lt; 155) {
physicsHandler2.setVelocity(50, 0);
backMove = true;
}
if(645 &lt; mainRightSprite.getX() + mainRightSprite.getWidth()){
physicsHandler2.setVelocity(-50, 0);
backMove = true;
}
if(mainRightSprite.getY() &lt; 5) {
physicsHandler2.setVelocity(0, 50);
backMove = true;
}
if(475 &lt; mainRightSprite.getY() + mainRightSprite.getHeight()){
physicsHandler2.setVelocity(0, -50);
backMove = true;
}
if(!backMove){
physicsHandler2.setVelocity(pValueX * 100, pValueY * 100);
}
}

@Override
public void onControlClick(final AnalogOnScreenControl pAnalogOnScreenControl) {
/* Nothing. */
}
});
rightControl.getControlBase().setBlendFunction(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
rightControl.getControlBase().setAlpha(0.5f);

leftControl.setChildScene(rightControl);
}

private float convertRGB(int val){
return val / 255f;
}
}

[/java]

vol5_2

※重要な部分のみコード記載してますので、変数宣言とか一部抜けているかも知れませんがご了承。。

次回以降も随時シューティングゲームの途中経過を記載していきます!

{ Add a Comment }