夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【iOS & Android & cocos2d-x 3.4】タッチ操作しないゲーム中に画面ロックを防止する方法

いや〜、今回は苦労しました。
ゲーム部分の移植はほぼ完了したのですが、cocos2d-xが提供していないネイティブ関連の機能をいじる段階になったとたんにググる頻度が急上昇しています。
毎度の事ですが、先輩開発者の皆様のブログやcocos2d-xのオフィシャルフォーラムなどにお世話になりっぱなしです。

さて、今回やりたかったのは、ジャイロ操作中の画面ロックの抑制です。
通常、皆さんのiPhone/iPadAndroidは画面を触っていなければ画面が暗くなり、ロックする設定でになっていますよね?
Androidに移植中のTravelShooting JPというゲームにはタッチ操作を使わず画面の傾きだけで操作するモードがあるので、ゲーム中に突然画面が暗くなると非常にカッコ悪いのです。

Androidの場合、Javaで画面ロックを制御するコードを書く必要があり、C++側からcocos2d-xが提供するJniHelperというクラスを使ってアクセスする仕組みのようです。
iOSの場合はObjective-C++の.mmファイルであればC++のコードの中に普通にObjective-Cのメソッド呼び出しを書く事ができるので楽ですね。

アプリケーションが直接呼び出すインターフェースは、iOSAndroidで共通にできます。

ヘッダファイルNativeBridge.hはこんな感じになりました。

#ifndef __TravelShooting__NativeBridge__
#define __TravelShooting__NativeBridge__

#include "cocos2d.h"

class NativeBridge
{
public:
    static void keepBackLightOn(bool keep);

};

#endif /* defined(__TravelShooting__NativeBridge__) */

iOS用にNativeBridge.mmを作り、メソッドを実装します。

#import <Foundation/Foundation.h>

#include "NativeBridge.h"
#include "JNIBridge.h"

USING_NS_CC;

void NativeBridge::keepBackLightOn(bool keep)
{
    if (keep) {
        [UIApplication sharedApplication].idleTimerDisabled = YES; //画面ロックを防止
    } else {
        [UIApplication sharedApplication].idleTimerDisabled = NO; //画面ロック防止を解除
    }
}

iOSの場合はこれで完了ですが、注意しなければならない事が2つあります。
1つ目は、XCodeから実行してテストすると画面は常にON状態なので全くテストにならないと言う事です。
なので、画面ロックのテストのときは必ずiOSバイスでアプリを実行して動作確認をしてください。

2つ目は、[UIApplication sharedApplication].idleTimerDisabled = NO;から画面ロックが有効になるまでのタイミングです。画面をしばらく触っていない状態でこのコマンドを実行すると、すぐに画面が暗くなってしまいますので、画面ロック防止の状態に関わらず常にタッチ操作無しの時間をカウントしているということになります。なお、Androidの場合は画面ロック防止を解除したらそこからカウントが始まるようですね。

私の場合は、ゲームオーバー画面では画面ロック防止を解除したいのですが、すぐに画面が暗くならないよう、呼び出し側でcocos2d-xのアクションを使って30秒のディレイを入れました。
見やすいように関係ない部分を省略したコードを貼りますので参考にどうぞ。
重要なのは、30秒待っている間にユーザー操作により再び画面ロック防止が実行される場合を考慮する必要があるという事です。ここでは画面ロック防止のコードの前にstopActionでアクションを止めています。これをやらないとダメなタイミングで画面が暗くなるでしょう。

void HUD::gameOver()
{
    auto delay = DelayTime::create(30.0); // 30秒のディレイ
    auto call = CallFunc::create([]() {
        NativeBridge::keepBackLightOn(false); // 画面ロック防止を解除
    });
    auto sequence = Sequence::create(delay, call, NULL);
    this->runAction(sequence);
    
    auto yesLabel = MenuItemLabel::create(Label::createWithBMFont("travelshootingJP.fnt", "YES"), [this, sequence](Ref* ref) {
        // コンティニュー YESボタンが押されたときの処理
        this->stopAction(sequence); // アクションを止める。止めないと後で暗くなる
        NativeBridge::keepBackLightOn(true); // 画面ロック防止
    });
}

さて、Android用には、NativeBridge.cppという実装ファイルを作成します。
そして、忘れずにAndroid.mkにNativeBridge.cppの行を追加します。(私は良く忘れます。)
逆にXcodeからは見えないように、Xcode画面右側のTargetMembershipの部分にあるチェックを外しておきます。
参考:http://albatrus.com/main/cocos2d/6252

#include "NativeBridge.h"
#include "JNIBridge.h"

USING_NS_CC;

void NativeBridge::keepBackLightOn(bool keep)
{
    if (keep) {
        JNIBridge::disableScreenLock(); //画面ロックを防止
    } else {
        JNIBridge::enableScreenLock(); //画面ロック防止を解除
    }
}

アンドロイドの場合はさらにファイルを作ります。

ヘッダファイルのJNIBridge.h

#ifndef __TravelShooting__JNIBridge__
#define __TravelShooting__JNIBridge__

#include "cocos2d.h"

class JNIBridge
{
public:
    static void disableScreenLock();
    static void enableScreenLock();
};


#endif /* defined(__TravelShooting__JNIBridge__) */


実装ファイルのJNIBridge.cpp
JNIBridge.cppも忘れずに、Android.mkへの登録と、TargetMembershipのチェック外しを行います。
こちらのサイトに詳しい解説があります。(お世話になりました!)
参考:田舎でAndoirdアプリを開発しています : cocos2d-xでのゲーム作成(JNI編)

#include "JNIBridge.h"
#include "platform/android/jni/JniHelper.h"

USING_NS_CC;

#define CLASS_NAME "org/cocos2dx/cpp/AppActivity"

void JNIBridge::disableScreenLock()
{
    JniMethodInfo info;
    
    if (JniHelper::getStaticMethodInfo(info, CLASS_NAME, "disableScreenLock", "()V")) {
        info.env->CallStaticVoidMethod(info.classID, info.methodID);
        info.env->DeleteLocalRef(info.classID);
    }
}

void JNIBridge::enableScreenLock()
{
    JniMethodInfo info;
    
    if (JniHelper::getStaticMethodInfo(info, CLASS_NAME, "enableScreenLock", "()V")) {
        info.env->CallStaticVoidMethod(info.classID, info.methodID);
        info.env->DeleteLocalRef(info.classID);
    }
}

AndroidはさらにJavaファイルの編集が必要になります。
ファイルはAppActivity.javaで、cocos2d-xプロジェクトに初めから用意されています。
場所はこちらを参考に。
f:id:takujidev:20150322173530p:plain:w200
eclipseで見るとそんなに深くない階層にありますが、実際にフォルダをたどって行くとかなり深いところにあります。
このファイルは、Eclipse上で編集するのが良いと思います。補間したりエラー表示したりしてくれますので。

package org.cocos2dx.cpp;

import org.cocos2dx.lib.Cocos2dxActivity;

import android.os.Bundle;
import android.view.WindowManager;

public class AppActivity extends Cocos2dxActivity {
	
	private static AppActivity myActivity;
	
	protected void onCreate(Bundle SavedInstanceState) {
		super.onCreate(SavedInstanceState);
		
		myActivity = this;
	}
	
	public static void disableScreenLock()
	{
		myActivity.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				myActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);		
			}
		});
	}
	
	public static void enableScreenLock()
	{
		myActivity.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				myActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
			}
		});
	}
}

画面ロックを防止する方法はこちらのフォーラムを参考にさせて頂きました。
参考:http://discuss.cocos2d-x.org/t/prevent-screen-dimming/13837
Javaについてはズブの素人なので、Eclipseに怒られつついろんなところからの情報の寄せ集めで何とか動くようになったという感じです。これで正しいのかどうかはわかりません。

あと、前述のフォーラムに書かれている通り、AndroidManifest.xmlにWAKE_LOCKパーミッションを追加する必要があります。Eclipseで開くとGUIで設定することができ、この行が追加されました。

以上で、タッチ操作をしないゲーム中に画面ロックを防止する事ができました!
https://itunes.apple.com/jp/app/travelshooting-jp-toraberushutingu/id917570972?mt=8&uo=4&at=10laCt
https://itunes.apple.com/jp/app/beecluster-wu-liaono-zongsukurorushutingugemu/id663801586?mt=8&uo=4&at=10laCt