Playgroundで独自Viewを表示する為に少し読んでみた話

Playgroundで遊んでみた。

====

0.環境&対象OS

Mac OS X 10.9.5
Android Studio 1.0 RC1
Android NDK 9d
=============
Nexus5
Android 5.0 Lollipop

※あくまで結果を載せてるので参考程度に。
○Labの人ではないので正しいかはわからない。




1.補足説明

Playgroundとは

「Playground」は、マルチプラットフォームな2D/2.5Dゲームを効率的に開発するためのフレームワークです。KLabのゲームタイトルでは株式会社ブシロード(本社:東京都中野区、代表取締役社長:木谷高明)と共同開発した「ラブライブ!スクールアイドルフェスティバル」と、グローバル向けに開発した「Rise to the Throne(ライズ・トゥ・ザ・スローン)」(※1)に使用されています。

とりあえずスクフェスに使われているゲームエンジン。 決してXcodeの中にあるPlaygroundではない

KLab/PlaygroundOSS · GitHub



2.実際にどのようにPlayground上で開発するのか

Json&Luaを使う

{
	"name":"root",
	"x":0,
	"y":0,
	"priority":0,
	"sub":[{
		"name":"container",
		"x":1200,
		"y":210,
		"class":"task_webview",
		"width":500,
		"height":700
	}],

	"width":1024,
	"height":1024
}

こんな感じで、コンポーネントをごりごり追加していく。
そして、Luaでプログラムを書く。



3.どのような形で独自を表示するか

上のコードを見ていただくとわかると思うが「task_webview」と言う物がある。Android開発をしているとこれがWebView
WebView | Android Developers
を使っていると言う事を想像するのはあまり難しい事ではない。なので今回はWebViewを独自Viewに書き換えて動かしてみる方法を載せる。



4 実装

1) Perser部分
jni > source > Assets > CompositeManagement.cpp内に以下のような関数があり、そこで「task_webview」を判定している。
なので自作コンポーネントを組み込む場合、ここに追加すれば良い。同じファイル上にCLASSIDを定義しているenum部分があるのでそこに自作IDを追加する。

// CompositeManagement.cpp
int CKLBCompositeAsset::readString(const unsigned char * stringVal, size_t stringLen, int /*cte_pool*/)  
{
	bool err = false;
	switch (m_parserField) {
	case CLASS_FIELD:
		if (strncmp("button", (const char*)stringVal,(sizeof("button")-1))==0) {
			m_pCurrInnerDef->classID = BUTTON_CLASSID;
		} 
//======== 中略 ============
		if (strncmp("task_webview", (const char*)stringVal, (sizeof("task_webview")-1)) == 0) {
			m_pCurrInnerDef->classID = WEBVIEW_CLASSID;
		} else



2) 生成部分
jni > source > Assets > CompositeManagement.cpp内に以下のような関数があり、そこで上のIDに応じたコンポーネントを生成していると予想できる。
(ここではCKLBUIWebArea)。

bool CKLBCompositeAsset::createSubTreeRecursive(u16 groupID, CKLBUITask* pParentTask, CKLBNode* parent, CKLBInnerDef* templateDef, u32 priorityOffset) {

		//===========中略===========

		switch (templateDef->classID) {
		case WEBVIEW_CLASSID:
			{
				const char* url = NULL;
				const char* cb  = NULL;

				if (templateDef->handler[1]) {
					url = filterDB(templateDef->handler[1]->string);
				}

				if (templateDef->handler[0]) {
					cb	= templateDef->handler[0]->string;
				}

				CKLBUIWebArea* tsk = CKLBUIWebArea::create(
					pParentTask, 
					parent,
					templateDef->flag[0] ? true : false,
					templateDef->x,
					templateDef->y,
					templateDef->width,
					templateDef->height,
					url,	// URL
					cb		// Call back.
				);

				setupTask(templateDef,tsk);

				// Do not authorize creation of sub node for now.

				pNode = NULL;

				/*	Allow sub system to be created.
					----------------------------------

					pNode = lNode;
					pParentTask = tsk;
				 */
				res = (tsk != NULL);
			}
			break;



3) CKLBUIWebAreaとは
jni > source > UISystem > CKLBUIWebArea.cppで定義されている。
コンポーネントの制御周りを処理していると想定できる。
その中に

CKLBUIWebView*	m_pWebView

と言うメンバがあるのでこれがWebViewだと思う。


4) CKLBUIWebViewとは
jni > source > UISystem > CKLBUIWebViewNode.cppで定義されている。
とはいえここでjavaのWebViewを呼び出してうにゃうにゃしている訳ではなく、IWidget型のnativeInputItemと言う変数に
CAndroidWebWidgetと言うAndroidのWebViewを操作するWidgetを入れて動かしている。
コンストラクタをのぞいてみると

CKLBUIWebView::CKLBUIWebView(bool isPageJump, const char * initialURL,
                            const char * token, const char * region, const char * client,
                            const char * consumerKey, const char * applicationId, const char * userID)
: nativeInputItem   (NULL)
, m_width           (0)
, m_height          (0)
, m_tx              (0)
, m_ty              (0)
, m_bgcolor         (0xffffffff)
, m_textLen         (0)
, m_textBuf         (0)
{
	IPlatformRequest& pForm = CPFInterface::getInstance().platform();
	IWidget::CONTROL control = (isPageJump) ? IWidget::WEBVIEW : IWidget::WEBNOJUMP;
    const char * url = (initialURL) ? initialURL : "";
	CKLBDrawResource& draw = CKLBDrawResource::getInstance();
	int px,py;
	draw.toPhisicalPosition(m_tx, m_ty, px, py);
	nativeInputItem = pForm.createControl(control , 0, url, px, py, 0, 0, token, region, client, consumerKey, applicationId, userID);

	if (nativeInputItem) {
		nativeInputItem->visible(false);
	}

	klb_assert(nativeInputItem, "EditBox allocation failed");
}

となっている。IPlatformRequestで実際にCAndroidWebWidgetを生成している。

5) Androidと接続部分とWidget
jni > Android > CAndroidRequestの中で上のIPlatformRequestが定義されている。
CAndroidRequest::createControl内では本当に生成しているだけだった

IWidget *
CAndroidRequest::createControl(IWidget::CONTROL type, int id, const char * caption, int x, int y, int width, int height, ...)
{
	IWidget * pWidget = 0;
	va_list ap;
	va_start(ap, height);
	switch(type)
	{

//========中略========

	case IWidget::WEBVIEW:
	case IWidget::WEBNOJUMP:
	{
		const char * token  = va_arg(ap, const char *);
		const char * region = va_arg(ap, const char *);
		const char * client = va_arg(ap, const char *);
		const char * cKey   = va_arg(ap, const char *);
		const char * appId  = va_arg(ap, const char *);
		const char * userId = va_arg(ap, const char *);

		CAndroidWebWidget * pWebWidget = new CAndroidWebWidget(this);
		if(!pWebWidget || !pWebWidget->create(type, id, caption, x, y, width, height,
												token, region, client, cKey, appId, userId)) {
			delete pWebWidget;
			pWebWidget = 0;
		}
		pWidget = pWebWidget;
		break;
	}



jni > android > CAndroidWidget内
CAndroidWebWidgetでjavaメソッドをコールしている事がわかる。

bool
CAndroidWebWidget::create(IWidget::CONTROL type, int id, const char * caption,
							int x, int y, int width, int height,
							const char * token, const char * region, const char * client,
							const char * consumerKey, const char * applicationId, const char * userID)
{
	jvalue jval;
	int hIndex = -1;
	bool nojump = false;
	m_id = id;

	switch(type)
	{
	default:
		return false;

	case WEBNOJUMP:
		nojump = true;
	case WEBVIEW:
		m_pParent->logging("WebView create!");
		m_pParent->callJavaMethod(jval, "webview_create", 'I', "IIIISSSSSSSZ",
				x, y, width, height,
				caption, token, region, client, consumerKey, applicationId, userID, nojump);
		hIndex = jval.i;

		// 背景色設定
		m_pParent->callJavaMethod(jval, "webview_setColor", 'V', "III", hIndex, m_bgalpha, m_bgcolor);

		// ズーム設定
		m_pParent->callJavaMethod(jval, "webview_setZoom", 'V', "IZ", m_hIndex, m_bZoom);

		m_pParent->logging("Create finished.: handle = %d", hIndex);
		break;
	}

	if(hIndex < 0) return false;

	return init(hIndex, id, x, y, width, height);
}

5 C++部実装まとめ

  • CompositeManagement.cppにJson perserとオブジェクトを呼んでいる処理がある
  • CKLBUIWebArea.cppにコンポーネントの制御が書かれており実態はCKLUIWebViewNodeで動いている
  • CAndroidWidgetでWebView(Android)を操作している

とても申し訳ないわかりにくさ。

6 Android部実装

先ほどのwebview_create等は
klb.android.GameEngine.PFInterface に記述されている。

	//! WebViewの生成
	public static int webview_create(int x, int y, int width, int height,
									String defURL, 
									String token, String region, String client, String consumerKey,
									String applicationId, String userId, boolean nojump) {
		
		PFInterface pfif = PFInterface.getInstance();
		GameEngineActivity context = pfif.m_context;
		WebViewItem web = new WebViewItem(context, defURL, x, y, width, height,
										token, region, client, consumerKey, applicationId, userId,
										pfif.m_tzone, pfif.m_osversion, nojump);
		for(int i = 0; i < pfif.MAX_WEB_VIEW; i++) {
			if(pfif.m_webList[i] == null) {
				pfif.m_webList[i] = web;
				if(i > pfif.m_webListCount) pfif.m_webListCount = i;
				return i;
			}
		}
		web = null;
		return -1;
	}

WebViewItemはいろいろコーティングされているがWebViewである。なのでPFInterface内で、作りたいviewを操作すれば
良い。

7. とりあえずMapViewを表示してみた

f:id:mizukisonoko:20150222161813p:plain

コードはリファクタリングしたらあげます。

個人的なハッカソンについての考え

こんにちは。
最近ハッカソンが話題になっているので自分にとってのハッカソンを明記しておく。
あくまで初心者の個人的な意見です。この文章を読んだとしてもあなたは価値を得られるかはわかりません。

続きを読む

PlaygroundOSSを使ってAndroidのアプリ作成

動いたので最初から

Playgroundとは

「Playground」は、マルチプラットフォームな2D/2.5Dゲームを効率的に開発するためのフレームワークです。KLabのゲームタイトルでは株式会社ブシロード(本社:東京都中野区、代表取締役社長:木谷高明)と共同開発した「ラブライブ!スクールアイドルフェスティバル」と、グローバル向けに開発した「Rise to the Throne(ライズ・トゥ・ザ・スローン)」(※1)に使用されています。

とりあえずスクフェスに使われているゲームエンジン。 決してXcodeの中にあるPlaygroundではない

0.研究環境&対象OS

Mac OS X 10.9.5
Android Studio 1.0 RC1
Android NDK 9d
=============
Nexus5
Android 5.0 Lollipop

ADTは今年のI/O的に積極的にさけた方が良いと思っているので
早くAndroidStudio対応Docくだしあ><

1.いろいろインストール

Android NDK r10以降で行うとこける事がわかったのでr9代ので行う。とはいえ公式サイトには最新版のリンクしか無いので
/*
今は繋がらない
以下のサイトを参考にした。Where do I find old versions of Android NDK? - Stack Overflow

URL 今は繋がらない
https://dl.google.com/android/ndk/android-ndk-r9d-derwin-x86_64.zip


こっちは繋がった 2015/03/20
https://dl.google.com/android/ndk/android-ndk-r9e-darwin-x86_64.tar.bz2

2015/6/11
今現在、NDK r9を落とせる場所が見当たらないです……

で、NDK r9dをダウンロード。
Path を通す

export ANDROID_NDK_ROOT="/{落とした場所}/android-ndk-r9d"
PATH=$PATH:$ANDROID_NDK_ROOT

上の2行を.profileというファイル名で~/の中にかいておく。
公式DocではSDKのインストールをしていたがAndroidStudioを使のでいらないと思う。(最近は別にインストールするのか?)
とりあえず

$ ndk-build -v
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for x86_64-apple-darwin

こんな感じなら問題ない

2.cloneする

とりあえずgitは入れておこう

$git clone https://github.com/KLab/PlaygroundOSS.git

3.buildする

中に入って

cd PlaygroundOSS/Engine/porting/Android/GameEngine-android/

権限かえて

$chmod +x ./build.py

build実行

$./build.py --rebuild --project SampleProject

とりあえずSampleProjectと言う名前にしておくと良い)

4.AndroidStudioで開く

AndroidStudio 起動

最初の画面で
Import project

  • > /{落とした場所}/PlaygroundOSS/Engine/porting/Android/GameEngine-androidを選択

当然いい感じに開ける。しかし現状問題ばかりなのでいろいろ修正する。

1. build.gradleの変更
    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
    }

gradleのバージョンが古いと怒られるので

    dependencies {
        classpath 'com.android.tools.build:gradle:0.14.+'
    }

に変更する

    compileSdkVersion 17
    buildToolsVersion "18.1.0"

古いので

    compileSdkVersion 21
    buildToolsVersion "21.0.0"

最新版に書き換える

ここで、jniDirなんて知らないと怒られる。Gradle+Androidプラグイン(0.7以降)でNDKプロジェクトをビルドする - やらなイカ?
上のサイトの方でいい感じに対応が書かれているがPlaygroundのファイル構造が対応していないので
全部を書き換えるのは面倒である。

と言う事で

    task ndkBuild(type:Exec) {
        commandLine 'ndk-build', '-j'
    }

    task ndkClean(type:Exec) {
        commandLine 'ndk-build', 'clean'
    }

    task libsClean(type:Exec) {
        commandLine 'rm', '-rf', 'libs/armeabi', 'libs/armeabi-v7a', 'libs/x86', 'libs/mips'
    }

    if(new File(projectDir, "jni").exists()){
        tasks.withType(JavaCompile) {
            compileTask -> compileTask.dependsOn ndkBuild
        }

        tasks.withType(com.android.build.gradle.tasks.PackageApplication) {
            pkgTask -> pkgTask.jniDir new File(projectDir, 'libs')
        }

        clean.dependsOn 'ndkClean'
        clean.dependsOn 'libsClean'
    }

を以下だけ残して削除する。以下の分が無いとlibsを読み込んでくれない

    tasks.withType(com.android.build.gradle.tasks.PackageApplication) {
        pkgTask ->
            pkgTask.jniFolders = new HashSet<File>()
            pkgTask.jniFolders.add new File(projectDir, 'libs')
    }

5.Assetsの追加

TODO Windows上の作業を書き込む

$cd /PlaygroundOSS/Tutorial/01.SimpleItem/.publish/android/
$zip -r -0 /PlaygroundOSS/Engine/porting/Android/GameEngine-android/assets/AppAssets.zip ./*
$md5 AppAssets.zip |  cut -d "=" -f 2 | sed -e 's/ //g' > /Playground0SS/Engine/porting/Android/GameEngine-android/assets/version ./*

assetsと言うフォルダを作成し、中にAppAssets.zipとversionと言うファイルを入れる。
versionの中にはAppAssets.zipのmd5を書き込む
(AppAssetsを更新したらversionの中も書き込まなければ読み込まれない)

6.実行

何とも無ければ動く。
ndk-buildはコマンドラインで直接実行する。

$ndk-build -j

PlaygroundOSSとの戦い(2)

こちらでまとめてます。

PlaygroundOSSを使ってAndroidのアプリ作成 - mizukisonoko’s diary

前回のブログ記事\デェン/

動かなかった

今回の内容

動いた

エラーを読む

~$ ndk-build
find: ./jni/custom: No such file or directory
Android NDK: WARNING:jni/Android.mk:Game: non-system libraries in linker flags: -lcurl -lfreetype2    
Android NDK:     This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES    
Android NDK:     or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the    
Android NDK:     current module    
find: ./jni/custom: No such file or directory
Android NDK: WARNING:jni/Android.mk:Game: non-system libraries in linker flags: -lcurl -lfreetype2    
Android NDK:     This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES    
Android NDK:     or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the    
Android NDK:     current module    
[x86] Compile++      : Game <= KLBPlatformMetrics.cpp
[x86] SharedLibrary  : libGame.so
[x86] Install        : libGame.so => libs/x86/libGame.so
[x86] Install        : libjniproxy.so => libs/x86/libjniproxy.so
[armeabi-v7a] Compile++ arm  : Game <= KLBPlatformMetrics.cpp
[armeabi-v7a] SharedLibrary  : libGame.so
{HOME}/ndk/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.8/../../../../arm-linux-androideabi/bin/ld: error: ./obj/local/armeabi/objs/Game/./libs/Tremolo/dpen.o: requires unsupported dynamic reloc R_ARM_REL32; recompile with -fPIC
{HOME}/ndk/android-ndk-r10d/toolchains/arm-linux-androideabi-4.8/prebuilt/darwin-x86_64/lib/gcc/arm-linux-androideabi/4.8/../../../../arm-linux-androideabi/bin/ld: error: ./obj/local/armeabi/objs/Game/./libs/Tremolo/mdctARM.o: requires unsupported dynamic reloc R_ARM_REL32; recompile with -fPIC
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [obj/local/armeabi/libGame.so] Error 1

[armeabi-v7a] Compile++ arm で死んでいるっぽい?

中に入ってみる

$ adb shell
shell@hammerhead:/ $ run-as klb.android.GameEngine
shell@hammerhead:/ $ ls lib/
libjniproxy.so

無いっぽい。終了

NDKをかえてみる

エラー文

 requires unsupported dynamic reloc R_ARM_REL32; recompile with -fPIC

をググってみたら以下のサイトを発見した。
CocoaChina 开发讨论区 最热的iOS开发论坛| 最热的Mac开发论坛 | 最热的iPhone开发论坛 | 最热的iPad开发论坛
もしやと思いr9をインストール
公式サイトにリンクがなさそうだったので

Old Versions of Android NDK - Stack Overflow
な形でr9dをインストール。export等を変更、ndk-buildする

結果

動いた

$ adb shell
shell@hammerhead:/ $ run-as klb.android.GameEngine
shell@hammerhead:/ $ ls lib/
libjniproxy.so
libGame.so

できてる。


後ほど動いた版の手順を書きます