明日の自分へ

主にUnityのゲーム開発に関することを呟いていく予定

【Unity】移動するオブジェクトの軌跡で地面を作る【2D】

Edge Collider 2Dというものがありますね。
線の形状をColliderにするもので、自由な形状に出来るのが特徴です。
昔、「Gravity Master」というフラッシュゲームで、画面をなぞって地面を生成するというギミックが印象に残っています。(BGMも良かった)

 

今回、あるオブジェクトの軌跡を基に地面を生成しようと思い、EdgeCollider2Dを使いました。
この記事はその備忘録です。

動作イメージ

f:id:e684-creaim:20201008231100g:plain 

構成

次の二つの役割から成り立ちます。

  1. オブジェクトの移動軌跡(座標の集合)を保存する→(VertexRegister.cs)
  2. 移動軌跡からEdgeCollider2Dの頂点を生成する→(GroundGenerator.cs)

 
[VertexRegister.cs]

    void Start()
    {
        listHead = 0;
        isFulled = false;

        AddVertex(this.gameObject.transform.position);
    }

    // Update is called once per frame
    void Update()
    {
        // Edge頂点を更新
        UpdateVertex();

        UpdateGround();
    }


    /// <summary>
    /// 地面生成用頂点のリストを更新する
    /// 前回の頂点から一定距離離れている場合に頂点を追加する
    /// </summary>
    private void UpdateVertex()
    {
        if (Vector2.Distance(this.gameObject.transform.position, pointList[listHead - 1]) > .1f)
        {
            AddVertex(this.gameObject.transform.position);
        }
    }

    private void AddVertex(Vector2 vertex)
    {
        pointList[listHead++] = vertex;
        if (listHead >= MAX_POINT_NUM)
        {
            listHead = 0;
            isFulled = true;
        }
    }

    /// <summary>
    /// 地面を生成する
    /// 頂点リストから地面を生成する
    /// 追加された順番が古い順に頂点を登録する
    /// </summary>
    private void UpdateGround()
    {
        if (!isFulled)
        {
            // リングバッファが一巡していない場合は、登録されている分だけを頂点とする
            Vector2[] work = new Vector2[listHead];
            Array.Copy(pointList, work, listHead);
            groundGenerator.UpdateGround(work);
        }
        else
        {
            // リングバッファが一巡している場合は、もっとも古い頂点からコピーする
            Vector2[] work = new Vector2[MAX_POINT_NUM];
            Array.Copy(pointList, listHead, work, 0, MAX_POINT_NUM - listHead);
            Array.Copy(pointList, 0, work, MAX_POINT_NUM - listHead, listHead);
            groundGenerator.UpdateGround(work);
        }
    }

[GroundGenerator.cs]

    /// <summary>
    /// 地面を生成する
    /// 頂点リストから地面を生成する
    /// 追加された順番が古い順に頂点を登録する
    /// </summary>
    public void UpdateGround(Vector2[] vertexes)
    {
        _collider.points = vertexes;
        Vector3[] vec3 = new Vector3[vertexes.Length];
        for (int i = 0; i < vertexes.Length; i++)
        {
            vec3[i] = vertexes[i];
        }
        _renderer.positionCount = vec3.Length;
        _renderer.SetPositions(vec3);
    }

落とし穴

当初、役割を分けずに一つのGameObjectで実装していました。
しかし、動かしてみると座標が合いません。
これは、オブジェクトの軌跡はワールド座標で取得していたのに対し、地面生成でEdgeCollider2Dに座標を渡すとローカル座標として扱われるから、という結論に至りました。
そのため、役割を2つに分け、地面生成を基準点から動かないGameObjectとしています。

(参考)
①EdgeCollider2DをもつGameObjectの座標は(2,1)
②EdgeCollider2Dのコライダー頂点は(-0.5, 0)と(0.5, 0)であり、自身の左右に伸びる長さ1のコライダーになっている
f:id:e684-creaim:20201008232107p:plain