cocos2d-xのAndroidの画面自動回転

cocos2d-x現時点バージョン3.17でも、コマンドで生成したデフォルトプロジェクトでは、iOS版が何も変更せずに、デバイスを上下反転すると、画面の向きも自動的に回転してくれると思いますが、Android版では未だに画面の自動回転に対応していないようです。

実はAndroid版の対応方法も簡単です。

プロジェクトルート/proj.android/app/src/org/cocos2dx/cpp/AppActivity.javaを開き、onCreate()のところに以下のソース3行を追加するだけです。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
        }

これだけです!

そしたらファイル全体がこんな感じになると思います。

package org.cocos2dx.cpp;

import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;

public class AppActivity extends Cocos2dxActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.setEnableVirtualButton(false);
        super.onCreate(savedInstanceState);
        // ここから
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
        }
        // ここまで
        // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
        if (!isTaskRoot()) {
            // Android launched another instance of the root activity into an existing task
            //  so just quietly finish and go away, dropping the user back into the activity
            //  at the top of the stack (ie: the last state of this task)
            // Don't need to finish it again since it's finished in super.onCreate .
            return;
        }
        // DO OTHER INITIALIZATION BELOW
        
    }

}
参考
  1. Autorotation problem

Cocos CreatorとTypeScriptの基本的な文法

JavaScriptはそのままでもTypeScriptに書けるので、ここにあるのは純粋のTypeScriptのトピックスとは言えませんが、ひとまずCocos Creatorにおいてよく使いそうなものをまとめてみました。

名前末尾の文字を数字に変換

末尾の一文字を取得

buttonName.charAt(buttonName.length - 1)

stringをnumberに変換

文字列の前に「+」をつけるだけです。

+'3'

上記の2つ合わせて例えばボタンの名前の場合は以下にすれば良いでしょう。

let buttonName = button.node.name;
let num = +buttonName.charAt(buttonName.length - 1);

クラスの読み込み

import CustomButton from "./CustomButton";

または、

import * as Encrypt from '../Library/encryptjs';

必要に応じてfrom直後のパスを変えるといいでしょう。
クラスファイルの拡張子は不要です。

クラス判定

インスタンスはどのクラスに所属しているかと確認したい時:

mySprite instanceof Sprite;

参考1ActiveScriptと似たような問題が存在するため、現時点非推奨だそうです。

いろいろなプロパティ宣言

export default class TestMe extends cc.Component {

    readonly MAX_LEVEL = 100; // 定数の宣言

    @property(cc.Label) // コンポーネントの宣言
    label: cc.Label = null;

    @property // 値の宣言
    text: string = 'hello';

    @property([cc.Button]) // 配列の宣言
    myButtons: cc.Button[] = [];

    @property // プライベート変数の宣言
    _cooker: cc.Node = null;
}

コールバック

関数宣言と受け渡し

func1() {
    let callback = (): void => {
        cc.log("callback");
    }
    this.func2(callback);
}

func2(callback: () => any) {
    callback();
}

パラメーターがある場合はこうなります。

func1() {
    let callback = (sender: cc.Node): void => {
        cc.log("callback");
    }
    this.func2(callback);
}

func2(callback: (sender: cc.Node) => any) {
    // 何かの処理
    callback();
}

もちろんcallback変数分けずに、一行で書くこともできます。

さらにC++のtypedefのような書き方もできるようです。

type NumberCallback = (n: number) => any;

class Foo {
// Equivalent
save(callback: NumberCallback) : void {
callback(42);
}
}

C++ならこんなん感じのコールバック宣言になるでしょうか。

    typedef std::function<void(float value)> NumberCallback;

文字列のフォーマット

フォーマット

let a = "hello";
let b = "world";
let str = `${a}_${b}`;
// strが「hello_world」になるはずです

これ以外にも「+」で連結できますが、上記の方法のほうがStringUtils::formatに似てますね。

数字指定桁数の表示

例えば月や時間を全部2桁で表示した場合です。
下記は参考6のJavaScriptフォーラムの回答ですが、その書き方がなんとなくかっこよく感じました。^^;

var myNumber = 7;
var formattedNumber = (“0” + myNumber).slice(-2);

もちろんTypeScriptでも問題ありませんでした。

ちなみに、小数点以降数字の指定桁数表示はnum.toFixed(桁数)です。
詳しくは参考7を参考するといいでしょう。

参考
  1. Class type check with TypeScript
  2. Unary plus (+)
  3. TypeScript 2.0: Read-Only Properties
  4. Use TypeScript
  5. Are strongly-typed functions as parameters possible in TypeScript?
  6. How to format numbers by prepending 0 to single-digit numbers?
  7. TypeScript – Number toFixed()

cocos2d-x v3.17がAndroidでビルドできない時の対策

※2019/04/04更新

NDK Build

従来cocos2d-xのプロジェクトに自前のクラスを追加したら、iOSの場合はXcodeにファイルを追加すれば良いし、Androidの場合はproj.android/app/jni/Android.mkに相応のファイルを追加すれば良かったのですが、v3.17ではそう行かなくなったようです。

実験として、新規プロジェクトを作成:

cocos new Test1 -l cpp

そして、デフォルトのHelloWorldシーンをMainシーンに変えてみました。
iOSで動作確認の上、上記Android.mkにMainScene.cppを追加して以下にすれば良いはずです。

LOCAL_SRC_FILES := $(LOCAL_PATH)/hellocpp/main.cpp \
                   $(LOCAL_PATH)/../../../Classes/AppDelegate.cpp \
                   $(LOCAL_PATH)/../../../Classes/HelloWorldScene.cpp \
                   $(LOCAL_PATH)/../../../Classes/MainScene.cpp

ところが、実行すると以下のようなエラーが出ました。

$ cocos run -p android
...
  /Users/poo/test/Test1/Classes/AppDelegate.cpp:121: error: undefined reference to 'MainScene::createScene()'
  clang++: error: linker command failed with exit code 1 (use -v to see invocation)
  ninja: build stopped: subcommand failed.

Mainシーンのクラスファイルが見つからないようですね。

ログをよく見ると、最初に実行されたコマンドは以下でした。

running: '"/Users/poo/test/Test1/proj.android/gradlew" --parallel --info assembleDebug -PPROP_BUILD_TYPE=cmake'

最後のビルドタイプはcmakeになっていることが分かります。

調べたところ(参考1)、proj.android/gradle.propertiesの中でデフォルトPROP_BUILD_TYPE設定がcmakeになっているので、それをndk-buildに変えればAndroid.mkが参照されるようになっているとのことでした。

しかし実際いくら変えても、実行されるコマンドは”-PPROP_BUILD_TYPE=cmake”のままで、もちろんビルドも失敗で終わります。

そこまで言うならcmakeを使うよとすっかり負けてしまいました。Orz
※2018/11/18追記:


proj.android/gradle.propertiesの設定はAndroid Studioに有効であり、コマンドラインでは–build-typeの指定で解決できることが分かりました。
つまりndk-buildでビルドしたいなら、コマンドは以下になります。

$ cocos run -p android --build-type ndk-build

今回はこれが回答になるかと思います。
以下のCMake部分では新たに追加した参考3によると、Google NDKのガイドではCMakeをネイティブライブラリをビルドするデフォルトツールとされているため、今後はndk-buildが徐々に減り、CMakeが徐々に増える傾向となるでしょう。
なので、早いうち慣れとくといいと思います。

しかしSDKBOXを使うなら、現段階v1.0.2.0ではCMakeに関してはノータッチのようですので、インポートするとCMakeではコンパイルエラーになります。
設定に関する情報も探してみましたが、見つかったのが参考5ぐらいで、まだまだ情報が少ないです。
ちなみに、参考5の情報は既に古いようで、そのままやってもうまくいきませんでしたので、今のところSDKBOXを使うなら、やっぱりndk-buildに切り替えたほうが一番手っ取り早いだと思います。

※2019/04/04更新:ここら辺が参考になります。
SDKBox 2.4.3.0 bugfix


CMake

CMakeのクラスファイルリストはプロジェクトルートにある「CMakeLists.txt」で設定できます。
デフォルトはこうなっているはずです。

list(APPEND GAME_SOURCE
     Classes/AppDelegate.cpp
     Classes/HelloWorldScene.cpp
     )
list(APPEND GAME_HEADER
     Classes/AppDelegate.h
     Classes/HelloWorldScene.h
     )

ここに追加したcppファイルとヘッダーをそれぞれ追加すれば良いはずです。

また、Classesディレクトリーの下にサブディレクトリーを作り、それらを追加したい場合、もうひと手間かかります。
例えば今回のMainSceneはClasses/mainのところに置くとします。
構造はこうなるはずです。

Classes/
├── AppDelegate.cpp
├── AppDelegate.h
├── HelloWorldScene.cpp
├── HelloWorldScene.h
└── main
    ├── MainScene.cpp
    └── MainScene.h

この場合、上記のcppとヘッダーファイル追加するだけでは足りません。
このようなエラーになるはずです。

  /Users/poo/test/Test1/Classes/AppDelegate.cpp:26:10: fatal error: 'MainScene.h' file not found
  #include "MainScene.h"
           ^~~~~~~~~~~~~
  1 error generated.

サブディレクトリーを作った場合、CMakeLists.txtの128行あたりにある「PRIVATE Classes」のあとにサブディレクトリーを追加する必要があります。

target_include_directories(${APP_NAME}
        PRIVATE Classes
        PRIVATE Classes/main #<=ここらへんに追加
        PRIVATE ${COCOS2DX_ROOT_PATH}/cocos/audio/include/
)

※2019/04/04追加
同じファイルの最後辺りに、必要に応じて以下を追加します。

# add admob lib 
if(ANDROID)
    add_definitions(-DSDKBOX_ENABLED)
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proj.android/app/jni/PluginAdmob/)
    target_link_libraries(${APP_NAME} ext_PluginAdMob
    )
endif()

# add Firebase lib 
if(ANDROID)
    add_definitions(-DSDKBOX_ENABLED)
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proj.android/app/jni/PluginFirebase/)
    target_link_libraries(${APP_NAME} ext_PluginFirebase
    )
endif()

# add IAP lib 
if(ANDROID)
    add_definitions(-DSDKBOX_ENABLED)
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proj.android/app/jni/PluginIAP/)
    target_link_libraries(${APP_NAME} ext_PluginIAP
    )
endif()

# add Sdkbox Ads lib 
if(ANDROID)
    add_definitions(-DSDKBOX_ENABLED)
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proj.android/app/jni/PluginSdkboxAds/)
    target_link_libraries(${APP_NAME} ext_PluginSdkboxAds
    )
endif()

# add sdkbox lib
if(ANDROID)
    add_definitions(-DSDKBOX_ENABLED)
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/proj.android/app/jni/sdkbox/)
    target_link_libraries(${APP_NAME} ext_sdkbox
    )
endif()

まとめ

新しいクラスファイル追加した場合、Android版ではCMakeLists.txtに以下の3箇所に追加する必要になります。

  1. list(APPEND GAME_SOURCE ブロックにCPPファイルを追加
  2. list(APPEND GAME_HEADER ブロックにヘッダーファイルを追加
  3. Classesの下にサブディレクトリーがあれば、target_include_directories(${APP_NAME} ブロックにそのディレクトリーを追加

ファイルがたくさんあるときは、以下のコマンドでリストアップしてからコピペするといいでしょう。

#cpp
find Classes -name *.cpp -type f

#header
find Classes -name *.h -type f
参考
  1. CMake Guide
  2. How to properly add include directories with CMake
  3. Android template project doesn’t build on `PROP_BUILD_TYPE=ndk-build`
  4. Can’t use cmake build for android when using cocos console
  5. SDKBOX CMake integration
  6. SDKBox 2.4.3.0 bugfix

Cocos Creatorのユーザーデータ暗号化

公式のユーザーデータ保存は参考1の通りですが、今回やりたいこととの違いは、①TypeScriptでの実装、②暗号化部分の情報補足の2点になります。

プロジェクトルートで下記コマンドでencryptjsをインストールすることができます。

npm i encryptjs

npmコマンドが見つからない場合はHomebrewなどで以下のようにインストールできます。

brew install node

encryptjsをインストールできたら、プロジェクトルート/node_modules/encryptjsのディレクトリーが増えると思います。

その下の「algo.js」と「encryptjs.js」があるはずですので、それらをドラックしてCocos Creatorの「Script」のようなフォルダに入れます。
※「Script」の下で、サードパーティライブラリ専用のサブフォルダを作り、その中に入れたほうがいいかもしれません。例として、Script/Libraryでいきます。

そしたら、こんな感じでユーザーデータ保存用クラス(UserData.ts)を作れます。

const {ccclass, property} = cc._decorator;
import * as Encrypt from '../Library/encryptjs'; // ここで先程追加したライブラリをインポート

@ccclass
export default class UserData extends cc.Component {

    private static _instance:UserData;
    private static secretKey:string = 'abab123'; // 暗号キー

    private constructor() {
        super();
    }

    // シングルトンの例
    public static get instance():UserData {
        if (!this._instance) {
            cc.log("Init UserData");
            this._instance = new UserData();
            this._instance.save();
            this._instance.load();
        }
        return this._instance;
    }

    public save() {
        // サンプルデータを作成
        let userData = {
            name: 'Tracer',
            level: 1,
            gold: 100
        };

     // JSONを文字列に変換
        let dataString = JSON.stringify(userData);
        cc.log("data 1: " + dataString);
     // 暗号化
        let encrypted = Encrypt.encrypt(dataString, UserData.secretKey, 256);
        cc.log("encryptd data: " + encrypted);
        // ローカルに保存
        cc.sys.localStorage.setItem('UserData', encrypted);
    }

    public load() {
        // ローカルに保存したデータの読込み
        let cipherText = cc.sys.localStorage.getItem('UserData');
        // 復号化
        let decrypted = Encrypt.decrypt(cipherText, UserData.secretKey, 256);
        // JSON解析
        let userData2 = JSON.parse(decrypted);
        cc.log("data 2 :" + JSON.stringify(userData2)); // ログ出力のために文字列に変換
    }
}

ここでは初期化のところに保存と読込みやってますが、必要に応じて場所を変えるといいでしょう。
実行してUserDataが初期化されると以下のようなログが出力されると思われます。

[Log] Init UserData (UserData.js, line 35)
[Log] data 1: {"name":"Tracer","level":1,"gold":100} (UserData.js, line 52)
[Log] encryptd data: iwPrvkfnzVuqXcF48BFHIXyOYwGJ2Uvma/yNL3dx2iPyWyQmQ/DEhB9POIlt1g== (UserData.js, line 54)
[Log] data 2 :{"name":"Tracer","level":1,"gold":100} (UserData.js, line 61)

ユーザーデータの暗号化は以上です。

ところで、最終的にネイティブで実行されるコードはJavaScriptである故に、バイナリにならず、.jscの付いたソースコードを難読化したものにとどまります。
ソースコードの難読化は暗号化ではなく、ソースコードの復元は比較的に容易にできるはずです。
さらに、もしCocos CreatorでWebブラウザ向けのゲームを作るなら、難読化すらされず(難読化されたらブラウザが読めなくなるため)、ソースコードそのまま公開されます。
これはCocosだけの問題ではなく、どのエンジンでも、プラグインなしではHTML+JavaScriptが主流ですから。

つまり、ユーザーのデータを暗号したところ安全ではなく、実はパスワード(暗号キー)がバレバレであることを注意していただきたいです!

じゃあ、なんで暗号化なんかするんだ?!というと、周知の通り、暗号化と言っても、どこまでやれば本当に安全であるかの話になればキリがないので、ここの暗号化によって、そう簡単にチートされることはないだろうと、平文保存よりは多少マシであるだろうとのことが言えると思います。
実際の案件は暗号化の実装難易度や暗号/復号による負荷などでやり方が決まると思いますので、この方法はサーバーを介さず、手軽でやれるところがいいと思います。

何か誤りや質問があれば、お気軽にコメントください!^^b

参考:
  1. User Data Storage
  2. encryptjs
  3. How to install NodeJS and NPM on Mac using Homebrew
  4. How to import js-modules into TypeScript file?

Cocos Creatorがコンパイルできない問題の対処法

環境:
macOS High Sierra: v10.13.6
Xcode: v10.0
Cocos Creator: v2.0.4

Cocos Creatorでビルド画面でプラットフォームをiOS選択し、「Build」ビルドは無事終わることができますが、「Compile」では失敗します。

ログを確認すると、以下の2行だけです。
Building mode: release
Update xcode please.

うぅん〜Xcodeは最新版のはずなのに。。。

調べてみたところ、簡単な方法はXcodeで直接プロジェクトを開けば良いです。
プロジェクトルート/build/jsb-link/frameworks/runtime-src/proj.ios_macの下にXcodeのプロジェクトファイルがあると思いますが、それを開けば普通に実行できます。

または、以下のスクリプトを修正することです。
/Applications/CocosCreator.app/Contents/Resources/cocos2d-x/tools/cocos2d-console/plugins/plugin_compile/project_compile.py

このファイルの476行〜480行あたりの下記部分をコメントアウトするといいようです。

        version = cocos.get_xcode_version()

        if version <= '5':
            message = MultiLanguage.get_string('COMPILE_ERROR_UPDATE_XCODE')
            raise cocos.CCPluginError(message, cocos.CCPluginError.ERROR_TOOLS_NOT_FOUND)

cocos creator次のバージョンでこれを修正してくれるといいんですが、もしそうでなければ、バージョンアップするたびにエンジンのソースコードを修正するのはちょっと、、、という方は直接Xcodeでコンパイルしたほうがいいではないでしょうか。

とりあえずエンジンのスクリプトにちょっとした不具合があったことと、直接Xcodeでコンパイルすれば問題ないということだけ知って一安心ですね。

参考

  1. Cannot compile on Mojave with Xcode 10
  2. creator编译发布ios 报 Update xcode please.
  3. Xcode升级到10.0后,cocos creator编译失败

Cocos Creator.app is damagedの対処法

10月17日にCocos Creator v2.0.4がリリースしたので、早速試そうとしましたが、下記のように
“CocosCreator.app” is damaged and can’t be opened. You should move it to the Trash.
のエラーが出ました。

CocosCreator.app is damaged and can't be opened

この場合は、インストールしたアプリを残し、ターミナルで以下のコマンドで解決できました。

xattr -rc /Volumes/Cocos\ Creator/CocosCreator.app

ちなみに、参考1の同じページで議論されているゲートキーパーを解除の方法ですが、つい昨日VirtualBoxインストールできなかった時に参考したサイト(参考2)で紹介されました。

普通「システム環境設定」⇒「セキュリティとプライバシー」を開くとこうなっているはずです。
Security & Privacy

下部の「ダウンロードしたアプリケーションの実行許可(Allow apps downloaded from)」のところでは、「すべてのアプリケーションを許可(Anywhere)」は出ていないと思います。

この時はターミナルで以下のコマンドを実行すると、「すべてのアプリケーションを許可(Anywhere)」が表示され、解除できるようになります。

sudo spctl --master-disable

また、元に戻す場合は以下です。

sudo spctl --master-enable
参考
  1. Damaged and can’t be open app error message
  2. Fixing ‘The Installation Failed’ VirtualBox Error on Mac High Sierra

Cocos CreatorのError:Cause: path may not be null or empty string. path=”問題

Cocos Creator(v2.0.2)でプロジェクトを新規作成して適当なシーンでAndroid実機での配置を確認したところ、Android Studio(v3.1.3)開いたらいきなり読み込み失敗です。
以下のようなエラーが出ました。

path may not be null or empty string. path=''

結論から言うと、Cocos Creatorのメニューから「Project」⇒「Build…」を選び、出てきたポップアップ中の「Platform」を「Android」にして、各種設定を調整した上でビルドすれば解決です。

特にここの「Keystore」のところ、「Use Debug Keystore」にチェック入れることが今回のポイントになります。
もちろん本番パッケージをビルドする際には本番用のKeystoreを入れる必要があります。


Cocos CreatorからiOSだけビルドしても、下記のところにAndroidなど他のプラットフォームのプロジェクトもが生成されます。
プロジェクトルート/build/jsb-link/frameworks/runtime-src

私はまずiOSの動作を確認したので、Androidのプロジェクトが既に作成されてると思い込んだことが今回の原因です。
実際ファイルがそこにありますからね。^^;

Android Studioでプロジェクトを読み込む時の詳細ログを見てみると、ここで引っかかったようです。

	at build_bf37h7qx9z4mihhuyxuow3w4l$_run_closure1.doCall(プロジェクトルート/build/jsb-link/frameworks/runtime-src/proj.android-studio/app/build.gradle:56)

実際そのファイルの56行あたりはこんな感じです。

    signingConfigs { # <=56行はここ

       release {
            if (project.hasProperty("RELEASE_STORE_FILE")) {
                storeFile file(RELEASE_STORE_FILE)
                storePassword RELEASE_STORE_PASSWORD
                keyAlias RELEASE_KEY_ALIAS
                keyPassword RELEASE_KEY_PASSWORD
            }
        }
    }

故にプロジェクトルート/build/jsb-link/frameworks/runtime-src/proj.android-studio/gradle.propertiesの下記の設定に辿り着きました。

RELEASE_STORE_FILE=
RELEASE_STORE_PASSWORD=
RELEASE_KEY_ALIAS=
RELEASE_KEY_PASSWORD=

Keystoreの設定がないんですね。
まだデバッグなのに、空でダメなの?と思いながらも設定してみました。
ところでここを正しいパスやパスワードを設定しても、また別のエラーで読み込むことができませんでした。

これは何かが違う!!
このゲームエンジンの人気が落ちたとは言え、一応それなりの人数が使ってるはずだから、こんなにいい加減なはずがないと思い、やっと冒頭のやり方に辿り着いた訳です。

そしてそのようなキーワードで検索しても、それらしい答えが見つからなかったので、ここでまとめてみました。

Cocos CreatorのビルドもUnityのように、選択したプラットフォームのみ出力したほうが良くないんですかね?!

Android v4.1~v4.4のTLS v1.2対応

さくらインターネットが5月でTLS1.0/1.1が使えなくなるため、cocos2d-xで作ったゲームがAndroid v4.1からv4.4(API Level 16~19)において通信ができなくなる問題が起きました。
SSLSocket参考

基本的には以下のところで紹介された方法でやりましたが、cocos2d-x周りに少し変更が必要でした。
Android 4.1+ enable TLS 1.1 and TLS 1.2
ソースコードはこちらです。

今回対応が必要としたcocos2d-xのバージョンは3.8.1です。

  1. まずは以下の内容でTLSSocketFactory.javaファイルを作ります。
    package org.cocos2dx.lib;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.security.KeyManagementException;
    import java.security.NoSuchAlgorithmException;
    
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.SSLSocketFactory;
    
    /**
     * @author fkrauthan
     */
    public class TLSSocketFactory extends SSLSocketFactory {
    
        private SSLSocketFactory internalSSLSocketFactory;
    
        public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, null, null);
            internalSSLSocketFactory = context.getSocketFactory();
        }
    
        public TLSSocketFactory(SSLSocketFactory sslSocketFactory) throws KeyManagementException, NoSuchAlgorithmException {
            internalSSLSocketFactory = sslSocketFactory;
        }
    
        @Override
        public String[] getDefaultCipherSuites() {
            return internalSSLSocketFactory.getDefaultCipherSuites();
        }
    
        @Override
        public String[] getSupportedCipherSuites() {
            return internalSSLSocketFactory.getSupportedCipherSuites();
        }
        
        @Override
        public Socket createSocket() throws IOException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket());
        }
    
        @Override
        public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
        }
    
        @Override
        public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
        }
    
        @Override
        public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
        }
    
        @Override
        public Socket createSocket(InetAddress host, int port) throws IOException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
        }
    
        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
        }
    
        private Socket enableTLSOnSocket(Socket socket) {
            if(socket != null && (socket instanceof SSLSocket)) {
                ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"});
            }
            return socket;
        }
    }
    

    ここ変えたのは最初のパッケージ名と最後のTLSv1.1を消すぐらいです。さくらインターネットはTLSv1.1も無効にするので不要かと。

  2. 上記TLSSocketFactory.javaをcocos2dxプロジェクトルート/cocos2d/cocos/platform/android/java/src/org/cocos2dxに保存します。
  3. /cocos2d/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHttpURLConnection.javaを変更します。
    1. 54行あたりに以下を追加
      import org.cocos2dx.lib.TLSSocketFactory;
      import javax.net.ssl.SSLSocketFactory;
      
    2. 131行あたりに以下のをコメントアウト
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
      
            httpsURLConnection.setSSLSocketFactory(context.getSocketFactory());
      
    3. 上記コメントアウトの箇所すぐ下に以下のを追加
                  SSLSocketFactory noSSLv3Factory = new TLSSocketFactory();
                  httpsURLConnection.setSSLSocketFactory(noSSLv3Factory);
      
  4. 本来ここまでやれば良いはずですが、cocosはssl認証のような面倒なことはやってくれませんでした。つまり、通常HTTPS通信行う時、上記変えた箇所のsetVerifySSL(HttpURLConnection urlConnection, String sslFilename)メソッドには通っていないんです。
    そのため、こちらのSSL証明書をチェックさせる方法で無理に通らせることにしました。
    HttpRequestを作る前に以下を追加しました。

        auto path = FileUtils::getInstance()->fullPathForFilename("cacert.pem");
        HttpClient::getInstance()->setSSLVerification(path);
    

    ちなみに、このcacert.pemはどこから来たかというと、自分サイトから以下のコマンド実行すると得られます。

    openssl s_client -showcerts -connect ホスト名:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >cacert.pem
    

    そしてこのpemファイルをresディレクトリなどの場所に置き、Xcodeのプロジェクトにも追加すれば良いでしょう。

    How to save a remote server SSL certificate locally as a fileのelec3647の回答
    余談になりますが、実際全く違うサイトの証明書を落としてきて試したところ、ここのca証明書なんでもいいようです。
    /cocos2d/cocos/network/HttpClient-android.cppの541行あたりのsetVerifySSL()を見ると分かりますが、まずはssl caファイルをチェックし、それがなければ狙いのsetVerifySSLに通してくれないので、あまり意味がなくても設定します。
    ※あっ!もしかして、そのif(_client->getSSLVerification().empty()) returnをコメントアウトする手もあったでは?!

  5. 最後に/cocos2d/cocos/network/HttpAsynConnection-apple.mの193行あたりに立て続け[(id)certArrayRef release];が2つありますので、そのうちの一つをコメントアウト(削除)します。
    ここでもcocosのssl証明書に関するやる気のなさが窺えます。

これでAndroid v4.1~v4.4の端末でも問題なくHttpRequestで通信できるはずです。

お気づきの方もいらっしゃると思いますが、Cocos2dxHttpURLConnection.javaのsetVerifySSL(HttpURLConnection urlConnection, String sslFilename)メソッド中では、SSLContext作る前に証明書やキーなどいろいろと準備してましたが、今回の変更で全く使われなくなります。

こちらのgotevが紹介した方法でやりたくなります(実際やりたくなりました^^;)が、下記のエラーが出てしまいます。

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

ここは深入りしてませんが、つまりtmf.getTrustManagers()を渡してcontext.init(null, tmf.getTrustManagers(), null);するとパスが見つからないというエラーが出ますが、context.init(null, null, null);にするといいです。
この問題はstevieが言ってることがを参考にするといいかもしれません。

luaノードイベントの呼び出し

環境はcocos2d-x 3.15 + Xcode v7.3.1 + macOS Sierra v10.12.4です。

cocos2d-xのc++プロジェクトではonEnter()を呼び出させるには、それを以下のようにオーバーライドすれば良いですが、
virtual void onEnter() override;
luaは以下のようにイベントハンドラーを登録する必要があります。

function GameScene:onCreate()
    local function onNodeEvent(event)
        if event == "enter" then
            self:onEnter()
        elseif event == "enterTransitionFinish" then
            self:onEnterTransitionFinish()
        elseif event == "exit" then
            self:onExit()
        elseif event == "cleanup" then
            self:onCleanup()
        end
    end
    self:registerScriptHandler(onNodeEvent)
end
	

これだと、Viewごとに登録する必要があります。よくこういったところに処理を追加するのであれば、src/packages/mvc/ViewBase.luaにある以下のコードを真似して
if self.onCreate then self:onCreate() end
以下のように追加すれば、各ビューで登録せずに呼ばれていいではないでしょうか。

    if self.onEnter then
        local function onNodeEvent(event)
            if event == "enter" then
                self:onEnter()
            end
        end
        self:registerScriptHandler(onNodeEvent)
    end
    if self.onEnterTransitionFinish then
        local function onNodeEvent(event)
            if event == "enterTransitionFinish" then
                self:onEnterTransitionFinish()
            end
        end
        self:registerScriptHandler(onNodeEvent)
    end
    if self.onExit then
        local function onNodeEvent(event)
            if event == "exit" then
                self:onExit()
            end
        end
        self:registerScriptHandler(onNodeEvent)
    end
    if self.onCleanup then
        local function onNodeEvent(event)
            if event == "cleanup" then
                self:onCleanup()
            end
        end
        self:registerScriptHandler(onNodeEvent)
    end

cocos2d-luaでAndroidの戻るボタンを制御

環境はcocos2d-x 3.15 + Xcode v7.3.1 + macOS Sierra v10.12.4です。

cocos2d-luaでAndroidの戻るボタン(back key)に応答するには、以下のをonEnter()に追加すると反応するはずです。

    local function onKeyReleased(keyCode, event)
        if keyCode == cc.KeyCode.KEY_ESCAPE then
            print("back")
            -- cc.Director:getInstance():endToLua()
        end
    end

    local listener = cc.EventListenerKeyboard:create()
    listener:registerScriptHandler(onKeyReleased, cc.Handler.EVENT_KEYBOARD_RELEASED)

    local eventDispatcher = self:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, self)

実は上記のコードをonCreate()に入れたことで、戻るボタンを押しても反応しないことで悩みました。
ネットで調べると、似たような問題に直面した人がいたようですが、解決方法がありませんでした。
公式サンプルをよく見ると、onEnter()の呼び出しのタイミングに入れてるので、もしかしたらと思い、結果、図星でした。
そのため、反応しないなぁと思ったら、追加の場所を確認してください。

ちなみに、onEnter()やonEnterTransitionFinish()イベントを呼ばさせるには別途実装が必要ですので、luaノートイベントの呼び出しを参考してください。