Unityでピンボールのフリッパーを作る

##はじめに
この記事は SLP KBIT Advent Calendar 2019 の21日目の記事です。
[SLP KBIT Advent Calendar 2019](https://adventar.org/calendars/4214)
##概要
ピンボールが作りたくなったので、Unityで実装していきたいと思いました。
中でも今回はピンボールのボールをはじく部分を<strong>HingeJoint</strong>を用いてつくっていきます。
##実行環境
Unity version：2019.2.9f1
##参考URL
[スクリプトリファレンス](https://docs.unity3d.com/ja/current/ScriptReference/HingeJoint.html)
[Hinge Jointでシーソーを作る](https://qiita.com/yanosen_jp/items/3c43474dd8c1ee958a79)
[【Unity】 重力を変更する](https://qiita.com/t_Kaku_7/items/fdf5bab18b65f6f9dcb4)
##事前準備
scene上に3Dオブジェクトを置きます。

|名前|3D Object|
|:-:|:-:|
|Floor|Cube|
|Ball|Sphere|
|AxisL, R|Cylinder|
|FlipperL, R|Cube|
このように配置しました。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/9553ec36-50cf-643e-0fb4-b5a874b9782b.png)


##HingeJoint
ヒンジとはドアの接合部に使われているような回転の支点となる部分の事だそうです。
UnityではHingeJointがついている物体をヒンジで接合されているように動かすことができるようです。
今回はHingeJointの中でもSpringという変数をつかって<strong>ばねの力</strong>でボールを弾こうと思います。

##使ってみます
まずHingeJointComponentを軸になるオブジェクトに加えます。
軸には重力がかかってほしくないのでUseGravityのチェックを外し、
物理の影響を受けてほしくないのでIs Kinematicのチェックをつけます。
ここではAxisRにAdd Componentしています。
![スクリーンショット (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/9b4166b5-41aa-0794-ebc2-44d57514a429.png)

HingeJointは2つのRigidbody(剛体)をつなげるので動かす部分にRigidbodyを加えます。
ここではFlipperRにAdd Componentしています
![スクリーンショット (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/8780a6b8-f3da-d417-9bf4-d38c58826165.png)

2つのRigidbodyをHingeJointで繋げるために、
AxisRのHingeJoint内のConenected BodyにFlipperRをドラッグアンドドロップします。

これをAxisLとFlipperLにも行います。

この後AxisRのHingeJointのEdit Joint Angular LimitsでHingeの向きと方向を編集します。
Axisの値で調整します。
矢印を軸として、円に沿って力が加わるので上向きにします。(反時計回りが正)

変更前
![スクリーンショット (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/bc931e09-469e-a41e-b26a-385c7d5a295e.png)
変更後
![スクリーンショット (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/e21979f1-d305-e30e-ff43-606f6aa05be3.png)
この時点でAxisRのHingeJointの
UseSpringにチェックを入れ、Springに40000、Damperに0、TargeetPositionを-60にすると
![スクリーンショット (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/d411a38d-7f9c-965c-3edd-3aafa266a8b7.png)
このようになりました。(AxisLも変更済み)

この時にフリッパーが回転しすぎたりしないようにAxisRのHingeJoint内のUse Limitsにチェックを入れ、
Minに-60、Maxに0を入れます。(AxisLも同様)

この後、左クリックで左フリッパー、右クリックで右フリッパーを動かしてみます。
##コード
~~~C#:Flipper.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Flipper : MonoBehaviour
{
    // Inspecterで値を変更する
    public float spring = 40000;
    public float openAngle = 60; // 開く角度
    public float closeAngle = 0; // 閉じる角度
    
    // Hinge Joint
    private HingeJoint hjL; // AxisL
    private HingeJoint hjR; // AxisR

    // JointSpring
    private JointSpring jL; // AxisL
    private JointSpring jR; // AxisR

    void Start()
    {
        // AxisLとAxisRを探す
        GameObject flipperL = GameObject.Find("AxisL");
        GameObject flipperR = GameObject.Find("AxisR");

        // HingeJointを受け取る
        hjL = flipperL.GetComponent<HingeJoint>();
        hjR = flipperR.GetComponent<HingeJoint>();

        // Springを受け取る
        jL = hjL.spring;
        jR = hjR.spring;
    }

    void Update()
    {
        // 左クリックを押す
        if (Input.GetMouseButtonDown(0))
        {
            jL.spring = spring;
            jL.targetPosition = openAngle;
            hjL.spring = jL;

        }
        // 左クリックを離す
        if (Input.GetMouseButtonUp(0))
        {
            jL.spring = spring;
            jL.targetPosition = closeAngle;
            hjL.spring = jL;
        }
        // 右クリックを押す
        if (Input.GetMouseButtonDown(1))
        {
            jR.spring = spring;
            jR.targetPosition = -openAngle;
            hjR.spring = jR;
        }
        // 右クリックを離す
        if (Input.GetMouseButtonUp(1))
        {
            jR.spring = spring;
            jR.targetPosition = closeAngle;
            hjR.spring = jR;
        }
    }   
}
~~~
Update関数内でボタンを検知しています。
左クリックをすると40000の力で60度左フリッパーが回転、
右クリックをすると40000の力で-60度右クリックが回転します。

##実行結果
Edit→Project Settings→PhysicsでGravityの方向を変更します。
フリッパーが重力の影響を受けないようにFlipperL、FlipperRのUseGravityのチェックを外しました。
重力を反映させるためBallにRigidbodyをつけます。
実行するとこんな感じになりました。
![gifanime-compressor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309110/e03eef3b-078d-8146-24ed-60ac2171a09d.gif)

カクついていますが何とか動いています。

##終わりに
結構ちゃんとフリッパーしてくれていると感じました。
ピンボールはフリッパーだけではできないので他の部分も実装してみたいと思いました。