1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】反射するレーザーを作ろう!#2.5「リファクタリング」

Last updated at Posted at 2024-04-15

はじめに

今回は前回作った動かせる反射レーザーのコードの改良。

動作は変えずにコードを改良していく、つまりリファクタリングをする(覚えた単語使いたいだけ)。

動作は変わらないので「Lazer.cs」だけ、この記事の改良後のコードに置き換えて次の記事に進んでも問題ない。

前回の記事はこちら

前回のコードの問題点

前回のコードを見つつ、自分が感じた問題点をあげていく。
改良を加えるのは「Lazer.cs」なので早速コードを見てみる。

前回のコード

Lazer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Lazer : MonoBehaviour
{
  [SerializeField] private GameObject lazerPrefab;
  const float MAX_DISTANCE = 23.0f;  //箱の対角線の長さ
  const int REFLECT_NUM = 5;  //反射回数
  private GameObject preWall;
  public LineRenderer[] lazers;
  public Vector3 nowOrigin;
  public Vector2 nowDirection;
  
  /**
   * レーザー生成関数
   * @param {Vector3} origin - レーザーの原点
   * @param {Vector3} direction - レーザーの方向
   * @param {int} n - 何本目か(0はじまり)
  **/
  public void creat(Vector3 origin, Vector2 direction, int n){
    if(n == 0){
      nowOrigin = origin;
      nowDirection = direction;
      lazers = new LineRenderer[REFLECT_NUM + 1];
      preWall = null;
    };
    if(n < REFLECT_NUM + 1){
      GameObject lazerChild = Instantiate(lazerPrefab, this.transform);
      lazerChild.name = "lazer_" + n;
      LineRenderer line = lazerChild.GetComponent<LineRenderer>();
      lazers[n] = line;
      RaycastHit2D[] hits = Physics2D.RaycastAll(origin, direction, MAX_DISTANCE);

      foreach(RaycastHit2D hit in hits){
        if (hit.collider.gameObject != preWall)
        {
          Vector3 endPos = hit.point;
          Vector2 reflectDirection = Vector2.Reflect(direction, hit.normal);
          
          line.SetPosition(0, origin);
          line.SetPosition(1, endPos);
          preWall = hit.collider.gameObject;

          creat(endPos, reflectDirection, ++n);
          
          break;
        }
      }    
    } 
  }

  /**
   * レーザー移動時に実行する関数
   * @param {Vector3} origin - レーザーの原点
   * @param {Vector3} direction - レーザーの方向
  **/
  public void move(Vector3 origin, Vector2 direction){
    preWall = null;
    nowOrigin = origin;
    nowDirection = direction;
    Vector3 startPos = origin;
    Vector2 nextDirection = direction;
    foreach(LineRenderer line in lazers){
      RaycastHit2D[] hits = Physics2D.RaycastAll(startPos, nextDirection, MAX_DISTANCE);

      foreach(RaycastHit2D hit in hits){
        if (hit.collider.gameObject != preWall)
        {
          Vector3 endPos = hit.point;
          Vector2 reflectDirection = Vector2.Reflect(nextDirection, hit.normal);
          line.SetPosition(0, startPos);
          line.SetPosition(1, endPos);
          preWall = hit.collider.gameObject;
          startPos = endPos;
          nextDirection = reflectDirection;
          break;
        }
      }  
    }
  }

  //getter
  public Vector3 getOrigin(){
    return nowOrigin;
  }
  public Vector2 getDirection(){
    return nowDirection;
  }
}

前回の記事の通り、このコードでも問題なく動作するのだが、少しコードが長い。
そこで、コードをよく見ると、creat()内とmove()内に似ている部分がある。

creat()内
foreach(RaycastHit2D hit in hits){
  if (hit.collider.gameObject != preWall)
  {
    Vector3 endPos = hit.point;
    Vector2 reflectDirection = Vector2.Reflect(direction, hit.normal);
    
    line.SetPosition(0, origin);
    line.SetPosition(1, endPos);
    preWall = hit.collider.gameObject;
  
    creat(endPos, reflectDirection, ++n);
    
    break;
  }
}
move()
foreach(RaycastHit2D hit in hits){
  if (hit.collider.gameObject != preWall)
  {
    Vector3 endPos = hit.point;
    Vector2 reflectDirection = Vector2.Reflect(nextDirection, hit.normal);
    
    line.SetPosition(0, startPos);
    line.SetPosition(1, endPos);
    preWall = hit.collider.gameObject;
    
    startPos = endPos;
    nextDirection = reflectDirection;
    break;
  }
}  

foreach()内が前半部分は完全に一致している。

しかし、一致するのは当然で、この部分はレーザーの描画と反射の計算という同様の処理をしている。同じ処理を二つ書くことで、コードが長くなるだけでなく、コードの修正や機能の追加の際、二つとも書き換えないといけなくなることも問題である。

そのため、同じ処理はできるだけ関数にしていきたい。

改良後のコード

Lazer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


public class Lazer : MonoBehaviour
{
  [SerializeField] private GameObject lazerPrefab;
  const float MAX_DISTANCE = 23.0f;  //箱の対角線の長さ
  const int REFLECT_NUM = 5;
  private GameObject preWall;
  public LineRenderer[] lazers;
  public Vector3 nowOrigin;
  public Vector2 nowDirection;
 
  /**
   * レーザー生成関数
   * @param {Vector3} origin - レーザーの原点
   * @param {Vector3} direction - レーザーの方向
   * @param {int} n - 何本目か(0はじまり)
  **/
  public void creat(Vector3 origin, Vector2 direction, int n){
    if(n == 0){
      nowOrigin = origin;
      nowDirection = direction;
      lazers = new LineRenderer[REFLECT_NUM + 1];
      preWall = null;
    };
    if(n < REFLECT_NUM + 1){
      GameObject lazerChild = Instantiate(lazerPrefab, this.transform);
      lazerChild.name = "lazer_" + n;
      LineRenderer line = lazerChild.GetComponent<LineRenderer>();
      lazers[n] = line;
      RaycastHit2D[] hits = Physics2D.RaycastAll(origin, direction, MAX_DISTANCE);
      var reflect = culcReflect(origin, direction, line);
      creat(reflect.origin, reflect.direction, ++n);  
    }
  }


  /**
   * レーザー移動時に実行する関数
   * @param {Vector3} origin - レーザーの原点
   * @param {Vector3} direction - レーザーの方向
  **/
  public void move(Vector3 origin, Vector2 direction){
    preWall = null;
    nowOrigin = origin;
    nowDirection = direction;
    Vector3 startPos = origin;
    Vector2 nextDirection = direction;
    foreach(LineRenderer line in lazers){
      var reflect = culcReflect(startPos, nextDirection, line);
      startPos = reflect.origin;
      nextDirection = reflect.direction;
    }
  }


  /**
   * レーザーを描画し、反射角を計算し、原点と反射方向を返す
   * @return {Vector3} origin - 反射後のレーザーの原点
   * @return {Vector3} direction - 反射後のレーザーの方向
   * @param startPos {Vector3} - 反射前のレーザーの原点
   * @param nextDirection {Vector2} - 反射前のレーザーの方向
   * @param line {LineRenderer} - 描画するレーザーのLineRenderer
  **/
  private (Vector3 origin, Vector2 direction) culcReflect(Vector3 startPos, Vector2 nextDirection ,LineRenderer line){
    RaycastHit2D[] hits = Physics2D.RaycastAll(startPos, nextDirection, MAX_DISTANCE);
    Vector3 endPos = new Vector3(0,0,0);
    Vector2 reflectDirection = new Vector2(0,0);
    foreach(RaycastHit2D hit in hits){
      GameObject hitObj = hit.collider.gameObject;
      if (hitObj != preWall){
        endPos = hit.point;
        reflectDirection = Vector2.Reflect(nextDirection, hit.normal);

        line.SetPosition(0, startPos);
        line.SetPosition(1, endPos);
        preWall = hitObj;

        break;
      }
    }
    return (endPos, reflectDirection);
  }


  //getter
  public Vector3 getOrigin(){
    return nowOrigin;
  }
  public Vector2 getDirection(){
    return nowDirection;
  }
}

レーザーの描画と反射の計算をする処理を関数culcReflect()にまとめた。

解説

もともとのコード(下記はcreat内のもの)はforeach内で計算とレーザーの描画をし、次の繰り返しに引き継ぐ処理もしている。creat()の処理とmove()の違いは繰り返しの処理を再帰的にしているかfor文でしているかである。

元のコード
foreach(RaycastHit2D hit in hits){
  if (hit.collider.gameObject != preWall)
  {
    Vector3 endPos = hit.point;
    Vector2 reflectDirection = Vector2.Reflect(direction, hit.normal);
    
    line.SetPosition(0, origin);
    line.SetPosition(1, endPos);
    preWall = hit.collider.gameObject;
  
    creat(endPos, reflectDirection, ++n);
    
    break;
  }
}

さらに、次の繰り返しに引き継ぐ処理はforeachの最後に実行されるので、外に出すことができる。そのためforeach内のコードをcreat()とmove()で全く同じにできる。これで関数化の準備が整った。

creat()内
foreach(RaycastHit2D hit in hits){
  if (hit.collider.gameObject != preWall)
  {
    Vector3 endPos = hit.point;
    Vector2 reflectDirection = Vector2.Reflect(direction, hit.normal);
    
    line.SetPosition(0, origin);
    line.SetPosition(1, endPos);
    preWall = hit.collider.gameObject;
    
    break;
  }
}
//再帰をforeachの外に出せる
creat(endPos, reflectDirection, ++n);
move()内
foreach(RaycastHit2D hit in hits){
  if (hit.collider.gameObject != preWall)
  {
    Vector3 endPos = hit.point;
    Vector2 reflectDirection = Vector2.Reflect(nextDirection, hit.normal);
    
    line.SetPosition(0, startPos);
    line.SetPosition(1, endPos);
    preWall = hit.collider.gameObject;

    break;
  }
}
//変数の書き換えをforeachの外に出せる
startPos = endPos;
nextDirection = reflectDirection;

引数、戻り値

関数化の前に受け取るべき引数と返すべき戻り値を整理する。

引数

foreach内で使われているが、foreachの外で定義されている変数を引数として受け取る必要がある。該当する変数は以下の通り。

*変数名の()はcreat()内とmove()内で名前が違うもの(役割は同じ)

変数名 役割
hits RaycastHit2D[] 反射方向への衝突を検知するRaycastHit2Dの配列
direction(nextDirection) Vector2 描画するレーザーの方向ベクトル
origin(StartPos) Vector3 描画するレーザーの原点座標
line LineRenderer 描画するレーザーのLineRenderer

foreachはhits(RaycastHit2Dの配列)からRaycastHit2Dを受け取り、繰り返している。
hitsを変数として受け取ってもよいが、hitsの定義はforeachの直前にされており、creat()内とmove()内で内容が変わらない。そのため新しく作る関数内で定義すればよい。

hitsの定義
RaycastHit2D[] hits = Physics2D.RaycastAll(startPos, nextDirection, MAX_DISTANCE);

さらにhitsの定義に必要な変数は、元々新たに作る変数に必要なものなので、hitsの定義を関数内に入れることで引数を一つ減らすことができる。

戻り値

戻り値として必要なのは、次の繰り返しに引き渡す変数で、以下の二つ。

変数名 役割
origin Vector3 次のレーザーの原点
direction Vector2 次のレーザーの方向

ここで一つの疑問が生まれる。

戻り値が二つってどうやるの?

戻り値が二つの関数を作る

次の記事を参考に戻り値が二つの関数を作った。

記事内で紹介されている方法の内、今回はTuple型を採用。
変数名で変数を呼び出せるように関数を定義する。(関数名はculcReflect)

private (Vector3 origin, Vector2 direction) culcReflect(Vector3 startPos, Vector2 nextDirection ,LineRenderer line){

}

このように関数を定義することで、「関数名().変数名」で戻り値を得ることができるようになる。

関数の中身を作る

中身を作ると言ってもほとんどコピペでOK。
戻り値として設定する変数の定義を追加している点に注意。

culcReflect()
private (Vector3 origin, Vector2 direction) culcReflect(Vector3 startPos, Vector2 nextDirection ,LineRenderer line){
  RaycastHit2D[] hits = Physics2D.RaycastAll(startPos, nextDirection, MAX_DISTANCE);
  Vector3 endPos = new Vector3(0,0,0);
  Vector2 reflectDirection = new Vector2(0,0);
  foreach(RaycastHit2D hit in hits){
    GameObject hitObj = hit.collider.gameObject;
    if (hitObj != preWall){
      endPos = hit.point;
      reflectDirection = Vector2.Reflect(nextDirection, hit.normal);

      line.SetPosition(0, startPos);
      line.SetPosition(1, endPos);
      preWall = hitObj;

      break;
    }
  }
  return (endPos, reflectDirection);
}

creat()とmove()を書き換える

それぞれの関数を新たに作ったculcReflect()を使用するように書き換えます。

creat()
public void creat(Vector3 origin, Vector2 direction, int n){
  if(n == 0){
    nowOrigin = origin;
    nowDirection = direction;
    lazers = new LineRenderer[REFLECT_NUM + 1];
    preWall = null;
  };
  if(n < REFLECT_NUM + 1){
    GameObject lazerChild = Instantiate(lazerPrefab, this.transform);
    lazerChild.name = "lazer_" + n;
    LineRenderer line = lazerChild.GetComponent<LineRenderer>();
    lazers[n] = line;
    //以下が変更された行
    var reflect = culcReflect(origin, direction, line);
    creat(reflect.origin, reflect.direction, ++n);  
  }
}
move()
public void move(Vector3 origin, Vector2 direction){
  preWall = null;
  nowOrigin = origin;
  nowDirection = direction;
  Vector3 startPos = origin;
  Vector2 nextDirection = direction;
  foreach(LineRenderer line in lazers){
    //以下が変更された行
    var reflect = culcReflect(startPos, nextDirection, line);
    startPos = reflect.origin;
    nextDirection = reflect.direction;
  }
}

何度もculcReflectを呼び出さないように、新たに定義した変数reflectを介して戻り値を入手しています。

おわりに

今回はコードの改良をした。最初にコードを書く時点でいいコードを書くのは難しいので適宜、改良していきたい。
自分も正解はわからないので、なにかあったらコメントください。

次回はレーザーの色が変わるギミックの追加をする。

続き

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?