【Godot Engine】背景無限スクロールのやり方

移動できるキャラ作成

Node を作成し、移動対象の Sprite と移動した時にカメラが追随するように Sprite の子として Camera2D を追加します。カメラを選択して、インスペクターから Current を ON にしておきます。

f:id:erudoru:20180308220015p:plain

Node にスクリプトを追加して、Sprite を移動できるようにします。スクリプトはこれだけ。

extends Node

var speed = 400

func _ready():
    pass

func _process(delta):
    var input = Vector2(0, 0)
    if Input.is_action_pressed("ui_right"):
        input.x += 1
    elif Input.is_action_pressed("ui_left"):
        input.x -= 1
    if Input.is_action_pressed("ui_up"):
        input.y -= 1
    elif Input.is_action_pressed("ui_down"):
        input.y += 1
    var pos = $Sprite.get_position() + input.normalized() * speed * delta
    $Sprite.position = pos

背景追加

画像はこちらのを使用させて頂きました。

Tutorial - creating depth | OpenGameArt.org

そして Node に ParallaxBackground、ParallaxLayer と実際に画像を表示する Sprite を追加します。Sprite は backgroundという名称に変更しました。

f:id:erudoru:20180308215840p:plain

まずは、background のインスペクター上で Texture に背景画像を設定します。大きさを変更したい場合、background の scale を設定します。

画像がウィンドウと同じくらいのサイズだと、繰り返し時にちらつく時があるため少し大きめのサイズにしておきます。ここでは画像を 1.2 倍にして少し表示位置を下にしたいので Position の y に 100 を設定しました。

f:id:erudoru:20180308221550p:plain

この時点で実行してみると、背景は表示されますが横に移動していくと背景が途切れてしまいます。また、比較対象が無いのでわかりにくいですが、背景がカメラと同じ速度で移動してしまうため背景感がありません。

f:id:erudoru:20180308221636p:plain

背景設定

まずは、遠くの景色が移動している感じを出すために、ParallaxLayer の Motion.Scale を設定します。1 が等速移動。0 が移動なし。1 から 0 の間が減速して移動する比率になります。

背景の横の動きをキャラクターの移動スピードの半分に設定し、縦方向は背景固定にします。

( マイナス値を設定すると逆方向に移動します )

f:id:erudoru:20180308222226p:plain

繰り返し表示は同じく ParallaxLayer の Motion.Mirroring に、繰り返し表示したい方向 (x or y or both) に画像サイズを設定します。その際、画像に設定している Scale を加味したサイズを設定する必要があります。

先程背景画像を 1.2 倍にしているため、Mirroring に設定するサイズも画像サイズ x 1.2 になります。幸いエディタ上で計算式が使えるため、画像サイズを調べるだけで入力可能です。

画像は横幅 800px だったため 800 * 1.2 を入力すれば自動で 960 が設定されます。

f:id:erudoru:20180308222624p:plain

これで延々と背景が繰り返されます。

f:id:erudoru:20180308223236p:plain

後は必要な背景分 ParallaxLayer を追加してこの作業を繰り返して、Motion.Scale を奥に行くほど小さくしていくことで遠近感のある背景ができあがります。

f:id:erudoru:20180308224817p:plain

f:id:erudoru:20180308230056p:plain

【Godot Engine】GDScriptから入力イベントを呼び出す

入力イベントをコード上から呼び出す

アクションイベント

# 押す
Input.action_press("ui_up")
# 離す
Input.action_release("ui_up")

InputEventクラスを使用して呼び出す場合

InputEventAction

アクションイベント

できることはInput.action_press、Input.action_releaseと同じ

var event = InputEventAction.new()
event.set_action("ui_up")
event.set_pressed(true)
Input.parse_input_event(event)

InputEventKey

キーボードイベント

InputEventAction( ui_up 等)に変換されて通知されるわけじゃないので、 InpuEventKey に対応したイベント処理を別途記述する必要がある。

var event = InputEventKey.new()
event.scancode = KEY_UP
event.pressed = true
Input.parse_input_event(event)

アクション名を動的に判断したい場合

アクション名はキーボードやジョイスティックの違いを吸収してくれ、 Godot のエディタ上で補完が効くので、基本アクション名で処理を書いていたほうが何かと都合がよさそうですが

どうしてもアクション名をその場に書きたくない場合、

現時点のバージョンでは、アクション名を逆引きできるメソッドがなさそうなため、登録済みのアクションを InputMap から取得してチェックする必要があります

# キーボード名からアクション名逆引き関数
func get_scancode_action_name(scancode):
    # アクション名を InputMap からすべて取得
    for action_name in InputMap.get_actions():
        # 各アクション名に紐づくイベントをすべてチェック
        for action in InputMap.get_action_list(action_name):
            # 対象のキーの場合
            if action is InputEventKey and action.scancode == scancode:
                return action_name
    return ""

# 呼び出し
Input.action_press(get_scancode_action_name(KEY_UP))

【Godot Engine】スマホアプリ向け画面設定

Godot Engine 3 がリリースされたのでスマホ向けゲームを作るためのメモ。主に Android で動作確認しているため、iOS に関しては別途設定が必要かも。

縦画面固定にしたい場合

Project > Project Setting

から、Width と Height に縦長サイズを指定するだけだと足りない。

f:id:erudoru:20180213230945p:plain

さらに Handheld の Orientation を portarait にする必要がある。Android の Export 側の Orientation を設定しても、Project 側の設定をしていないと縦画面になってくれない。

Stretch は公式ドキュメントを読んだら viewport が一番いい感じで表示してくれそうな設定っぽい。

http://docs.godotengine.org/en/3.0/tutorials/viewports/multiple_resolutions.html

Handheld > Emulate Touchscreen を ON にしておくと、マウス操作をタッチイベントに変換してくれるので、PC上でデバックする際に便利。

NetbeansでlibgdxのAndroidプロジェクトをビルドする

libdxのバージョンは1.9.3です。

libgdxはGradleプロジェクト形式のため、Gradleプラグインをインストールすれば開発出来ます。

NetbeansAndroidアプリを開発する場合、NBAndroidプラグインAndroid Gradle Supportプラグインをインストールすれば開発できます。

しかし、libgdxのAndroidプロジェクトを開くと、何故かプロジェクトの表示がおかしくなり、正しくビルドが出来ない事象がありました。

f:id:erudoru:20160907215221p:plain

そこで以下のようにbuild.gradleをいじったら解決できました。

android/build.gradleの以下の箇所をコメンアウト

android {
    sourceSets {
//            java.srcDirs = ['src']
//            aidl.srcDirs = ['src']
//            renderscript.srcDirs = ['src']
    }
}

//eclipse {
//    ~
//}

//idea {
//    ~
//}

ソースの場所がandroid/srcになっているため、maven形式に戻す。そのためフォルダ構成もandroid/src/android/src/main/java/に移動する。かつ、EclipseとInteliJIDEA用の設定をコメントアウト

f:id:erudoru:20160907220111p:plain

これでNetbeans上で問題なくビルド、実行することが出来るようになりました。

Groovy の ClassLoader

Java で作ったツールを Groovy 上で動かそうとした時に ClassLoader 回りではまってしまったのでメモ

Java のクラスローダ

Java のクラスローダについては以下の通り。

Javaクラスローダー - Wikipedia

まず Java のクラスローダのオブジェクトを取得してみる

public class ShowClassLoader {
 
    public static void main(String[] args) {
 
        ClassLoader loader = ShowClassLoader.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader);
            loader = loader.getParent();
        }
    }
}

継承関係ではなく、特定のクラスローダは親になるクラスローダを parent として保持しているため、getParent メソッドで親をたどれます。 ClassLoader の JavaDoc によると null を返す場合、親がブートストラップクラスローダーを表すとのこと。

sun.misc.Launcher$AppClassLoader@4e25154f
sun.misc.Launcher$ExtClassLoader@33909752

※OracleJDK を使用しているため他の VM では名称や階層が異なるかもしれません。

AppClassLoader、 ExtClassLoader は URLClassLoader を継承しているようなので、getURLs メソッドで読み込んでいる クラス パスを表示できます。

import java.net.URL;
import java.net.URLClassLoader;
 
public class ShowClassPaths {
 
    public static void main(String[] args) {
 
        ClassLoader loader = ShowClassPaths.class.getClassLoader();
        while (loader != null) {
            System.out.println(loader);
            if (loader instanceof URLClassLoader) {
                for (URL url : ((URLClassLoader) loader).getURLs()) {
                    System.out.println(url);
                }
            }
            System.out.println("");
 
            loader = loader.getParent();
        }
    }
}

結果は以下の様になります。

sun.misc.Launcher$AppClassLoader@4e25154f
// -cp オプションもしくは CLASSPATH 環境変数で指定されたパスの一覧

sun.misc.Launcher$ExtClassLoader@33909752
// JAVA_HOME/jre/lib/ext もしくは JRE_HOME/lib/ext 内のパスの一覧

Class.forName(String className) の OpenJDK 実装を見てみると sun.reflect.Reflection クラスを使用して呼び出し元のクラスのクラスローダを取得しているようなので、AppClassLoader が使われているっぽい。
クラスローダの仕様で、まず親から探すらしいので ブートストラップクラスローダー > ExtClassLoader > AppClassLoader の順でクラスが検索されているようです。

Groovy のクラスローダ

Groovy 上でもクラスローダのオブジェクトを取得してみる。
Java の時はせいぜい2階層だったけど、Groovy は取得の仕方によってクラスローダの階層が若干異なります。

def showLoader(loader) {
    def indent = 0
    while (loader != null) {
        print ' ' * (++indent * 2)
        println loader
        loader = loader.parent
    }
    println ''
}

class Test {}
def localObj = new Test()

println 'Class.classLoader'
showLoader Class.classLoader

println 'ClassLoader.systemClassLoader'
showLoader ClassLoader.systemClassLoader

println 'localObj.class.classLoader.rootLoader'
showLoader localObj.class.classLoader.rootLoader

println 'Thread.currentThread().contextClassLoader'
showLoader Thread.currentThread().contextClassLoader

println 'localObj.class.classLoader'
showLoader localObj.class.classLoader

結果は以下の様になります。

Class.classLoader
// 出力なし = ブートストラップクラスローダー

ClassLoader.systemClassLoader
  sun.misc.Launcher$AppClassLoader@55f96302
    sun.misc.Launcher$ExtClassLoader@5e853265

localObj.class.classLoader.rootLoader
  org.codehaus.groovy.tools.RootLoader@2b193f2d
    sun.misc.Launcher$AppClassLoader@55f96302
      sun.misc.Launcher$ExtClassLoader@5e853265

Thread.currentThread().contextClassLoader
  groovy.lang.GroovyClassLoader@4441d8d9
    org.codehaus.groovy.tools.RootLoader@2b193f2d
      sun.misc.Launcher$AppClassLoader@55f96302
        sun.misc.Launcher$ExtClassLoader@5e853265

localObj.class.classLoader
  groovy.lang.GroovyClassLoader$InnerLoader@4a694160
    groovy.lang.GroovyClassLoader@4441d8d9
      org.codehaus.groovy.tools.RootLoader@2b193f2d
        sun.misc.Launcher$AppClassLoader@55f96302
          sun.misc.Launcher$ExtClassLoader@5e853265

クラスパスも出力

def showClassPaths(loader) {
    while (loader != null) {
        println loader
        println loader.getURLs()
        println ''
        loader = loader.parent
    }
    println ''
}
class Test {}
def localObj = new Test()
showClassPaths localObj.class.classLoader

結果は以下の様になります。

groovy.lang.GroovyClassLoader$InnerLoader@13486a8a
// なし

groovy.lang.GroovyClassLoader@151cdf23
// なし

org.codehaus.groovy.tools.RootLoader@2b193f2d
// -cp オプションもしくは CLASSPATH 環境変数で指定されたパスの一覧
// GROOVY_HOME/lib 内のパスの一覧

sun.misc.Launcher$AppClassLoader@55f96302
// GROOVY_HOME/lib/groovy-x.x.x.jar(のみ)

sun.misc.Launcher$ExtClassLoader@1f554b06
// JAVA_HOME/jre/lib/ext もしくは JRE_HOME/lib/ext 内のパスの一覧

Java の時は AppClassLoader に読み込まれていた CLASSPATH がすべて RootLoader に読まれています。
Groovy 起動時のクラスパスは Groovy のクラスローダに渡され、Groovy 自体を動作させる Java のクラスパスは groovy-x.x.x.jar が固定で指定されています。これは groovy を起動する startGroovy スクリプトで確認できます。

Groovy のクラスローダ (Grab 時)

今まで Grab を使ってガッツリコードを書いたことが無かったので知らなかったのですが、Grab を使う際にも、クラスローダを意識する必要があるらしいです。

@Grab(group='mysql', module='mysql-connector-java', version='x.x.xx')

// JDBC ドライバを使ったコード ~~

上記のように書くだけだと JDBC ドライバが探し出せない様です。
上記のコードでロードされたクラスの位置は以下の様になってます。

groovy.lang.GroovyClassLoader$InnerLoader@13486a8a
// Grab で指定したクラスパス

groovy.lang.GroovyClassLoader@151cdf23
// Grab で指定したクラスパス

org.codehaus.groovy.tools.RootLoader@2b193f2d

sun.misc.Launcher$AppClassLoader@55f96302

sun.misc.Launcher$ExtClassLoader@1f554b06

GroovyClassLoader と GroovyClassLoader$InnerLoader に読み込まれていますが、システムクラスローダに読み込ませてあげないとダメらしいので、Grub では GrabConfig アノテーションが用意されています。

@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='x.x.xx')

// JDBC ドライバを使ったコード ~~

GrabConfig を付けた後にもう一度クラスパスを表示すると

groovy.lang.GroovyClassLoader$InnerLoader@13486a8a
// Grab で指定したクラスパス

groovy.lang.GroovyClassLoader@151cdf23
// Grab で指定したクラスパス

org.codehaus.groovy.tools.RootLoader@2b193f2d
// Grab で指定したクラスパス (NEW!)

sun.misc.Launcher$AppClassLoader@55f96302

sun.misc.Launcher$ExtClassLoader@1f554b06

RootLoader にクラスパスが追加されました。Groovy のシステムクラスローダが RootLoader というのも今始めて知りました。
じゃあ ClassLoader.systemClassLoader で取得できるのは何なんだ?って感じなんですが、これは Java のシステムクラスローダで Groovy のシステムクラスローダとは別物ということで納得するしかなさそう。
今回はまったのがまさにここで、java.net.URL クラスに独自プロトコルを認識させるため Handler クラスを読み込ませたいのに、 OpenJDK の実装を見る限り内部で ClassLoader.getSystemClassLoader() を呼び出しているので Java 上では問題なく動くのに Groovy 上で実行すると、自作のライブラリを全然認識してくれないんですよね(´Д⊂ヽ

今回調べたクラスローダに関しては以上です。

Gluon で Java & Android & iOSアプリ開発

今後 JavaFX の Scene Builder が提供されなくなるということで、Gluon という企業が Scene Builder 8.0 を公開したようです。

Bye Bye JavaFX Scene Builder, Welcome Gluon Scene Builder 8.0.0 | Javalobby

(Scene Builder の話題はここで終了)

公開した Gluon はどうやら JavaFX で デスクトップアプリだけではなく Androidアプリ・iOSアプリを作成できるツールを開発している企業らしい。(ざっくりとした理解)

最近お試しで契約してみた MacinCloud を持て余してたところなので Netbeans の plugin も公開されていることだし、さっそく Windows 上で Java を使った AndroidiOS アプリの動作確認を行ってみました。

Java の設定

Gluon が公開した Scene Builder を試す際に Java 8 Update 40 が必要だったので更新しましたが、これ以降の手順で最新の Java が必須かどうかわかりません。でもとりあえずセキュリティのためにも上げておきましょう。

Netbeans plugin の設定

以下の plugin をインストール

Gluon Plugin - NetBeans Plugin detail

インストール済みだったので必須かどうか不明ですが Gradle Support もインストール

Gradle Support - NetBeans Plugin detail

Gradle の設定

GVM でさくっとインストール。 Gradle Support plugin が 2.3 に対応できてないっぽいので 2.2 で。

プロジェクト作成

新規プロジェクト > JavaFX > Basic Gloun Application

プロジェクトを作成すると以下のコードがプリセットされています。

package org.example.gluon;

import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) {
        StackPane root = new StackPane(new Label("Hello JavaFX World!"));

        Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
        Scene scene = new Scene(root, visualBounds.getWidth(), visualBounds.getHeight());

        stage.setScene(scene);
        stage.show();
    }

}

Android SDK の設定

SDK をダウンロードして環境変数 ANDROID_HOME を設定する。 API は 21必須。 Build-tools は 22 (か21.1.2) 以上。 dx コマンドで ~list というオプションがないと言われたら Build-tools のバージョンを上げる必要あり。

いったん Android で動作確認

Gradle で

gradle androidInstall

Netbeans 上から プロジェクト右クリック > tasks > android > androidInstall を選択。

Installed on device.

BUILD SUCCESSFUL

Total time: 1 mins 55.745 secs

が表示されたらインストール成功。使用している PC はハイスペックではないとはいえ、そこそこ時間がかかります。 インストール後は自動でアプリが立ち上がるわけではないので、手動で起動します。 エミュレータも実行してみましたが、画面真っ暗で起動できず。とりあえず実機での動作確認はできました。アプリの起動時間は特に気にならず、普通のアプリが起動したように感じます。

f:id:erudoru:20150315014641p:plain

MacinCloud 上の iOSシミュレータ で動作確認

MacinCloud 上で iOS 用にビルドをしてみたいと思います。 Mac 上にも GVM と Gradle をインストール。git か何かでコードを Mac 上にダウンロードしたら

gradle launchIPhoneSimulator

でシミュレータを起動できます。 MacinCloud の安いプランだからなのか初回のビルドは10分近くかかりましたが、2回目以降はそこまでかかりませんでした。 RoboVM を使ってるようです。

f:id:erudoru:20150315015915p:plain

動作確認できました(^^)/
Gradle でビルドできるのはいいですね。