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

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