Browsing: ブロック崩し

ブロック崩し&シューティングゲームVol5_ステージ選択解放処理&広告表示



前回までは
・画面の重力制御
・ボールの表示
・プレイヤーの移動&移動方向の補助線
・ゲームクリア
・ゲームオーバー
・制限時間
・敵の実装
 キーパー
 ディフェンダー
 ディフェンダー2
・当たり判定
・クラッシュエフェクト
・ステージセレクト

今回はステージ解放の条件と広告の実装をします。
※AdMobのダミー広告を表示します。
 AdMobのSDKの導入が既に済んでいる想定の説明です。

ステージ解放

前回までのステージの実装だと、無条件で選択できてしまいます。
ゲームのやりこみ性をあげるために、クリアしたらどんどんステージが解放されるようにしましょう。

クリアしたステージ情報の保持

[java]
MainScene.java

private void showGameClear(){
long clearStage = SPUtil.getInstance(getBaseActivity()).getClearStage();
if(stage > clearStage){
SPUtil.getInstance(getBaseActivity()).setClearStage(stage);
}

・・・省略・・・
}
[/java]
[java]
package org.geex.battlesoccerplayer;

import android.content.Context;
import android.content.SharedPreferences;

public class SPUtil {
// 自身のインスタンス
private static SPUtil instance;

// シングルトン
public static synchronized SPUtil getInstance(Context context) {
if (instance == null) {
instance = new SPUtil(context);
}
return instance;
}

private static SharedPreferences settings;
private static SharedPreferences.Editor editor;

private SPUtil(Context context) {
settings = context.getSharedPreferences("shared_preference_1.0", 0);
editor = settings.edit();
}

public long getClearStage() {
return settings.getLong("ClearStage", 0);
}
public void setClearStage(long value) {
editor.putLong("ClearStage", value);
editor.commit();
}
}
[/java]
showGameClear時に、既に設定してあるステージクリア情報より大きい場合に更新するようにします。
(getClearStage/setClearStage)
※SPUtilの仕組みも「AndEngineでつくるAndroid 2Dゲーム」に記載があります。

[java]
StageSelectScene.java

@Override
public void init() {

if (0 <= clearStage) {

long clearStage = SPUtil.getInstance(getBaseActivity()).getClearStage();

if (1 <= clearStage) {
ButtonSprite stage2Btn = getBaseActivity().getResourceUtil()
.getButtonSprite("selectStage2.png", "selectStage2_2.png");
stage2Btn.setPosition((camera.getWidth() / 4) * 2 – (stage2Btn.getWidth() / 2), line1_height);
stage2Btn.setTag(TAG_BTN2);
stage2Btn.setOnClickListener(this);
attachChild(stage2Btn);
registerTouchArea(stage2Btn);
} else {
Sprite stage2Btn = getBaseActivity().getResourceUtil().getSprite("selectStage2_3.png");
stage2Btn.setPosition((camera.getWidth() / 4) * 2 – (stage2Btn.getWidth() / 2), line1_height);
attachChild(stage2Btn);
}

・・・省略・・・
}
[/java]
保持しているステージクリア情報を取得して、各ステージボタン表示の判定をしていきます。
>if (1 <= clearStage) { ステージクリア情報が「1」以上の場合、ステージ「②」のボタンをボタンスプライトで表示します。 elseの場合はただのスプライトで表示して、クリックしても意味がないようにします。 これを各ステージボタン毎に設定します。 そうすると、前ステージをクリアしていない場合は、ステージ選択できないようにする制御が可能になります。
vol6_10

広告の実装

トップ画面の下部とステージ画面の真ん中に広告を表示します。

activity_main.xmlにLinearLayoutで画面下部と画面中央に広告表示領域を確保します。

トップページの広告

[java]
MainActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

addAdView();
}

public void addAdView(){
if(null == adView) {
adView = new AdView(this);
adView.setAdUnitId("ca-app-pub-3940256099942544/6300978111"); // テスト用ID
adView.setAdSize(AdSize.BANNER);

layout_ad = (LinearLayout) findViewById(R.id.banner1);
layout_ad.addView(adView);

//AdRequest adRequest = new AdRequest.Builder().build();

AdRequest adRequest = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR) // エミュレータ
.build();

adView.loadAd(adRequest);
}
}

public void removeAdView(){
layout_ad.removeView(adView);
adView = null;
}
[/java]

上記でトップ画面表示時に広告が表示できました。

ただし、このままだと画面遷移しても広告が表示されっぱなしになるので
InitialSceneで画面遷移時に広告の表示を削除します。
[java]
getBaseActivity().runOnUiThread(new Runnable(){
@Override
public void run() {
((MainActivity)getBaseActivity()).removeAdView();
}
});
[/java]

ステージ選択画面の広告

[java]
MainActivity.java

public void addAdView2(){
if(null == adView2) {
adView2 = new AdView(this);
adView2.setAdUnitId("ca-app-pub-3940256099942544/6300978111"); // テスト用ID
adView2.setAdSize(AdSize.BANNER);

layout_ad = (LinearLayout) findViewById(R.id.banner2);
layout_ad.addView(adView2);

//AdRequest adRequest = new AdRequest.Builder().build();

AdRequest adRequest = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR) // エミュレータ
.addTestDevice("333B0DEDC6F2112DF53F6C3579DCD5DB") // テストデバイス
.build();

adView2.loadAd(adRequest);
}
}

public void removeAdView2(){
layout_ad.removeView(adView2);
adView2 = null;
}

[/java]
[java]
StageSelectScene.java

@Override
public void init() {
getBaseActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
((MainActivity) getBaseActivity()).addAdView2();
}
});

・・・省略・・・
}

public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX,
float pTouchAreaLocalY) {

getBaseActivity().runOnUiThread(new Runnable(){
@Override
public void run() {
((MainActivity)getBaseActivity()).removeAdView2();
}
});

・・・省略・・・
}
[/java]
StageSelectSceneのinitメソッドで広告の表示をするために
addAdView2を呼び出します。
画面遷移時にremoveAdView2を呼び出します。

これで、トップ画面・ステージ選択画面に広告が表示されるようになりました。

vol6_9

vol6_10

実際にリリースする際には、「ca-app-pub-3940256099942544/6300978111」のテスト用IDを
AdMobで実際に発行した広告枠のIDに変更する必要があります。

ほぼアプリの機能が完成しました!

後は、音楽とGoogleアナリティクスの設定と画像をちゃんとしたものに差し替えを行います。
ゲームクリア時の文字の表示方法もできれば変えたい。

そろそろアプリリリースに近づいてきました!
最後までがんばるぞー

{ Add a Comment }

ブロック崩し&シューティングゲームVol5_ステージ選択画面実装



前回までは
・画面の重力制御
・ボールの表示
・プレイヤーの移動&移動方向の補助線
・ゲームクリア
・ゲームオーバー
・制限時間
・敵の実装
 キーパー
 ディフェンダー
 ディフェンダー2
・当たり判定
・クラッシュエフェクト

今回はステージ選択画面を作成し、各ステージ毎に敵の配置を変えます。

ステージ選択

まず初めに、Secneを作成し、シーンの遷移を制御します。
現在は、MainActivity→InitialScene→MainSecne
修正後は、MainActivity→InitialScene→StageSelectScene→MainSecne
のようなシーンにします。

StageSelectSceneを作成

[java]
package org.geex.battlesoccerplayer;

import java.awt.Font;
import java.awt.event.KeyEvent;
import java.io.IOException;

public class StageSelectScene extends KeyListenScene implements
ButtonSprite.OnClickListener {

private Font font;

private Camera camera;

private static final int TAG_BTN1 = 1;
private static final int TAG_BTN2 = 2;
private static final int TAG_BTN3 = 3;

public StageSelectScene(MultiSceneActivity context) {
super(context);
init();
}

@Override
public void init() {
camera = getBaseActivity().getEngine().getCamera();

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

Text title = new Text(0, 0, this.font,
"ステージセレクト" +
"", new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
title.setPosition(camera.getWidth() / 2 – title.getWidth() / 2, 100);
attachChild(title);

Sprite bg = getBaseActivity().getResourceUtil().getSprite(
"stageSelect.png");
bg.setPosition(0, 0);
attachChild(bg);

Sprite sprite = getBaseActivity().getResourceUtil().getSprite("pcman.png");
sprite.setPosition(20, 50 );
sprite.setScale(1.3f);
attachChild(sprite);

Sprite spriteTip = getBaseActivity().getResourceUtil().getSprite("stageSelectTip.png");
spriteTip.setPosition(150, 60);
spriteTip.setScale(1.5f);
attachChild(spriteTip);

int line1_height = 180;

ButtonSprite stage1Btn = getBaseActivity().getResourceUtil()
.getButtonSprite("selectStage1.png", "selectStage1_2.png");
stage1Btn.setPosition((camera.getWidth() / 4) – (stage1Btn.getWidth() / 2), line1_height);
stage1Btn.setTag(TAG_BTN1);
stage1Btn.setOnClickListener(this);
attachChild(stage1Btn);
registerTouchArea(stage1Btn);

ButtonSprite stage2Btn = getBaseActivity().getResourceUtil()
.getButtonSprite("selectStage2.png", "selectStage2_2.png");
stage2Btn.setPosition((camera.getWidth() / 4) * 2 – (stage2Btn.getWidth() / 2), line1_height);
stage2Btn.setTag(TAG_BTN2);
stage2Btn.setOnClickListener(this);
attachChild(stage2Btn);
registerTouchArea(stage2Btn);

ButtonSprite stage3Btn = getBaseActivity().getResourceUtil()
.getButtonSprite("selectStage3.png", "selectStage3_2.png");
stage3Btn.setPosition((camera.getWidth() / 4) * 3 – (stage3Btn.getWidth() / 2), line1_height);
stage3Btn.setTag(TAG_BTN3);
stage3Btn.setOnClickListener(this);
attachChild(stage3Btn);
registerTouchArea(stage3Btn);
}

public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX,
float pTouchAreaLocalY) {

ResourceUtil.getInstance(getBaseActivity()).resetAllTexture();
KeyListenScene scene = new MainScene(getBaseActivity(), MainActivity.camera, pButtonSprite.getTag());
// MainSceneへ移動
getBaseActivity().getEngine().setScene(scene);
// 遷移管理用配列に追加
getBaseActivity().appendScene(scene);

}
}
[/java]
特に難しい事はしてません。
ボタンを配置して、クリックイベントを拾ってMainSceneに移動しているだけ。
MainSceneのコンテキストで、どのステージのボタンが押下されたかの情報を渡しています。(pButtonSprite.getTag())

InitialSceneから画面遷移する際に、MainSceneをセットしていたのをStageSelectSceneに変更します。
※この当たりは「AndEngineでつくるAndroid 2Dゲーム」に記載があるので割愛します。

[java]
private void setStageData(int stage){

if(1 == stage){
currentTime = 30;
}else if(2 == stage){
currentTime = 20;
addEnemyPlayer(camera.getWidth() / 4 – 25, camera.getHeight() / 4);
addEnemyPlayer(camera.getWidth() / 2 – 25, camera.getHeight() / 4);
addEnemyPlayer2(camera.getWidth() – (camera.getWidth()) / 4 – 25, camera.getHeight() / 4);
}else if(3 == stage){
currentTime = 20;
addEnemyKeeper(camera.getWidth() / 2, 50);
}
}
[/java]
setStageDataメソッドで各ステージの設定を行います。
※initメソッドから呼び出しています。

currentTime:制限時間
addEnemyPlayer:ディフェンダーの位置を指定して配置
addEnemyPlayer2:ディフェンダー2の位置を指定して配置
addEnemyKeeper:ディフェンダー2の位置を指定して配置

ステージ毎に配置を変えるだけで、たくさんのステージが増やせますね!

vol6_8

今回は、Andengineの基本の部分の画面遷移が主だったので、かなり説明を簡単にしてます。
分からないことがあれば気軽にコメントください。

広告表示エリアが勢いあまって表示されてますが、それは次回記載します。

次回は
・ステージ解放の条件
・広告実装

おたのしみにー

{ Add a Comment }

ブロック崩し&シューティングゲームVol4_敵キャラの実装&当たり判定



前回までは
・画面の重力制御
・ボールの表示
・プレイヤーの移動&移動方向の補助線
・ゲームクリア
・ゲームオーバー
・制限時間

今回は敵キャラの実装と、その当たり判定をします。

敵キャラの実装

まず初めに、敵キャラの種類ですが3パターン考えています。
・ディフェンダー1
 ※移動しない/ボールが当たると消える。
・ディフェンダー2
 ※移動しない/ボールが当たっても消えない。
・キーパー
 ※左右に移動/ボールが当たっても消えない。

これらの敵の配置などを調整してステージにバリエーションをつけようと思います。

[java]
MainScene.java

private float keeperMoveX = 5;

private void addEnemyPlayer(float x, float y) {
final FixtureDef fixtureDef = PhysicsFactory.createFixtureDef(2, 0.2f, 0f);
AnimatedSprite enemyPlayerSprite = ResourceUtil.getInstance(getBaseActivity()).getAnimatedSprite("enemy1.png", 1, 2);
enemyPlayerSprite.setPosition(x, y);
enemyPlayerSprite.setRotation(enemyPlayerSprite.getRotation() – 90);
enemyPlayerSprite.setTag(TAG_ENEMYPLAYER);
Body enemyPlayerBody = PhysicsFactory.createCircleBody(this.mPhysicsWorld, enemyPlayerSprite, BodyDef.BodyType.StaticBody, fixtureDef);
enemyPlayerBody.setUserData(enemyPlayerSprite);

this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(enemyPlayerSprite, enemyPlayerBody, true, false));

this.attachChild(enemyPlayerSprite);
}

private void addEnemyPlayer2(float x, float y) {
final FixtureDef fixtureDef = PhysicsFactory.createFixtureDef(2, 0.2f, 0f);
AnimatedSprite enemyPlayerSprite = ResourceUtil.getInstance(getBaseActivity()).getAnimatedSprite("enemy2.png", 1, 2);
enemyPlayerSprite.setPosition(x, y);
enemyPlayerSprite.setRotation(enemyPlayerSprite.getRotation() – 90);
enemyPlayerSprite.setTag(TAG_ENEMYPLAYER2);
Body enemyPlayerBody = PhysicsFactory.createCircleBody(this.mPhysicsWorld, enemyPlayerSprite, BodyDef.BodyType.StaticBody, fixtureDef);
enemyPlayerBody.setUserData(enemyPlayerSprite);

this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(enemyPlayerSprite, enemyPlayerBody, true, false));

this.attachChild(enemyPlayerSprite);
}

private void addEnemyKeeper(float x, float y) {
final FixtureDef fixtureDef = PhysicsFactory.createFixtureDef(2, 0.2f, 0f);
AnimatedSprite enemyKeeperSprite = ResourceUtil.getInstance(getBaseActivity()).getAnimatedSprite("enemy3.png", 1, 2);
enemyKeeperSprite.setPosition(x, y);
enemyKeeperSprite.setRotation(enemyKeeperSprite.getRotation() – 90);
enemyKeeperSprite.setTag(TAG_KEEPER);
keeperBody = PhysicsFactory.createCircleBody(this.mPhysicsWorld, enemyKeeperSprite, BodyDef.BodyType.KinematicBody, fixtureDef);
keeperBody.setUserData(enemyKeeperSprite);

this.mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(enemyKeeperSprite, keeperBody, true, false) {
@Override
public void onUpdate(float pSecondsElapsed) {
if (!isEnd && null != keeperBody) {
super.onUpdate(pSecondsElapsed);
Sprite sprite = (Sprite) keeperBody.getUserData();
if (TAG_KEEPER == sprite.getTag()) {
if (sprite.getX() < camera.getWidth() / 3) {
System.out.println("X:" + (sprite.getX() – playerSprite.getWidth() / 2));
keeperMoveX = 5;
} else if (sprite.getX() + playerSprite.getWidth() > (camera.getWidth() / 3) * 2) {
System.out.println("X:" + sprite.getX());
keeperMoveX = -5;
}
keeperBody.setLinearVelocity(keeperMoveX, 0);
}
}
}
}
);

this.attachChild(enemyKeeperSprite);
}
[/java]
前回までに説明している「addPlayer」メソッドとほぼ同じですね。
>addEnemyPlayer
このキャラは動かないので「BodyDef.BodyType.StaticBody」で定義します。
setTagメソッドで「TAG_ENEMYPLAYER」を設定します。

>addEnemyPlayer2
これも同じ。
setTagメソッドで「TAG_ENEMYPLAYER2」を設定します。

>addEnemyKeeper
これも同じ。
setTagメソッドで「TAG_KEEPER」を設定します。
「onUpdate」メソッドをオーバーライドして、キーパーの移動を制御します。
キーパースプライトのX座標がカメラ幅の3分の1より小さかったらX座標の移動力を「5」にします。
3分の2より大きかったらX座標の移動力を「-5」にします。

X座標の移動力を5⇔-5を入れ替える事により、左右に移動するようになります。

[java]

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

this.mPhysicsWorld.setContactListener(createContactListener());

・・・省略・・・
}

private ContactListener createContactListener(){
ContactListener contactListener = new ContactListener(){
@Override
public void beginContact(Contact contact){

}

@Override
public void endContact(Contact contact){
final Fixture x1 = contact.getFixtureA();
final Fixture x2 = contact.getFixtureB();

if (x1.getBody().getUserData() != null && x2.getBody().getUserData() != null){
Sprite sprite1 = (Sprite)x1.getBody().getUserData();
Sprite sprite2 = (Sprite)x2.getBody().getUserData();

// 1:player, 2:enemyPlayer, 3:keeper, 4:ball
if ((TAG_ENEMYPLAYER == sprite1.getTag()
|| TAG_BALL == sprite1.getTag())
&& (TAG_ENEMYPLAYER == sprite2.getTag()
|| TAG_BALL == sprite2.getTag())){
if (TAG_ENEMYPLAYER == sprite1.getTag()){
x1.getBody().setActive(false);

crashAnimation(sprite1);
}
if (TAG_ENEMYPLAYER == sprite2.getTag()){
x2.getBody().setActive(false);

crashAnimation(sprite2);
}
}
}
}

@Override
public void preSolve(Contact contact, Manifold oldManifold){

}

@Override
public void postSolve(Contact contact, ContactImpulse impulse){

}
};
return contactListener;
}

public void crashAnimation(Sprite sprite) {

final Sprite crashSprite = sprite;

crashSprite.registerEntityModifier(
new SequenceEntityModifier(
new FadeOutModifier(0.1f)
,new FadeInModifier(0.1f)
,new FadeOutModifier(0.1f)
,new FadeInModifier(0.1f)
,new FadeOutModifier(0.1f)
)
);

// アニメーション終了後に削除
registerUpdateHandler(new TimerHandler(0.5f, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
crashSprite.detachSelf();
}
}));

[/java]

>this.mPhysicsWorld.setContactListener(createContactListener());
上記処理を設定する事により、Body同士の当たり判定の制御が可能になります。
・beginContact
 Body同士が当たった瞬間に処理する内容
・endContact
 Body同士が当たり、離れる際に処理する内容
・preSolve
 ???未調査
・postSolve
 ???未調査

>final Fixture x1 = contact.getFixtureA();
>final Fixture x2 = contact.getFixtureB();
上記で、衝突したBodyの二つの情報が取得できます。
※どちらに何が入っているかの順番の保障はない?

>if ((TAG_ENEMYPLAYER == sprite1.getTag()
> || TAG_BALL == sprite1.getTag())
> && (TAG_ENEMYPLAYER == sprite2.getTag()
> || TAG_BALL == sprite2.getTag())){
ディフェンダー1とボールの組合せの衝突の場合、処理をする。

>if (TAG_ENEMYPLAYER == sprite1.getTag()){
> x1.getBody().setActive(false);
>
> crashAnimation(sprite1);
>}
ディフェンダー1は当たったら消える仕様なので、
Bodyをactiveではないように設定し、
crashAnimationメソッドを呼び出します。

>crashAnimation
0.1秒毎にフェードイン・アウトを繰り返し、点滅させる。
点滅処理が終わったら、スプライトを削除します。

これで、ボールが当たったらディフェンダー1だけ点滅して消えるようになりました。

試しに、画面に表示して見ます。

vol6_6

こんな感じになりました。
青色の敵がディフェンダー1なので、他の色の敵は当たっても消えません。

次回は、敵の配置を変えて、各ステージを作って行きます。
ステージ選択画面を作成して、ステージを選べるようにします。

{ Add a Comment }

ブロック崩し&シューティングゲームVol3_ゲームクリア/ゲームオーバー



前回までは
・画面の重力制御
・ボールの表示
・プレイヤーの移動&移動方向の補助線

今回はゲームクリア・ゲームオーバーの処理を実装したいと思います。

ゲームクリア/ゲームオーバー

まず初めに、ゲームクリアの定義ですが
・相手ゴールにボールを入れる。

単純ですね。
ただ、今のままだとボールに入ってもボールが跳ね返ってしまうので
ゴールの背中部分だけ壁をなくします。
ついでなので、ゴールポストも作っちゃいましょう。

[java]
MainScene.java

@Override
public void init() {

・・・省略・・・

final VertexBufferObjectManager vertexBufferObjectManager = getBaseActivity().getVertexBufferObjectManager();
final Rectangle ground1 = new Rectangle(0, camera.getHeight() – 2, 170, 2, vertexBufferObjectManager);
final Rectangle ground2 = new Rectangle((camera.getWidth() / 3) * 2, camera.getHeight() – 2, 307, 2, vertexBufferObjectManager);
final Rectangle ground3 = new Rectangle(170, camera.getHeight() – 40, 4, 40, vertexBufferObjectManager);
final Rectangle ground4 = new Rectangle(307, camera.getHeight() – 40, 4, 40, vertexBufferObjectManager);

final Rectangle roof1 = new Rectangle(0, 0, 170, 2, vertexBufferObjectManager);
final Rectangle roof2 = new Rectangle((camera.getWidth() / 3) * 2, 0,307, 2, vertexBufferObjectManager);
final Rectangle roof3 = new Rectangle(170, 0, 4, 40, vertexBufferObjectManager);
final Rectangle roof4 = new Rectangle(307, 0, 4, 40, 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, ground1, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground2, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground3, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, ground4, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof1, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof2, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof3, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, roof4, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, left, BodyDef.BodyType.StaticBody, wallFixtureDef);
PhysicsFactory.createBoxBody(this.mPhysicsWorld, right, BodyDef.BodyType.StaticBody, wallFixtureDef);

roof1.setColor(0, 0, 0);
roof2.setColor(0, 0, 0);
roof3.setColor(0, 0, 0);
roof4.setColor(0, 0, 0);

ground1.setColor(0, 0, 0);
ground2.setColor(0, 0, 0);
ground3.setColor(0, 0, 0);
ground4.setColor(0, 0, 0);

this.attachChild(ground1);
this.attachChild(ground2);
this.attachChild(ground3);
this.attachChild(ground4);
this.attachChild(roof1);
this.attachChild(roof2);
this.attachChild(roof3);
this.attachChild(roof4);
this.attachChild(left);
this.attachChild(right);

・・・省略・・・

}
[/java]
roof1/roof2でゴールポストの絵に合わせたゴール部分だけ開いた壁を作ります。
roof3/roof4で左右のゴールポスト部分の壁を作ります。

色は分かりやすいように一旦黒にしておきましょう。
>roof1.setColor(0, 0, 0);

※自分のゴールも同じようにします。

つづいてゲームクリアの判定処理です。
[java]
// アップデートハンドラー
TimerHandler timerHandler = new TimerHandler(1 / 60f, true, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {

if(isEnd){
return;
}

if(-50 > ballSprite.getY()){

showGameClear();
}
if(camera.getHeight() + 50 < ballSprite.getY()){

showGameOver();
}
if(currentTime <= 0){
showGameOver();
}
}
});
[/java]

1秒間に60回処理するハンドラーを登録します。
ボールのスプライトのY座標が-50より小さい場合、ゲームクリアとします。
すなわち、画面上部にボールが隠れるくらい移動した場合です。

同じように、ゲームオーバー処理も実装します。
画面の高さ+50よりY座標が大きい場合、ゲームオーバーとします。
>if(currentTime <= 0)は制限時間が0になったら終了処理ですね。 制限時間のカウント処理を後述します。 このハンドラーをinit()メソッドで設定します。 >registerUpdateHandler(timerHandler);

ゲームの制限時間

[java]
private void countTime(){

if(isEnd){
return;
}

currentTime–;
currentTimeText.setText("Time " + currentTime);
currentTimeText.setColor(0,0,0);

if(currentTime <= 0){
return;
}

registerUpdateHandler(new TimerHandler(1, new ITimerCallback() {
public void onTimePassed(TimerHandler pTimerHandler) {
countTime();
}
}));
}
[/java]
1秒毎に、自分自身を呼び出すようにして、currentTimeを1ずつマイナスしていきます。
currentTimeをsetTextで画面に表示していくと、毎秒数字がマイナスされた値が表示されます。
このメソッドをinit()でよびだすようにします。

ゲームクリア/ゲームオーバーシーン

[java]
private void showGameClear(){

isEnd = true;

Text text = new Text(0, 0, font, "ゴーーーーーール!!", new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
text.setPosition((camera.getWidth() / 2) – (text.getWidth() / 2), (camera.getHeight() / 2) – (text.getHeight() / 2));
text.setColor(convertRGB(246),convertRGB(6),convertRGB(6));
attachChild(text);

text.registerEntityModifier(new ParallelEntityModifier(new ScaleModifier(3.0f, 0.1f, 1.5f), new RotationByModifier(2.0f, 360)));

physicsClear();

setEndButton();
}

private void showGameOver(){
isEnd = true;

Text text = new Text(0, 0, font, "ゲームオーバー!!", new TextOptions(HorizontalAlign.CENTER), getBaseActivity().getVertexBufferObjectManager());
text.setPosition((camera.getWidth() / 2) – (text.getWidth() / 2), (camera.getHeight() / 2) – (text.getHeight() / 2));
text.setColor(convertRGB(246),convertRGB(6),convertRGB(6));
attachChild(text);

text.registerEntityModifier(new ParallelEntityModifier(new ScaleModifier(3.0f, 0.1f, 1.5f), new RotationByModifier(2.0f, 360)));

physicsClear();

setEndButton();
}

private void setEndButton(){
ButtonSprite btnRetry = getBaseActivity().getResourceUtil()
.getButtonSprite("retry1.png", "retry1.png");
btnRetry.setPosition((camera.getWidth() / 2) – (btnRetry.getWidth() / 2), (camera.getHeight() / 2) + 50);
btnRetry.setTag(TAG_RETRY);
btnRetry.setOnClickListener(this);
attachChild(btnRetry);
registerTouchArea(btnRetry);

ButtonSprite btnMenu = getBaseActivity().getResourceUtil()
.getButtonSprite("stageSelect1.png", "stageSelect1.png");
btnMenu.setPosition((camera.getWidth() / 2) – (btnRetry.getWidth() / 2), (camera.getHeight() / 2) + 150);
btnMenu.setTag(TAG_STAGE);
btnMenu.setOnClickListener(this);
attachChild(btnMenu);
registerTouchArea(btnMenu);
}

private void physicsClear() {
Iterator<Body> localIterator = mPhysicsWorld.getBodies();
while (true) {
if (!localIterator.hasNext()) {
mPhysicsWorld.clearForces();
mPhysicsWorld.clearPhysicsConnectors();
mPhysicsWorld.reset();
return;
}
try {
final Body localBody = (Body) localIterator.next();
Sprite sprite = (Sprite) localBody.getUserData();
if(null == sprite){
continue;
}
sprite.detachSelf();
mPhysicsWorld.destroyBody(localBody);
} catch (Exception localException) {
Debug.e(localException);
}
}
}

public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX,
float pTouchAreaLocalY) {
//btnPressedSound.play();
switch (pButtonSprite.getTag()) {
case TAG_RETRY:
physicsAllClear();
MainScene mainScene = new MainScene(getBaseActivity(), camera, stage);
getBaseActivity().refreshRunningScene(mainScene);
break;
case TAG_STAGE:
physicsAllClear();
StageSelectScene stageSelectScene = new StageSelectScene(getBaseActivity());
getBaseActivity().refreshRunningScene(stageSelectScene);
break;
}
}
[/java]
>showGameClear
上記メソッドでisEndをtrueにします。
このフラグで、各処理でゲーム処理の判定をしているところをスキップするようにします。
それから、ゲームクリア時のメッセージを画面に表示します。
「ゴーーーーーール!!」回転しながら段々大きくなるように表示してます。

>physicsClear
画面に表示されているBodyや画像などをクリアしてます。

>setEndButton
ゲーム終了時の「リトライ」「ステージ選択」などの遷移用の画像を表示します。
※ステージ選択については今後記載します。

>onClick
各ボタンが押下された時の処理です。
リトライは、再度MainSceneをセットして起動します。
ステージは、ステージ選択画面のSceneをセットして起動します。

これで、ゲームクリア・ゲームオーバーの処理ができました!

vol6_3

vol6_4

vol6_5

次回はディフェンダー・キーパーの敵キャラを表示します。

{ Add a Comment }

ブロック崩し&シューティングゲームVol2_画面タッチで線を描画



えー、ゲーム開発自体は色々進んでるのですが、
今回の記事はちょこっとだけ。

ウチの子供にちょっと遊ばせたら、移動の仕方に苦戦していたので、
移動方向の補助線を表示させることにしました。

画面をなぞって線を描画

[java]
MainScene.java

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

private Font font;

public void init() {


省略

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(firstXY, secondXY);

// 移動距離と角度から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));

System.out.println("moveX:" + x);
System.out.println("moveY:" + y);
movePlayer(x, y);

// 矢印削除 ※1
if (null != line) {
line.detachSelf();
}
// 矢印表示
line = new Line(firstXY[0], firstXY[1], secondXY[0], secondXY[1], 10, vertexBufferObjectManager);
line.setColor(convertRGB(80), convertRGB(160), convertRGB(220));
line.setAlpha(convertRGB(200));
attachChild(line);
}
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_UP) {
stopPlayer(0, 0);
// 矢印削除 ※2
if (null != line) {
line.detachSelf();
}
}
return true;
}
});
}

[/java]

※1.画面タッチ時の座標、タッチして移動した後の座標から線を描画
lineが既に描画されていたら、一度消してラインを描画する。
開始、移動座標を指定してLineを生成する。

[java]
// 矢印削除 ※1
if (null != line) {
line.detachSelf();
}

// 矢印表示
line = new Line(firstXY[0], firstXY[1], secondXY[0], secondXY[1], 10, vertexBufferObjectManager);
line.setColor(convertRGB(80), convertRGB(160), convertRGB(220));
line.setAlpha(convertRGB(200));
attachChild(line);
[/java]

※2.画面の手をはなしたら線を削除
[java]
if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_UP) {
stopPlayer(0, 0);

// 矢印削除 ※2
if (null != line) {
line.detachSelf();
}
}
[/java]

これで少しは操作しやすくなったかな?

ってことで、今日は短いですがこんなとこで。

{ 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 }