【Unity】ゲームパットの接続/切断時に通知を出してあげる

f:id:coffee_ryo:20190203020248g:plain

概要

#StrangeTelephone を開発されている方のツイートを見て、ゲーム側でゲームパット接続の通知を出してあげることに良さみを感じました。
接続した際に、これがゲームで使えるかどうかを教えてあげるのはプレイヤーに対して優しいですね。
Nintendo SwitchなんかでもJoyConくっつけたら画面に表示されますし。

これについて、Unityで実装をしてみました。

確認環境

Unity2018.2.20f1 Personal

Input.GetJoystickNamesを使う方法

docs.unity3d.com

スクリプト
using UnityEngine;
using System.Linq;

/// <summary>
/// ゲームパット接続/切断 サンプルコード
/// </summary>
public class OutputLog : MonoBehaviour {
    string[] CacheJoystickNames;

    void Start () {
        CacheJoystickNames = Input.GetJoystickNames();
    }
    
    void Update () {
        var joystickNames = Input.GetJoystickNames();

        // ※同一フレーム中に接続/切断した時については未検証
        // ※複数のゲームパットでは動作未検証
        if(CacheJoystickNames.Length > joystickNames.Length){
            Debug.Log("切断" + CacheJoystickNames.Except(joystickNames).ToList()[0]);
        }

        if(CacheJoystickNames.Length < joystickNames.Length){
            Debug.Log("接続" + joystickNames.Except(CacheJoystickNames).ToList()[0]);
        }

        CacheJoystickNames = joystickNames;
    }
}
結果

f:id:coffee_ryo:20190203013004p:plain

ゲームパット接続/切断時を取得することができました。

Rewiredのイベントを使う方法

assetstore.unity.com

様々なゲームパットの対応を用意にしてくれるAssetのRewiredを使用している場合、
Rewired側がイベントの用意をしてくれているので、公式ドキュメントにならえば良いです。

guavaman.com

Receiving Joystick connect and disconnect events

その他

あとはuGUIやDotweenなどで画面を作ってあげたり、通知時にSEを鳴らしたりすれば良さそうです。

f:id:coffee_ryo:20190203020248g:plain

【Unity】2Dゲーム 斜め移動の移動量が大きくなる件

概要

2Dのゼルダのような、見下ろし型でプレイヤーを四方八方動かせるようなゲームにおいて、
移動の実装方法によっては、斜め方向の移動速度が大きくなり手触り感に支障をきたす場合があります。

例えば、以下の処理の場合、inputX=1, inputY=1が入力されると
斜め方向の移動量は√2になります。
これは、三平方の定理を使えば算出できます。

using UnityEngine;

public class PlayerMover : MonoBehaviour {
    
    void Update () {

        var inputX = Input.GetAxisRaw("Horizontal");
        var inputY = Input.GetAxisRaw("Vertical");

        this.transform.position += new Vector3(inputX, inputY, 0);
    }
}

斜め移動の量を、水平や垂直方向と同じ様にするには、
以下のサイトの様に斜め移動時に補正値をかけることで対応できます。
斜めに移動させる | ゲームプログラミング入門~bituse~

角度が45,135,225,315度以外の時の実装方法が見当たらなかったので、
コードを書いてみました。

実装

using UnityEngine;
using System;

public class PlayerMover : MonoBehaviour {
    
    void Update () {

        var inputX = Input.GetAxisRaw("Horizontal");
        var inputY = Input.GetAxisRaw("Vertical");

        var moveVectorBeforeCorrection = new Vector2(inputX, inputY);

        //極座標の距離rを計算
        var r = Mathf.Sqrt(
            moveVectorBeforeCorrection.x * moveVectorBeforeCorrection.x +
            moveVectorBeforeCorrection.y * moveVectorBeforeCorrection.y
            );
        
        // 極座標のラジアンを計算
        var radian = Vector2.Angle(Vector2.right, moveVectorBeforeCorrection) * Mathf.Deg2Rad;

        // 斜め移動の補正
        // 斜め45度の時、補正を最大にする。水平や垂直の時は補正をかけない。
        //
        // ・角度が45,135,225,315の時→Cosが0になるので、rには0.7071fの補正値が乗算される
        // ・角度が0,90,180,270の時→Cosが1になるので、rには0.7071f+0.2928f = 1の補正値が乗算される(つまり補正されない)
        r *= (0.7071f + 0.2928f * Mathf.Abs(Mathf.Cos(2 * radian)));

        if(inputY >= 0){
            this.transform.position += new Vector3(r * Mathf.Cos(radian), r * Mathf.Sin(radian), 0);
        }else{
            this.transform.position += new Vector3(r * Mathf.Cos(radian), -r * Mathf.Sin(radian), 0);
        }
    }
}

角度が45,135,225,315度の時、移動量がもっとも大きくなってしまうので、
この際の補正値がもっとも大きくなる様に三角関数を組みました。

unityroomアドベントカレンダー2018 よもやま話

概要

渾身のバグに溢れる、プロトタイプを投稿しました。
よかったら遊んでください。PCブラウザでプレイできます。

RePainter | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

コンセプトについて

  • 自分の作ったオリジナル技で敵を倒していくロマン・かっこよさ
  • ゲームクリア=イラストうまいね!という評価をプレイヤーに与えるもの

を目指しています。

「アニメとかに憧れてイラストを描き始めた・・・」
「しかし描き始めると自分のイラストがクソすぎて挫折した・・・」
Twitterで神絵師がアップしている絵がめちゃくちゃ評価される・・・」
「誰にも評価されないからイラストやめる・・・」

このゲームは、
「そんな創作活動に挫折してしまった人をなんとか勇気付けて、もう一度復帰できたらいいな〜」
というところからスタートしてます。

将来的には対人戦であったり、村人に絵を描いてあげるアドベンチャー的要素とか謎解きとか
グラフィックとか音楽めちゃくちゃ気合い入れるとか
色々実験したいことがあるんですが
とりあえず1年間程度で作りきれる規模で進めています。
まだ完成していないけど次回作予定しています。

イラスト攻撃の実装方法について

描画については、今の所全てLineRendererに任せています。
気になる方がいらっしゃったら、こちらの記事をどうぞ。

coffee-ryo.hatenablog.com

coffee-ryo.hatenablog.com

今後について

リリースは2019年の夏か秋頃を予定しています。価格はわかりません。数百円かフリーゲームになるか。
本当はデジゲー博で出したかったのですが見事落選。。。!

「TokyoIndies」という月1の集まりで展示している場合があります。
この場で最新の進捗出しているので、ご興味持たれました方よかったら声かけていただけると嬉しいです。

twitter.com

【Unity備忘録】AudioMixer Normalize Effectをかけると音量が極端に小さくなる件

内容

unity 2018.2.13f1

  • 敵を爆発させるSE音を同時に10個再生
  • 音割れが激しいので、AudioMixerのNormalizeエフェクトを適用
  • 10個再生時の音割れは解消したが、10個再生後、単発で音を鳴らすとSEが小さすぎて聞こえない

対策

Normalizeエフェクトのパラメーターを調整

f:id:coffee_ryo:20181204141541p:plain

公式にリファレンスがあるが、理解ができなかったので色々試したところ
Fade in time プロパティの変更でうまくいった。

Fade in time プロパティを0にすると、音割れが発生する。
Maxにすると、音割れは発生しないがSEが小さすぎて聞こえなくなる現象が発生する。
→ このエフェクトは時間の経過により効き目が減衰する模様で、どれくらい経過したら減衰するかをこのパラメーターで調整できるっぽい

https://docs.unity3d.com/ja/current/Manual/class-AudioNormalizeEffect.html

レイドボス「naichi」登場!というゲームを作りました。

概要

第10回目開催、おめでとうございます!
unityroomさんにはお世話になっていて、リスペクトを表現したく
いつかnaichiさんをラスボスとしたゲームを作りたいなー考えていました。

そして、今回ついに作ることができました。
レイドボス「naichi」登場!

レイドボス「naichi」登場! | 無料ゲーム投稿サイト unityroom - Unityのゲームをアップロードして公開しよう

燃え尽き症候群となったunityroom運営者を
みんなでクリックして褒めて、やる気を出してもらうゲームです。

企画する

以下2つの条件を満たすものを考えていました。

  1. naichiさんがラスボス
  2. unityroomの人ならすごく盛り上がれる何か

実在する1人の人をラスボスにしている時点で、楽しめる人が限られる。
ならいっそもうunityroomに全寄せしてしまおう!と考えました。

で、プラットフォーム系のサービスはそこで活動しているクリエイターさんありき、だと思ったので
じゃあnaichiさんだけじゃなくて他の開発者様にも登場してもらおう!と。

それから開発期間1週間の制限を合わせ、考えついたのが
プレイヤー協力型のクリッカーゲーム「レイドボス「naichi」登場!」です。
「unity1weekに挑戦している」というゆるい繋がりで結ばれたプレイヤー同士で遊んだら
面白いんじゃね?と予想してました。

交渉する

今回の製作で一番難しかったところです。

勝手に名前を使うのはご法度、なので
登場して欲しい開発者様にコンタクトをとりました。
面と向かって話したこともあったこともない人に
コンタクト時何伝えようか悩んだんですが、とにかく「意味が伝わるか?」を色々考え
最終的に4点を意識するようにしました。

  • 自己紹介 (unityroomで動いている人間です)
  • こっちがやりたいこと (ゲームのOPで開発者を登場させたい)
  • ご協力いただく範囲 (Twitterアイコンとこちらで考えたセリフの使用許可)
  • ご協力いただいた際のリターン (クレジットにお名前表示、あれば拡散したいもののURLなど)

コンタクトとった6名の開発者様からは初回の返信で、 「全面OK、またはセリフだけ変えればOK」
でしたので、こちらの意図はうまく伝わったのかなと思います。

リターンについては、こちらが提示できるものが少なく...
この条件でご協力いただいた開発者様に感謝申し上げます。

それから、unityroom開発者の中で誰に依頼するか...なのですが
界隈で活躍されている方の中で、

  • 自分がリツイートとかいいねとかちょいちょいしている人
  • TLをみて、コラボ系の話乗ってくれそうと判断した人
  • TLをみて、すごく忙しくなさそうな人
  • ツイッターアイコンが顔写真じゃない人

であれば、快く承諾してくださるかなーと思ってお声がけしました。
OKをいただいた6名の開発者様の他にも何名か依頼しようとしていたのですが、
DMが解放されていなかったり、話しかけて良いかわからず不安だったりで
最終的にコンタクト取らなかった方もいらっしゃいます。

今回は穏便に行きましたが、1歩間違えると大炎上およびunityroom出禁のリスクがある、
というのを感じてて、非常にドキドキしてました。

まとめ

次回開催時、開発者の名前を使いたい方はぜひ自分にお声がけください。welcomeです。
チーム開発もお待ちしてます(チームで開発したことないですが...)
そして、unityroomに感謝!

おまけの「開発する」

全部かくと大変なので、工夫したところを絞って書きます。

シーン全体で参照したいデータについて

小規模個人開発で使える、シングルトンでデータを持たせる形にしています。
どんなシングルトンかというと。。。

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using CoffeeR.Model;

public class ServiceLocator : SingletonMonoBehaviour<ServiceLocator> {

    /// <summary>
    /// シーン毎に初期化されるコンテナ
    /// </summary>
    List<object> sceneContainer;

    override protected void Awake ()
    {
        if (this != Instance)
        {
            Destroy(this.gameObject);
            Debug.Log(
                typeof(ServiceLocator) +
                " は既に他のGameObjectにアタッチされているため、コンポーネントを破棄しました." +
                " アタッチされているGameObjectは " + Instance.gameObject.name + " です.");
            return;
        }

        Initialize();

        DontDestroyOnLoad(this.gameObject);
    }

    void Start () {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    void OnSceneLoaded( Scene scene, LoadSceneMode mode ){
        Initialize();
    }

    void Initialize() {
        sceneContainer = new List<object>();

        var gameState = new GameState();
        var score = new Score();
        var tooltip = new ToolTipModel();
        var todo = new Todo();

        sceneContainer.Add(gameState);
        sceneContainer.Add(score);
        sceneContainer.Add(tooltip);
        sceneContainer.Add(todo);
    }

    public T Get<T>() {

        for(int i = 0; i < sceneContainer.Count; i++){
            if(typeof(T) == sceneContainer[i].GetType()){
                return (T)sceneContainer[i];
            }
        }
        
        throw new System.Exception(typeof(T) + "のインスタンスを取得できませんでした。");
    }
}

シーン全体で使う変数はクラス化、インスタンスを「sceneContainer」に入れてます。
使うときはこんな感じで、Get()だけ使えばよし。

var score = ServiceLocator.Instance.Get<Score>();
var gameState = ServiceLocator.Instance.Get<GameState>();
ボタンのアニメーションについて

押せるボタンについては、アニメーションの処理が記述されたコンポーネントをアタッチしてます。

using UnityEngine;
using UnityEngine.EventSystems;

public class GeneralUIReaction : MonoBehaviour , IPointerEnterHandler, IPointerClickHandler, IPointerExitHandler{

    public void OnPointerClick(PointerEventData eventData){
        // ここにクリックした時のアニメーション処理
    }

    public void OnPointerEnter( PointerEventData eventData ){
        // ここにマウスポインターが入った時のアニメーション処理
    }

    public void OnPointerExit(PointerEventData eventData){
        // ここにマウスポインターが離れた時のアニメーション処理
    }
}

ゲーム上で押せるボタンはどれなのか、プレイヤーに早く学習させたかったので
どの押せるボタンにも、上記のコンポーネントをアタッチしました。

リアルタイムっぽくクリック数を集計する

クリック数はニフクラのモバイルバックエンドから取ってきてます。
で、10秒おきに各プレイヤーのクリック数をsumするという、
日頃DB触っている人にとっては激おこ案件になる恐ろしいことをしています。

// ※UniRxというライブラリを使ってます。

//  10秒おきに、
Observable.Interval(System.TimeSpan.FromSeconds(10))
.Subscribe(_ => {
        // サーバーにある、自身以外のプレイヤー全員のスコアを通信して取得
    Observable.FromCoroutineValue<int>(NCMBClient.LoadOtherPlayersScore)
    .Subscribe(otherScore => {
                 // 通信して取得したデータを、変数に入れておくよ
        score.OtherPlayersScore.Value = otherScore;
        score.OtherPlayersScoreOfLoadTiming = otherScore;
    }).AddTo(this);
}).AddTo(this);

開発中のゲームを展示・試遊会に持っていってやらかしたこと一覧

概要

開発者が集まってゲームを見せ合ったり情報交換する集まりに2回参加し、
会場で開発中のゲームを展示しました。
外部で展示するのは初めての経験だったのですが、
他の展示者と比べて「こうした方が良かった」点があったり、
1回目と2回目の差で色々気づきがあったので、書いていきます。

展示した時の状況
  • PC1台を持ち込み
  • 会場の人数は体感で50人
  • 同様に展示している方が数名
  • 参加者の何割かが外国出身

やらかしたことまとめ

進行不能バグがある

バグったたびにアプリを再起動しなきゃいけない。
この作業中、どんな目でプレイヤーに見られているか不安になり背筋に冷や汗がつたう。
貴重な時間を奪ってしまうし、展示側としては精神衛生上よくない。
バグっている部分は展示では見せない!

プレイされていない時にタイトル画面を表示

通りすがった人が、どんなゲームなのかわからない。
そもそもゲームなのかわからない。
プレイされていない時はトレーラーやデモ動画を流す!

英語が話せない・英語で伝えられる媒体がない

日本語通じない人がいる。。。
操作方法も、コンセプトもうまく伝えることができない。
次回参加時は「どんなゲームか」「操作方法はこうです」といったのが英語まとめられている 立て札を用意して参加してみようと思います。

名刺がない

いろんな人(開発者、作曲、ライター、etc)がきて名刺を渡してくれる中で自分の名刺がない。
いただいたのに返せないというのが精神衛生上よくなかったので
自分の名刺を持つ!

思ったようにコンセプトが話せない

開発作業ばっかに集中しているためか、緊張してしまって
どんなことが体験できるの?ウリなの?がきちんと話せない。
営業トークの練習をしておく!

スマホをいじってしまう

展示会、一人ぼっちで寂しくてスマホ触っていたのですが、
後々考えるとプレイヤーさんが話しかけにくい状況だったかなと思ってます。
スマホいじらない!

無表情

色々初めてのことだらけで精一杯で、振り返ってみると切羽詰まった顔になってたかもしれないです。
ニコニコされている展示者の人がいたのですが、その人の周り結構人集まっていたので
ニコニコする!

ゲームパットで操作できない

周りで展示されているほとんどのコンソールゲー展示者、ゲームパット対応しているのに対し、
自分のゲームはキーボード対応しかしておりませんでした。
展示会の状況とかにも寄るかと思いますが、
プレイ時のインターフェースは他開発者に似せた方が遊びやすいのかなと思いました。
ゲームパット対応する!

アドバイスを聞く姿勢

2回目参加時に、プレイヤーの目の前でメモ書きはじめたところ、
プレイヤーの方がどんどん意見を言ってくれたことがありました。
話聞いてますよ、大事にしますよっていうのが伝わると思ったので
プレイヤーの目の前でメモを取る!

【Unity】LineRendererを使ってお絵かきソフトにある機能を実装する その2

概要

LineRendererを用いての対称定規や円、螺旋の描き方について
実装方法を記載します。

綺麗な円の描き方等がなかなか見つからなかったのですが、
誰かに需要あると信じてまとめます。

実装したもの

f:id:coffee_ryo:20181014005607g:plain

サンプルとして以下を作りました。

  • フリーハンド
  • 対称定規
  • 螺旋
  • 正方形っぽい形

ドラッグで線の描画、右クリックでUndoができます。

処理の流れ

マウスの位置情報と線のタイプをインプットとします。
線のタイプによって、線の数の決定や座標変換を行い、
最終的にできた座標をLineRendererに渡して描画します。
ここでの線のタイプは、円とかフリーハンドか対称定規か、などを指します。

f:id:coffee_ryo:20181014011728p:plain

実装サンプルについて

Materialの作成

描画の色を設定する用途で、Projectタブ上にMaterialを作成します。

f:id:coffee_ryo:20181014012258p:plain

Prefabの作成

空のオブジェクトを作成し、LineRendererをアタッチ。
LineRendererのMaterialsに先ほど作成したMaterialを設定したら
オブジェクトをPrefab化します。

f:id:coffee_ryo:20181014012334p:plain

お絵描き用のオブジェクトを作成する

空のオブジェクトを作成し、後述するIllustDrawerコンポーネントをアタッチ。
インスペクター上で先ほど作成したPrefabを設定をしたら、準備完了です。

f:id:coffee_ryo:20181014012932p:plain

ソースコード

IllustDrawer.cs

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace CoffeeR.Paint{
    public class IllustDrawer : MonoBehaviour {

        [Header("LineRendererがついたPrefab指定")]
        [SerializeField]
        LineRenderer lineRendererPrefab;

        [Header("線の太さを指定")]
        [SerializeField]
        [Range(0.05f, 1.0f)]
        float lineWidth;

        [Header("線のタイプを指定")]
        [SerializeField]
        EnumLineType lineType;

        [Header("円等を描く時の中心点を設定")]
        [SerializeField]
        Vector3 centerPosition;

        /// <summary>
        /// 描画コンポーネント群
        /// </summary>
        List<List<LineRenderer>> lineRendererMultipleList;

        void Start () {
            lineRendererMultipleList = new List<List<LineRenderer>>();
        }

        void Update () {
            if(Input.GetMouseButtonDown(1)){
                UndoLine();
            }
            if(Input.GetMouseButtonDown(0)){
                CreateLineRendererObject(lineType);
            }
            if(Input.GetMouseButton(0)){
                var mousePosition = GetPostionOfInput();
                DrawingLine(mousePosition);
            }
        }

        /// <summary>
        /// レンダラー付きのオブジェクトを作成する
        /// </summary>
        void CreateLineRendererObject (EnumLineType type) {
            lineRendererMultipleList.Add(new List<LineRenderer>());

            // 線を作成する個数を設定
            int lineCount = 0;
            switch(type){
                case EnumLineType.FreeHand:
                case EnumLineType.Circle:
                case EnumLineType.Spiral:
                    lineCount = 1;
                    break;
                case EnumLineType.LikeSquare:
                    lineCount = 4;
                    break;
                case EnumLineType.Symmetry:
                    lineCount = 12;
                    break;
                default:
                    Debug.LogError(type.ToString() + "での線の本数が指定されていません。");
                    break;
            }

            for(int i = 0; i < lineCount; i++){
                // 描画コンポーネントがついたオブジェクトを作成
                LineRenderer line = Instantiate(lineRendererPrefab);

                // 太さを設定する
                line.startWidth = lineWidth;
                line.endWidth   = lineWidth;

                // 子オブジェクトに設定
                line.transform.parent = this.transform;

                // 作成した描画コンポーネントをこのクラスにキャッシュする
                lineRendererMultipleList.Last().Add(line);
            }
        }

        void DrawingLine (Vector3 mousePosition){

            int positionIndex = 0;

            switch(lineType){
                case EnumLineType.FreeHand:
                    lineRendererMultipleList.Last().Last().positionCount++;
                    positionIndex = lineRendererMultipleList.Last().Last().positionCount;
                    lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, mousePosition);
                    break;
                
                case EnumLineType.Circle:
                    lineRendererMultipleList.Last().Last().positionCount++;
                    positionIndex = lineRendererMultipleList.Last().Last().positionCount;

                    if(positionIndex == 1){
                        lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, centerPosition + mousePosition);
                    }
                    else{
                        var firstPosition = lineRendererMultipleList.Last().Last().GetPosition(0);
                        lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler (0f, 0f, positionIndex * 5) * (firstPosition - centerPosition));
                    }
                    break;
                
                case EnumLineType.Spiral:
                    lineRendererMultipleList.Last().Last().positionCount++;
                    positionIndex = lineRendererMultipleList.Last().Last().positionCount;

                    if(positionIndex == 1){
                        lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, centerPosition + mousePosition);
                    }
                    else{
                        var firstPosition = lineRendererMultipleList.Last().Last().GetPosition(0);
                        lineRendererMultipleList.Last().Last().SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler (0f, 0f, positionIndex * 5) * (firstPosition  - centerPosition) / (1 + positionIndex * 0.025f));
                    }
                    break;
                
                case EnumLineType.Symmetry:
                    var degreeIndex = 0;
                    foreach(var line in lineRendererMultipleList.Last()){
                        line.positionCount++;
                        positionIndex = line.positionCount;
                        line.SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler (0f,  0f, degreeIndex * 360 / 12) * (mousePosition - centerPosition));
                        degreeIndex++;
                    }
                    break;
                
                case EnumLineType.LikeSquare:
                    for(int i = 0; i < 4; i++){
                        lineRendererMultipleList.Last()[i].positionCount++;
                    }
                    positionIndex = lineRendererMultipleList.Last().Last().positionCount;


                    if(positionIndex == 1){
                        for(int i = 0; i < 4; i++){
                            lineRendererMultipleList.Last()[i].SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler(0f, 0f,   (i - 1) * 90) * mousePosition);
                        }
                    }else{
                        var vertexPos = lineRendererMultipleList.Last()[0].GetPosition(0) - centerPosition;
                        lineRendererMultipleList.Last()[0].SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler(0f, 0f,   0f)  * vertexPos + positionIndex * Vector3.down  * 0.1f);
                        lineRendererMultipleList.Last()[1].SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler(0f, 0f,  90f)  * vertexPos + positionIndex * Vector3.right * 0.1f);
                        lineRendererMultipleList.Last()[2].SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler(0f, 0f,  180f) * vertexPos + positionIndex * Vector3.up    * 0.1f);
                        lineRendererMultipleList.Last()[3].SetPosition(positionIndex - 1, centerPosition + Quaternion.Euler(0f, 0f,  270f) * vertexPos + positionIndex * Vector3.left  * 0.1f);
                    }
                    break;

                default:
                    Debug.LogError(lineType.ToString() + "での線の描き方が指定されていません。");
                    break;
            }
        }

        /// <summary>
        /// 一つ前の状態に戻す
        /// </summary>
        void UndoLine(){
            try{
                var lastLineRendererList = lineRendererMultipleList.Last();
                foreach(var line in lastLineRendererList){
                    Destroy(line.gameObject);
                }
                lineRendererMultipleList.Remove(lastLineRendererList);
            }catch(System.InvalidOperationException){
                Debug.Log("線がないためUndoされませんでした");
            }
        }

        /// <summary>
        /// 入力位置を返却する
        /// </summary>
        /// <returns></returns>
        Vector2 GetPostionOfInput(){
            Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane + 1.0f);
            return Camera.main.ScreenToWorldPoint(position);
        }
    }

    /// <summary>
    /// 線のタイプ
    /// </summary>
    internal enum EnumLineType{

        /// <summary>
        /// 自由線
        /// </summary>
        FreeHand,
        /// <summary>
        /// 対称定規
        /// </summary>
        Symmetry,
        /// <summary>
        /// 円
        /// </summary>
        Circle,
        /// <summary>
        /// 螺旋
        /// </summary>
        Spiral,
        /// <summary>
        /// 正方形状
        /// </summary>
        LikeSquare
    }
}

それぞれの線について

Quaternionを使って座標変換をするのがポイントになります。

マウス入力してからの1フレーム目の、マウスの位置と中心点との間を半径とします。
フレームを進めるごとに、「1フレーム目のマウス位置」を中心点から回転させた位置を描画していきます。

f:id:coffee_ryo:20181014032908p:plain

螺旋

円と類似していますが、フレームを進めるごとに半径を小さくしていきます。

対称定規

複数のLineRendererを動かします。
マウス入力位置を、中心点から一定数回転させた位置をLineRendererに設定します。
ソースコードにてList<List>という構造を使っているのは、これのため。

正方形状

複数のLineRendererを動かします。
マウス入力位置を、中心点から90度ずつ回転させた位置をLineRendererに設定します。