ブロックチェーンゲーム制作チェーン -Minimal Arcade Medal-

ブロックチェーン技術、ゲーム作成のためのコード、ドット絵やアニメーションの作り方を解説するブログです。最終的に記事の内容を組み合わせることで誰でもブロックチェーンゲームが作れるようにしたいです。Micro Arcade MedalというdApps開発中です。

ゲームの実行環境によってコンテンツを動的に変更するーCocosCreator

f:id:kusakabob:20180711135336p:plain


CocosCreatorではNativeApp(iOSやアンドロイドにインストールするアプリ)やWebApp(ブラウザからアクセスするアプリ)をbuildすることができます。


つまり、一つのコードで色々なプラットフォームに対応できるということです。


この機能があるかどうかがゲームエンジンを選ぶ上で一つの大きな基準になると思います。


別々の環境でゲームを走らせる場合、プラットフォームごとに表示する内容を変えたいといったことがあるかもしれません。


その際のテクニックをこの記事では紹介します。
ちなみにめっちゃ長いです。。


サンプルプロジェクトのwebp-testを使います。


サンプルプロジェクトはCocosCreatorのNewProjectからExample Collectionでアクセスできます。


場所はここです。
example/assets/cases/01_graphics/03_texture_format/webp-test


このSceneでは、表示内容を変える以外にも


といった内容も扱っています。


せっかくなのでそれらも説明します。

Webpとは


WebpとはGoogleが開発している画像のフォーマットです。


.jpgや.pngのように画像の後ろに~.webpとつく拡張子です。


f:id:kusakabob:20180711140739p:plain


今までの画像フォーマットと比べて圧縮率が高いです。
つまりサイズが小さくなります。


それだけではなく画像の劣化がありません。


Googleの公式ページではloseless and lossy compressionと書いてあります。


A new image format for the Web  |  WebP  |  Google Developers


詳しく知りたい方は読んでみてください。


簡単にいうとサイズが小さいのに画質が良いという良いことづくめのフォーマットです。

Sceneの解説


f:id:kusakabob:20180711134803p:plain


Sceneを開くとこのような画面になります。


女性の画像がwebpファイルです。


このSceneを実行すると実行環境がWebなのかNativeなのか判断して、Content Nodeの中身が変わります。


その機能を実装しているのがTipsManagerというNodeです。


このNodeに紐づいているScriptを見ていきます。

//
// 用于提示用户哪些范例不支持平台
//
const i18n = require('i18n');

// 平台检查
var PlatformType = cc.Enum({
    None: 0,
    Native: 1,
    Native_Desktop: 2,

    Mobile: 10,
    Mobile_Android: 11,

    Runtime: 20,

    WebGl: 30,
    Canvas: 31,

    Native_Browser_Chrome: 100
});

var canvas = null;

cc.Class({
    extends: cc.Component,

    properties: {
        support: false,
        // 需要检测的平台
        platform: {
            default: PlatformType.Node,
            type: PlatformType
        }
    },

    onLoad () {
        this._showTips();
    },

    _checkNonSupport () {
        var showed = false, textKey = '';
        switch (this.platform) {
            case PlatformType.Native_Desktop:
                showed = (cc.sys.isNative && (cc.sys.platform === cc.sys.WIN32 ||
                          cc.sys.platform === cc.sys.MACOS));
                textKey = i18n.t("example_case_nonsupport_native_desktop_tips");
                break;
            case PlatformType.Mobile:
                showed = cc.sys.isMobile;
                textKey = i18n.t("example_case_nonsupport_mobile_tips");
                break;
            case PlatformType.Runtime:
                showed = cc.runtime;
                textKey = i18n.t("example_case_nonsupport_runtime_tips");
                break;
            case PlatformType.Canvas:
                showed = cc._renderType === cc.game.RENDER_TYPE_CANVAS;
                textKey = i18n.t("example_case_nonsupport_web_canvas_tips");
                break;
        }
        return {
            showed: showed,
            textKey: textKey
        }
    },

    _checkSupport () {
        var showed = false, textKey = '';
        switch (this.platform) {
            case PlatformType.Mobile:
                showed = !cc.sys.isMobile || cc.runtime;
                textKey = i18n.t("example_case_support_mobile_tips");
                break;
            case PlatformType.WebGl:
                showed = cc._renderType !== cc.game.RENDER_TYPE_WEBGL;
                textKey = i18n.t("example_case_support_webGl_tips");
                break;
            case PlatformType.Mobile_Android:
                showed = !(cc.sys.isMobile && cc.sys.platform === cc.sys.ANDROID) || cc.runtime;
                textKey = i18n.t("example_case_support_mobile_android_tips");
                break;
            case PlatformType.Native_Browser_Chrome:
                showed = !(!cc.sys.isMobile &&
                            cc.sys.isBrowser &&
                            cc.sys.browserType === cc.sys.BROWSER_TYPE_CHROME);
                textKey = i18n.t("example_case_support_native_chrome_tips");
                break;
        }
        return {
            showed: showed,
            textKey: textKey
        }
    },

    _showTips () {
        if (this.platform === PlatformType.None) { return; }
        var info = this.support ? this._checkSupport() : this._checkNonSupport();
        var bg = this.node.getComponent(cc.Sprite);
        bg.enabled = info.showed;
        if (info.showed) {
            var content = this.node.getChildByName('Content').getComponent(cc.Label);
            content.textKey = info.textKey;
        }
    }
});


他のサンプルコードと比べて比較的長いですが、関数単位で読み解いていくと簡単です。

i18n

const i18n = require('i18n');


これはi18nを使えるようにするコードです。


i18nはアプリのテキストを国ごとに変更することができるというモジュールです。


このファイルではシステムごとにtextKeyにテキストを割り当てるために使われています。


ここが公式です。
i18n - npm

Global変数

var PlatformType = cc.Enum({
    None: 0,
    Native: 1,
    Native_Desktop: 2,

    Mobile: 10,
    Mobile_Android: 11,

    Runtime: 20,

    WebGl: 30,
    Canvas: 31,

    Native_Browser_Chrome: 100
});

var canvas = null;


platformTypeにはEnumTypeのオブジェクトが入っています。


Enumは値が-1のアイテムを持っている場合、リスト内の順番を返します。


それ以外の場合は普通のObjectのような振る舞いをします。

var WrapMode = cc.Enum({
    Repeat: -1,
    Clamp: -1
});

// Texture.WrapMode.Repeat == 0
// Texture.WrapMode.Clamp == 1
// Texture.WrapMode[0] == "Repeat"
// Texture.WrapMode[1] == "Clamp"


上記の例ではRepeatが-1なのでキーでEnum型を参照すると値の-1ではなく0番目を意味する0が返ってきます。
続くClampも順番が返ってきています。


サンプルでは実行環境名と数字を対応させています。

property

properties: {
        support: false,
        // 需要检测的平台
        platform: {
            default: PlatformType.Node,
            type: PlatformType
        }
    },


ここではplatformをsupportを定義しています。


supportはBooleanでfalseです。


platformの型は上で定義したPlatformTypeです。


f:id:kusakabob:20180711145157p:plain


これらはここと対応しています。

onLoad

 onLoad () {
        this._showTips();
    },


this.showTipsという関数を読んでいます。
これはこのクラス内で定義されています。

_showTips () {
        if (this.platform === PlatformType.None) { return; }
        var info = this.support ? this._checkSupport() : this._checkNonSupport();
        var bg = this.node.getComponent(cc.Sprite);
        bg.enabled = info.showed;
        if (info.showed) {
            var content = this.node.getChildByName('Content').getComponent(cc.Label);
            content.textKey = info.textKey;
        }
    }

まず2行目で選択されたplatformの値がNoneなのかどうかを確認してNoneならば処理を終わらせています。


3行目、infoに、supportがtrueならば_checkSupportをfalseならば_checkNonSupportの実行結果を入れています。
これらの関数もこのクラスで定義されているので後ほど見ます。


が何をreturnするかは先に知っておいたほうが後のコードがわかりやすいです。


showed:BooleanとtextKey:Stringを持つObjectを返してます。


4行目、bgはSpriteクラスへの参照で、Spriteクラスの関数を使用するために宣言してあります。


5行目、bg.enabledにinfo.showedを入れています。
_checkSupportか_checkNonSupportを実行して得られた結果によって、画像を表示するかしないかを切り替えています。


7〜9行目、そして画像を表示する場合、TipsManagerの子供であるContentのLabel Componentを持ってきて、
TextKeyを上記の関数を実行して得られたTextKeyで更新しています。


f:id:kusakabob:20180711152119p:plain


ここの一番下です。

_checkSupportと_checkNonSupport

_checkNonSupport

 _checkNonSupport () {
        var showed = false, textKey = '';
        switch (this.platform) {
            case PlatformType.Native_Desktop:
                showed = (cc.sys.isNative && (cc.sys.platform === cc.sys.WIN32 ||
                          cc.sys.platform === cc.sys.MACOS));
                textKey = i18n.t("example_case_nonsupport_native_desktop_tips");
                break;
            case PlatformType.Mobile:
                showed = cc.sys.isMobile;
                textKey = i18n.t("example_case_nonsupport_mobile_tips");
                break;
            case PlatformType.Runtime:
                showed = cc.runtime;
                textKey = i18n.t("example_case_nonsupport_runtime_tips");
                break;
            case PlatformType.Canvas:
                showed = cc._renderType === cc.game.RENDER_TYPE_CANVAS;
                textKey = i18n.t("example_case_nonsupport_web_canvas_tips");
                break;
        }
        return {
            showed: showed,
            textKey: textKey
        }
    },


長く見えますが、switch文で分岐をしているだけです。


まず2行目、ローカル変数showedとtextKeyを定義。
ここで定義することにより、この関数の外から隠蔽されます。


隠蔽すると_checkNonSupport.showed = false
といったように書き換えることができなくなります。


これはクロージャーという仕組みで、スコープといった概念を理解する必要があります。
が説明を省いて簡単に言うと、セキュリティが高くなったということです。


22行目、それらのローカル変数をObjectに入れて返しています。


これで外からアクセスできるのですが、
ここで重要なのは、この方法でローカル変数の読み書きのうち読みだけに制限しているいう点です。


変数の中身を書き換えたい場合、この関数を実行する必要があります。


このテクニックはJavaScriptの古典的なカプセル化の方法なので詳しく知りたい方は検索してみてください。


3行目、Swith文ではsystemのpropertyを見て、NativeアプリなのかWindowsなのかといったことをBooleanで返しています。


どのようなpropertyがあるかはここに書いてあります。
sys


7行目、i18n.t()でテキストを割り当てています。


tはtranslateの略で、引数としてStringかArrayのKeyをとります。


このケースの場合、本来の使い方ではなくシンプルにテキストを表示するために使われているみたいです。


あとは同じことの繰り返しなので割愛します。


以上で終わりです!

所感


色々なトピックをカバーしたので記事がめちゃくちゃ長くなってしまいました。


多言語対応やマルチデバイス対応は昨今必須です。


知っておいて損はない内容なのでまとめることができてよかったです。