制御のゲーム

# はじめに
倒立振子で相撲をしよう。
というゲーム『Stable Master』(親方の意)を作ったのでプレアルファですが公開します。
[こちら](https://play.unity.com/mg/other/sumo-0cpk)から触ってみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/dc5d41f2-4855-e9b7-6176-2e7c0e6b0fa7.png)

# 基本ルール
## 力士のモデル
力士は下図のような車輪型倒立振子として表されています。プレイヤーは車輪の並進速度と振子の角度(＋ユーザ入力)から車輪のトルクを決定する制御器を実装することで力士を操作します。

デフォルトでは左右キーまたはA/Dキーでそれぞれの方向のトルクが印加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/439b8048-f734-dc60-ba24-1b77db83c3ad.png)

## ゲームルール
トップページのSTARTボタンからゲームを開始できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/fe617346-e3e6-e82a-649a-16955d811279.png)

- 相撲と同じく、土俵内で振子部分が接地するか、土俵外に出た場合負けとなります。
- 力士はそれぞれ「スタミナ」が定められており、スタミナ量は画面上部のバーで表示されます。
- スタミナは印加トルクの2乗に比例して消費され、時間経過で回復します。
- スタミナが0になった場合、一定時間トルクが印加できなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/65625008-85b1-4781-5b48-7a3244a12ebe.png)

# 制御器設計
ユーザは力士を制御するためのコントローラを設計することができます。
トップページのDESIGNボタンで設計画面を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/be35fbd8-184e-8f2a-ef17-0e6f342427a3.png)

設計画面右側のテキストボックスに[Miniscript](https://miniscript.org)を使って制御用スクリプトを記述することで、力士の挙動を変更できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211913/89db24ec-3de3-86d9-f967-e83af5320382.png)

Miniscriptの文法は[こちら](https://miniscript.org/files/MiniScript-QuickRef.pdf)をご参考ください。

このスクリプトでは、ゲーム側が事前に設定した以下の変数をグローバルに使用できます。

- `initialize`: 初期化フラグ。このスクリプトはゲーム開始前の初期化時に一度、ゲームが始まってからは毎サンプルごとに呼び出されます。初期化時にはこの変数が`initialize = 1`に、ゲーム開始後は0に設定されます。
- `wheel_velocity`: 車輪部の右向き並進速度です。
- `pole_rotation`: 振子部の右回り角度です。
- `horizontal_input`: ユーザからの左右キーボード入力(矢印キーまたはA/Dキー)です。左入力の場合-1、右入力の場合1が設定されます。
- `space_input`: ユーザからのスペースキー入力です。押下時1、解放時0が設定されます。

デフォルトでは以下のスクリプトが設定されており、PID制御を実装しています。

```Miniscript
// Predefined Variables
//  - initialize:
//      Initialization flag. 1(True), 0(False)
//  - wheel_velocity: 
//      Horizontal velocity of wheel. (right is positive)
//  - pole_rotation:
//      Angle of pole. (deg, clockwise is positive)
//  - horizontai_input:
//      Horizontal axis input from keyboard. ([-1.0, 1.0])
//  - space_input:
//      Space bar input flag. (1(pressed), 0(released))

// Write initialization code here
if initialize == 1 then
    // Define PID controller class
    PID = {
        "gain_p": 0,
        "gain_i": 0,
        "gain_d": 0,
        "error_sum": 0,
        "error_previous": 0 }

    // Step method
    PID.Step = function(error)
        self.error_sum = self.error_sum + error
        control = self.gain_p * error +
            self.gain_i * self.error_sum +
            self.gain_d * (error - self.error_previous)
        self.error_previous = error

        return control
    end function

    // Instantiate controller
    pid = new PID
    pid.gain_p = 5.0
    pid.gain_i = 0.0
    pid.gain_d = 5.0

    // Define function calculates angular difference
    AngleDiff = function(a1, a2)
        diff = a1 - a2
        if abs(diff) < 180 then
            return diff
        else
            return diff - sign(diff) * 360
        end if
    end function

// Write code for control here
else
    // Feedback control
    error = AngleDiff(pole_rotation, 0)
    torque = pid.Step(error) + 10 * horizontal_input
end if
```

スクリプトの長さやメモリ使用量にゲーム側で制限は掛けていませんが、10 ms以内に実行が完了しない場合にはエラーが表示されます。