2
1

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「レーザーを動かす」

Last updated at Posted at 2024-04-08

はじめに

前に作った反射するレーザーの続き。

前の記事はこちら
https://qiita.com/TanakaNeko/items/fbd57b3c24b7fd4adb0d

今回は、キー操作で下のgifのようにレーザーが回転するようにした。

lazer_move.gif

概要

十字キーの左右でレーザーを回転できる。反射の計算と描画は引き続き、RaycastHit2DとLineRendererを使っている。

作り方

前回作ったスクリプトを以下のものに置き換える。

LazerStarter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LazerStarter : MonoBehaviour
{
  [SerializeField] private GameObject lazerMother;
  private Vector2 INITIAL_DIRECTION = new Vector2(0.3f,1); //最初の方向
  const float ROTATE_SPEED = 0.02f;  //回転速度
  private GameObject newLazer;
  private Lazer lazerCS;

  public void Start(){
    newLazer = Instantiate(lazerMother, this.gameObject.transform.position, Quaternion.identity);
    lazerCS = newLazer.GetComponent<Lazer>();
    lazerCS.creat(newLazer.transform.position, INITIAL_DIRECTION, 0);
  }

  public void Update(){
    // 左回転
    if (Input.GetKey (KeyCode.LeftArrow)) {
      lazerCS.move(lazerCS.getOrigin(), Quaternion.Euler(0f, 0f, ROTATE_SPEED) * lazerCS.getDirection());
    }
    // 右回転
    if (Input.GetKey (KeyCode.RightArrow)) {
      lazerCS.move(lazerCS.getOrigin(), Quaternion.Euler(0f, 0f, -ROTATE_SPEED) * lazerCS.getDirection());
    }
  }
}

レーザー発射のメソッドをStart()に変更し、キー操作を受け付けるためにUpdate()メソッドを追加している。


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;
  }
}

レーザー移動時に実行する関数と現在のレーザーの情報を返すゲッターを追加している。

レーザーの発射方法を変更しているので注意

前回、ボタンを押すと発射するようにしていたが、今回はStart()メソッドに変更。そのため、ボタンではなく任意のGameObjectにアタッチすることで、そのオブジェクトの生成時にレーザーが発射される。
(オブジェクトの座標からレーザーが発射されるのはそのまま)

スクリプトを置き換えたら、十字キーを押したときにレーザーが動くようになる。

解説

追加した変数

LazerStarter.cs

回転速度の変数と、Update()内でLazer.csの関数を呼び出すための変数を追加。
回転速度は任意の値に変更してOK。

変数名 役割
ROTATE_SPEED float キー入力をしたときのレーザーの回転速度
newLazer GameObject 新たに生成されたLazerMother
lazerCS Lazer 生成したLazerMotherにアタッチされているLazer.cs

Lazer.cs

レーザーの状態が動的に変化するので、現在のレーザーの情報を入手するための変数を追加。

変数名 役割
lazers LineRenderer[] 反射によって生成された各レーザーの持つLineRendererを順に格納する配列
nowOrigin Vector3 レーザーの現在の始点
nowDirection Vector2 レーザーの現在の発射方向

変数の追加に伴い、いくつかコードを変更した。

LazerStater.cs Start()内
newLazer = Instantiate(lazerMother, this.gameObject.transform.position, Quaternion.identity);
lazerCS = newLazer.GetComponent<Lazer>();
lazerCS.creat(newLazer.transform.position, INITIAL_DIRECTION, 0);

newLazerをローカル変数からグローバル変数に変更し、LazerCSに値をいれる行を追加している。この二つをグローバル変数にすることでUpdate()内でも、これらの値を使用できる。


Lazer.cs 23~28行目
if(n == 0){
  nowOrigin = origin;
  nowDirection = direction;
  lazers = new LineRenderer[REFLECT_NUM + 1];
  preWall = null;
};

preWallを初期化していたif文内に、新しい変数の初期化の処理を追加。


Lazer.cs 33行目
lazers[n] = line;

LineRendererを配列に格納する処理を追加。

追加した関数

Update()

LazerStarter.cs Update()
public void Update(){
    // 左回転
    if (Input.GetKey (KeyCode.LeftArrow)) {
      lazerCS.move(lazerCS.getOrigin(), Quaternion.Euler(0f, 0f, ROTATE_SPEED) * lazerCS.getDirection());
    }
    // 右回転
    if (Input.GetKey (KeyCode.RightArrow)) {
      lazerCS.move(lazerCS.getOrigin(), Quaternion.Euler(0f, 0f, -ROTATE_SPEED) * lazerCS.getDirection());
    }
}

キー入力を受け付け、右矢印、左矢印が押されたときに、Lazer.csの関数move()を実行する。
move()の引数には現在のレーザーの原点の座標のVector3と、現在のレーザーの発射方向からROTATE_SPEEDだけ回転させたVector2を渡している。

回転の計算はQuaternion * Vector2で計算している。(よくはわかってはいないが、これで、Quaternionで指定した角度分回転させたベクトルを得ることができる)Vector2 * Quaternionの順では計算できないので注意が必要。

getter

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

LazerStarter.csからレーザーの情報を得るために使用する。

move(Vector3, Vector2)

Lazer.cs move(Vector3, Vector)
  /**
   * レーザー移動時に実行する関数
   * @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;
        }
      }  
    }
  }

最初のレーザーの始点と方向を引数で受け取り、その後のレーザーの始点、終点もforeach内で計算し、配列lazersに格納されたLineRandererの値を更新する。

反射方向や座標の計算方法は、前回の記事で作ったcreat()メソッド内と同じ。

おわりに

今回はレーザーを動的に動かせるよう変更した。前回作った反射の計算をそのまま使えたので、思っていたより簡単に作ることができた。今回は発射する方向のみ動かせるようにしたが、発射する位置を動かせるようにしても面白そう。

続き

参考

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?