カスタム エンジン ビューポートを作成する

viewports 拡張機能を使用すると、プラグインによって Stingray エディタに追加されたカスタム パネルまたはビューにエンジン ビューポートを埋め込むことができます。

また、ビューポートの操作方法や、ビューポートでレンダリングされているエンジンの動作方法を定義する、一連のカスタム JavaScript ファイルおよび Lua ファイルを提供して、このビューポートの動作をコントロールすることもできます。

この拡張機能は、Asset Preview をコントロールする previews 拡張機能と非常に似ています。「カスタム アセットのプレビューを定義する」も参照してください。ただし、viewports 拡張機能を使用すると、既存の Asset Preview パネルをコントロールできるだけでなく、自分が決定したコンテンツおよび相互作用を含む独自のビューポートを独自のパネル内で作成することができます。

環境設定

ビューポート拡張機能を使用するには、次のパラメータが必要です。

extensions = {
    viewports = [
        {
            name = "particle-viewport"
            engine = "particle_editor/particle-behavior.lua"
            module = "particle-viewport.js"
        }
    ]

name

ビューポートをインスタンス化して、Lua コマンドを送信するために内部で使用される、ビューポートの分かりやすい名前です。この名前は、Stingray エディタ内で登録されているすべてのビューで一意である必要があります。必須

engine

ビューポート内で実行されているエンジンの動作をコントロールする、Lua ファイルのパスおよびファイル名です。.stingray_plugin 記述子ファイルの場所を基準として指定します。このファイルは一般に、core/editor_slave/stingray_editor/viewport_behavior.lua で公開されている関数の一部を上書きします。必須

Lua ファイルはプロジェクト コンテンツ内に配置する必要があることに注意してください。resources 拡張機能を使用して、このフォルダをプロジェクトの一部としてマウントすることが必要になる可能性があります。「プロジェクト コンテンツを拡張する」を参照してください。

module

ビューポートをインスタンス化する前に初期化コードを実行し、ビューポートとの高度な相互作用(マウスの動作など)をコントロールするためにエディタによって呼び出される JavaScript モジュールのパスです。必須

次のセクションでは、新しいパーティクル エディタ プラグインを例として使用します。このファイルは、Stingray インストール フォルダ内の editor/plugins/particle-editor 内にあります。以下の例では、選択したパーティクル システムをプレビューするためにエンジン ビューポートを使用して、パーティクルの設定に対してユーザが行った変更とプレビューの同期を維持します。

(このプラグインの作業はまだ継続中であるため、このリリースでは既定で有効になっていません。エディタ内でこのプラグインを試してみる場合は、.stingray_plugin ファイルに移動し、menus および views 拡張機能の先頭にあるコメントを除去する必要があります。)

ビューポートの相互作用の概要

ユーザがエディタ内のビューポートを操作する場合の一般的な制御フローは、次のとおりです。

  1. エディタはビューポートとのすべての相互作用を捕捉します。

  2. エディタはこれらの相互作用を EngineViewport JavaScript コントローラ editor/core/components/engine-viewport.js に転送します。

  3. EngineViewport コントローラはユーザ定義の JavaScript ViewportBehavior を検索して、そのイベントをユーザ定義の動作に転送します。これらの動作の例として、core/plugins/particle_editor/particle-viewport.js を参照してください。つまり、現在の ViewportBehavior を変更することにより、ビューポートとユーザの相互作用方法を変更することができます。

  4. ViewportBehavior はエンジンの特定のビューポートに Lua コマンドを送信します。これらのコマンドは、core/editor_slave/stingray_editor/editor_viewport.lua に実装されている標準の Lua EditorViewport コントローラで受信されます。

  5. Lua EditorViewport は入力を現在のユーザ定義の Lua 動作に転送します。転送先で、プラグインはユニットのスポーン、マテリアルの変更など、エンジン内で発生する動作をトリガして、ユーザ入力に応答することができます。この Lua 動作を定義する仕様については、core/editor_slave/stingray_editor/viewport_behavior.lua を参照してください。例については、core/plugins/particle_editor/particle_editor/particle-behavior.lua も参照してください。

JavaScript でのビューポートの設定

ビューポートの module パラメータで設定された JavaScript ファイルは、setup() 関数を含むモジュールを返す必要があります。ビューポートがインスタンス化されていて、使用する準備が整っている場合、エディタはこの関数を呼び出します。たとえば、ユーザはこの関数を使用して初期レベルをロードしたり、ViewportBehavior を実装したり、ビューポートと他の Stingray サービス間の相互作用を微調整したりできます。

setup 関数の返り値は、ビューポート ウィンドウからユーザ イベントを直接受信する ViewportBehavior オブジェクトです。

// Example from particle-viewport.js

define([
    'services/engine-service',
    'services/engine-viewport-service'
], function () {
    'use strict';

    var engineService = require('services/engine-service');
    var engineViewportService = require('services/engine-viewport-service');

    /**
     * Viewport extension test controller. Implements a viewport extension controller module.
     * @module ParticleViewportController
     */
    var ParticleViewportController = {
        /**
         * Setup the viewport extension controller.
         * @memberof ParticleViewportController#
         * @param {string} engineViewportId - id of the newly created viewport.
         * @param {EngineViewportInterops} engineViewportInterops - engine viewport interop utility class
         */
        setup: function (engineViewportId, engineViewportInterops) {
            this.mouseBehavior = new MouseBehavior(engineViewportId, engineViewportInterops);

            var off = null;
            // Setup will be resolved when it receives a Viewportcreated event.
            return new Promise(function (resolve) {
                off = engineViewportService.on('ViewportCreated', function (id) {
                    if (id === engineViewportId) {
                        resolve();
                    }
                });

                engineViewportService.getViewportNameFromId(engineViewportId).then(function (name) {
                    if (name) {
                        // Viewport has already been created, so no need to wait any longer.
                        resolve();
                    }
                });
            }).then(function () {
                return engineViewportInterops.raise(engineViewportId, 'load_background_level', 'core/editor_slave/resources/levels/empty_level');
            }).then(function () {
                if (off) {
                    off();
                }

                // Notice how the result
                return this.mouseBehavior;
            }.bind(this));
        }
    };

    return ParticleViewportController;
});

上記の例の engineViewportInterops オブジェクトは、Lua 環境と JavaScript 環境間の通信をパッケージ化するヘルパーです。このヘルパーを使用して、新しい ViewportBehavior を記述することができます。この API は次のとおりです。

// From editor\core\components\engine-viewport.js
// All of these functions return a promise and send a Lua command directly to the engine through a websocket connection.
var EngineViewportInterops = {
    invoke: function (viewportId, method) {},

    // Will eventually do an Editor:raise()
    raise: function (viewportId, method) {},

    keyDown: function (viewportId, keyCode) {},

    keyUp: function (viewportId, keyCode) {},

    mouseLeftDown: function (viewportId, x, y) {},

    mouseLeftUp: function (viewportId, x, y) {},

    mouseMiddleDown: function (viewportId, x, y) {},

    mouseMiddleUp: function (viewportId, x, y) {},

    mouseRightDown: function (viewportId, x, y) {},

    mouseRightUp: function (viewportId, x, y) {},

    mouseMove: function (viewportId, x, y, dx, dy) {},

    mouseWheel: function (viewportId, delta) {}
};

このヘルパーを使用して、JavaScript から ViewportBehavior を作成できます。

// From core/plugins/particle_editor/particle-viewport.js
// This MouseBehavior implements all functions supported by a ViewportBehavior.
var MouseBehavior = function (engineViewportId, engineViewportInterops) {
        this.viewportId = engineViewportId;
        this.engineViewportInterops = engineViewportInterops;
    };

    MouseBehavior.prototype = {
        mouseDown: function (evt) {
            var x = evt.originalEvent.detail.positionX;
            var y = evt.originalEvent.detail.positionY;
            var buttonNumber = evt.originalEvent.detail.button;
            console.log(buttonNumber);

            engineService.sendToLocalEditors('Editor:set_camera_control_style(%s, %s)', luaUtils.toSyntax(this.viewportId), luaUtils.toSyntax("MayaStyleTurntableRotation"));

            switch (buttonNumber) {
                case 0: this.engineViewportInterops.mouseLeftDown(this.viewportId, x, y);
                    break;
                case 1:this.engineViewportInterops.mouseMiddleDown(this.viewportId, x, y);
                    break;
                case 2:this.engineViewportInterops.mouseRightDown(this.viewportId, x, y);
                    break;
            }
        },

        mouseUp: function (evt) {
            var x = evt.originalEvent.detail.positionX;
            var y = evt.originalEvent.detail.positionY;
            var buttonNumber = evt.originalEvent.detail.button;

            engineService.sendToLocalEditors('Editor:set_camera_control_style(%s, %s)', luaUtils.toSyntax(this.viewportId), luaUtils.toSyntax("None"));

            switch (buttonNumber) {
                case 0: this.engineViewportInterops.mouseLeftUp(this.viewportId, x, y);
                    break;
                case 1:this.engineViewportInterops.mouseMiddleUp(this.viewportId, x, y);
                    break;
                case 2:this.engineViewportInterops.mouseRightUp(this.viewportId, x, y);
                    break;
            }
        },

        mouseMove: function (evt) {
            var x = evt.originalEvent.detail.positionX;
            var y = evt.originalEvent.detail.positionY;
            var deltaX = evt.originalEvent.detail.deltaX;
            var deltaY = evt.originalEvent.detail.deltaY;
            this.engineViewportInterops.mouseMove(this.viewportId, x, y, deltaX, deltaY);
        },

        mouseWheel: function (evt) {
            this.engineViewportInterops.mouseWheel(this.viewportId, -evt.originalEvent.deltaY);
        },

        keyDown: function (evt) {
            this.engineViewportInterops.keyDown(this.viewportId, evt.originalEvent.detail.keyCode);
        },

        keyUp: function (evt) {
            this.engineViewportInterops.keyUp(this.viewportId, evt.originalEvent.detail.keyCode);
        },

        viewportResized: function () {

        },

        viewportDrop: function () {

        }
    };

Lua でエンジンの動作をコントロールする

JavaScript ViewportBehavior の Lua の対応部分を使用すると、ユーザはエンジンと直接相互作用できます。新しいユニットのスポーン、マテリアルの設定、カメラのコントロール、シェーディング環境の変更など、ユーザ操作によってトリガされるエフェクトをエンジン内で生成するためにプラグインで実行しなければならないすべての処理を実行できます。

Lua ViewportBehavior では次のインタフェースを実装できます。

// From core/editor_slave/stingray_editor/viewport_behavior.lua
ViewportBehavior = interface {
    required = {
        render = function(editor_viewport, lines, lines_no_z) end,
        is_accepting_drag_and_drop = function() return does_accept end,
        world = function() return world end,
        editor_camera = function() return editor_camera end,
        selected_units = function() return selected_units_array end,
        shading_environment = function() return shading_environment end
    },
    optional = {
        update = function(editor_viewport, dt) end,
        shutdown = function() end,
        reset = function() end, -- Called at editor start and at every level change.
        activated = function() end,
        key_down = function(key) end,
        key_up = function(key) end,
        mouse_move = function(x, y, dx, dy, viewport) end,
        mouse_left_down = function(x, y, viewport) end,
        mouse_left_up = function(x, y, viewport) end,
        mouse_middle_down = function(x, y, viewport) end,
        mouse_middle_up = function(x, y, viewport) end,
        mouse_right_down = function(x, y, viewport) end,
        mouse_right_up = function(x, y, viewport) end,
        mouse_wheel = function(delta, steps, viewport) end,
        is_dirty = function() return is_dirty end,
        grid = function() return grid end,
        toolbar_behavior = function() return toolbar_behavior end,
        set_skydome_unit = function(unit) end,
        set_shading_environment = function(shading_environment) end,
        pre_render = function(viewport) end,
        post_render = function(viewport) end
    }
}

パーティクル エフェクトをコントロールする実装については、core/plugins/particle_editor/particle_editor/particle-behavior.lua を参照してください。

ビューポートを作成する(Mithril を使用)

JavaScript ビューにビューポートを追加する最も簡単な方法は、EngineViewport Mithril コンポーネントを使用することです。

Mithril は、JavaScript ビューの作成およびデータ モデルとの同期の維持を簡単に実行できる、軽量のフレームワークです。このフレームワークを使用してエンジン ビューポートを含むビューを作成する作業の例については、editor/plugins/particle_editor/particle-editor.js を参照してください。

Mithril ビューに 3D ビューポートをホストする最も簡単な方法は、通常、plugin_tests/viewort_extension/viewport-extension-test.js に示されているメソッドを使用することです。

define([
    'app',
    'stingray',
    'lodash',
    '3rdparty/mithril/mithril.min',
    'components/engine-viewport'
], function () {
    'use strict';

    var m = require('3rdparty/mithril/mithril.min');
    var EngineViewport = require('components/engine-viewport');

    var $container = $("#viewport-here");

    // Use Mithril mount function to attach the EngineViewport component and pass it the name of the
    // viewport setup file (viewport-extension-test.js)
    m.mount($container[0], EngineViewport.component({
        name: "viewport-extension-test"
    }));
});

Mithril ビューを #viewport-here 要素に接続するには、パネル内で次の HTML コンテンツが必要となります。

<!-- From plugin_tests/viewort_extension/viewport-extension-test.html -->
<div class="module-viewport-extension-test fullscreen">
    <!-- This element is the anchor on which we attach the viewport -->
    <div class="fullscreen" id="viewport-here"></div>
</div>

ビューポートの拡張機能テストのスクリーンショット: