月別: 2016年7月

ランキング機能実装!(リーダーボード)



さて、今回はいつもと変わって
以前にリリースしたアプリの機能追加についてです。

追加された機能はなんと!

ランキング機能

です!

実は前からランキング機能は組み込みたかったのですが
なかなか。。

無料でランキングのサービスを提供してないか探したのですが
なかなか希望にあうものがなくて。

自前でやるには今かりてるサーバーだとPHPで実装しないといけなかったり。
(PHPあんまり好きじゃなくてw)

だったのですが、ようやくPHPに手をだして実装しました!

Android側では
・ユニークID発行
・ニックネーム入力
・ユーザー情報追加・更新のPHPのリクエスト
・WebViewにPHP側で作成したランキングページを表示

ってとこですかね。

HttpURLConnectionを使用してユーザー情報更新PHPを呼び出すところが
苦戦しました。
POSTでのリクエスト時にパラメータを渡すのがわからなくて。

[java]
OutputStreamWriter osw = new OutputStreamWriter(con.getOutputStream());
BufferedWriter bw = new BufferedWriter(osw);
// POSTのパラメータ
bw.write(param);
bw.close();
osw.close();
[/java]

抜粋してかくと、ここでリクエストしつつパラメータを付与してます。
はまったのが、上記の前にHttpURLConnectionの「con.connect();」をしたので
その後エラーになってました。

まぁ、冷静に調べればわかることだったのですが
ちょっと焦ってましてw

ということで、下記のような感じになりました!

vol4_4
ランキングとニックネームの機能追加。

vol4_5
ランキング押下時にWebViewにランキングページ表示。

これでみんなでスコアを競って遊べますね!

じゃんじゃんあそんでね!

{ Add a Comment }

ブロック崩し&シューティングゲーム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 }