Unityを触り始めると、つい「とりあえず全部Updateに書く」実装になりがちですよね。プレイヤーの移動、カメラ操作、射撃処理、エフェクト表示、UI更新……すべてが1つのスクリプトに詰め込まれていくと、だんだん「どこを触ればいいのか分からない巨大スクリプト(Godクラス)」になってしまいます。

たとえば「スナイパーライフルの照準レーザーを点滅させたい」というだけの要望でも、巨大なPlayerControllerのUpdateに

  • 入力チェック
  • 弾数管理
  • アニメーション制御
  • そしてレーザーの表示/非表示ロジック

…と、どんどん処理が積み上がっていきます。これでは、後から「レーザーの仕様だけ変えたい」と思っても、他の処理を巻き込んでバグを生みやすくなります。

そこで今回は、「照準線(レーザーサイト)だけ」を担当する小さなコンポーネントとして、「SniperLine」を用意してみましょう。
銃口から伸びる赤いレーザーサイトを、発射前だけ点滅表示することに特化したコンポーネントです。

【Unity】スナイパー感アップの赤い照準線!「SniperLine」コンポーネント

このコンポーネントは、LineRendererを使って銃口から伸びる赤いレーザーを描画し、一定間隔で点滅させる役割だけを持たせます。
「いつレーザーを出すか / 消すか」は、別の射撃コンポーネントからメソッドを呼ぶだけで制御できるようにして、責務を分離します。

フルコード(SniperLine.cs)


using UnityEngine;

/// <summary>
/// 銃口から伸びる赤いレーザーサイト(照準線)を制御するコンポーネント。
/// ・LineRenderer を使って線を描画
/// ・指定した間隔で点滅
/// ・外部から「有効/無効」を切り替え可能
/// </summary>
[RequireComponent(typeof(LineRenderer))]
public class SniperLine : MonoBehaviour
{
    [Header("参照設定")]
    [SerializeField]
    private Transform muzzleTransform;
    // レーザーの起点となる銃口(muzzle)のTransform
    // ここを銃の先端の子オブジェクトなどに設定する

    [Header("レーザー設定")]
    [SerializeField]
    private float maxDistance = 50f;
    // レーザーが届く最大距離(レイキャストの長さ)

    [SerializeField]
    private LayerMask hitLayers = ~0;
    // レーザーが当たるレイヤー(デフォルト:すべて)

    [SerializeField]
    private Color laserColor = Color.red;
    // レーザーの色(LineRenderer に反映)

    [SerializeField]
    private float laserWidth = 0.03f;
    // レーザーの太さ

    [Header("点滅設定")]
    [SerializeField]
    private bool startEnabled = false;
    // シーン開始時にレーザーを有効にするかどうか

    [SerializeField]
    private float blinkInterval = 0.2f;
    // 点滅の間隔(秒)

    [SerializeField]
    private bool useBlink = true;
    // 点滅させるかどうか(false なら常時点灯)

    private LineRenderer lineRenderer;
    private float blinkTimer;
    private bool isLaserVisible = true;
    private bool isActive = false;

    private void Awake()
    {
        // 必須コンポーネントを取得
        lineRenderer = GetComponent<LineRenderer>();

        // LineRenderer の基本設定を初期化
        SetupLineRenderer();
    }

    private void Start()
    {
        // 初期状態を設定
        SetActive(startEnabled);
    }

    private void Update()
    {
        // SniperLine 自体が無効なら、何もしない
        if (!isActive)
        {
            if (lineRenderer.enabled)
            {
                lineRenderer.enabled = false;
            }
            return;
        }

        // レーザーの描画位置を更新
        UpdateLaserPosition();

        // 点滅処理
        if (useBlink)
        {
            UpdateBlink();
        }
        else
        {
            // 点滅を使わない場合は常に表示
            lineRenderer.enabled = true;
        }
    }

    /// <summary>
    /// LineRenderer の初期設定を行う。
    /// </summary>
    private void SetupLineRenderer()
    {
        // ワールド座標で線を描画する
        lineRenderer.useWorldSpace = true;

        // 頂点数は「開始点」と「終了点」の2つ
        lineRenderer.positionCount = 2;

        // 太さを設定
        lineRenderer.startWidth = laserWidth;
        lineRenderer.endWidth = laserWidth;

        // マテリアルが未設定なら、簡易的なマテリアルを自動生成
        if (lineRenderer.sharedMaterial == null)
        {
            // Unlit/Color シェーダーを使ったマテリアルを作成
            var mat = new Material(Shader.Find("Unlit/Color"));
            mat.color = laserColor;
            lineRenderer.sharedMaterial = mat;
        }
        else
        {
            // 既存マテリアルがある場合は色だけ上書き
            lineRenderer.sharedMaterial.color = laserColor;
        }

        // 初期状態では非表示にしておく
        lineRenderer.enabled = false;
    }

    /// <summary>
    /// レーザーの開始位置・終了位置を更新する。
    /// </summary>
    private void UpdateLaserPosition()
    {
        if (muzzleTransform == null)
        {
            Debug.LogWarning("[SniperLine] muzzleTransform が設定されていません。");
            return;
        }

        Vector3 start = muzzleTransform.position;
        Vector3 direction = muzzleTransform.forward;

        // レイキャストでヒットを調べる
        Ray ray = new Ray(start, direction);
        if (Physics.Raycast(ray, out RaycastHit hit, maxDistance, hitLayers, QueryTriggerInteraction.Ignore))
        {
            // 何かに当たった場合は、ヒット位置までレーザーを伸ばす
            lineRenderer.SetPosition(0, start);
            lineRenderer.SetPosition(1, hit.point);
        }
        else
        {
            // 何にも当たらなければ、最大距離まで伸ばす
            Vector3 end = start + direction * maxDistance;
            lineRenderer.SetPosition(0, start);
            lineRenderer.SetPosition(1, end);
        }
    }

    /// <summary>
    /// 点滅用のタイマー処理。
    /// </summary>
    private void UpdateBlink()
    {
        blinkTimer += Time.deltaTime;

        if (blinkTimer >= blinkInterval)
        {
            blinkTimer = 0f;
            isLaserVisible = !isLaserVisible;
            lineRenderer.enabled = isLaserVisible;
        }
    }

    /// <summary>
    /// 外部からレーザーの有効/無効を切り替えるためのメソッド。
    /// 例: 射撃前に true、射撃後に false にする。
    /// </summary>
    /// <param name="active">true ならレーザー機能を有効化、false なら無効化</param>
    public void SetActive(bool active)
    {
        isActive = active;

        // 機能が無効なら LineRenderer も消す
        if (!isActive)
        {
            lineRenderer.enabled = false;
            return;
        }

        // 有効化された場合、点滅状態を初期化
        blinkTimer = 0f;
        isLaserVisible = true;
        lineRenderer.enabled = true;

        // すぐに位置を更新しておく
        UpdateLaserPosition();
    }

    /// <summary>
    /// 外部から一時的にレーザーの色を変更したい場合に呼び出す。
    /// 例: チャージ中は黄色、発射直前は赤など。
    /// </summary>
    /// <param name="color">変更後の色</param>
    public void SetColor(Color color)
    {
        laserColor = color;

        if (lineRenderer.sharedMaterial != null)
        {
            lineRenderer.sharedMaterial.color = laserColor;
        }
    }

    /// <summary>
    /// 外部から点滅の有無を切り替える。
    /// 例: スナイプモード中は点滅、それ以外は常時点灯など。
    /// </summary>
    /// <param name="enableBlink">true で点滅、false で常時点灯</param>
    public void SetBlink(bool enableBlink)
    {
        useBlink = enableBlink;

        if (!useBlink)
        {
            // 点滅をやめた瞬間に常時表示へ
            lineRenderer.enabled = isActive;
        }
    }
}

使い方の手順

ここでは、スナイパーライフルの銃に照準レーザーを付ける例で説明します。

  1. 銃モデルに「銃口用の子オブジェクト」を作る
    • ヒエラルキーで銃オブジェクトを選択し、右クリック → Create Empty で子オブジェクトを作成。
    • 名前を Muzzle などに変更。
    • 銃の先端位置に移動させ、前方向(Z軸)が弾が飛ぶ向きになるように回転を合わせておきます。
  2. 銃オブジェクトに SniperLine コンポーネントを追加
    • 銃オブジェクトを選択し、「Add Component」から SniperLine を追加。
    • Muzzle Transform に、さきほど作成した Muzzle オブジェクトをドラッグ&ドロップ。
    • 必要に応じて Max Distance(例: 100)、Laser Width(例: 0.02)、Blink Interval(例: 0.15)などを調整。
    • シーン開始時から照準線を出したくない場合は、Start Enabled をオフにしておきます。
  3. 簡単な射撃スクリプトから SniperLine を制御する
    プレイヤーの射撃入力などに応じて、照準線の表示/非表示を切り替えます。
    例として、右クリックで照準モードON/OFF、左クリックで射撃するシンプルなコントローラを示します。
    
    using UnityEngine;
    
    /// <summary>
    /// 簡易スナイパー銃コントローラの例。
    /// ・右クリックで照準モード ON/OFF
    /// ・照準モード中だけ SniperLine を有効化
    /// ・左クリックで射撃(ここではログ表示のみ)
    /// </summary>
    [RequireComponent(typeof(SniperLine))]
    public class SimpleSniperGunController : MonoBehaviour
    {
        [SerializeField]
        private KeyCode aimKey = KeyCode.Mouse1; // 右クリック
    
        [SerializeField]
        private KeyCode fireKey = KeyCode.Mouse0; // 左クリック
    
        private SniperLine sniperLine;
        private bool isAiming = false;
    
        private void Awake()
        {
            sniperLine = GetComponent<SniperLine>();
        }
    
        private void Update()
        {
            // 右クリックで照準モードの ON/OFF を切り替え
            if (Input.GetKeyDown(aimKey))
            {
                isAiming = !isAiming;
                sniperLine.SetActive(isAiming);
            }
    
            // 照準中に左クリックで射撃
            if (isAiming && Input.GetKeyDown(fireKey))
            {
                Fire();
            }
        }
    
        private void Fire()
        {
            // 実際の弾発射処理は各自のプロジェクト仕様に合わせて実装
            Debug.Log("Sniper Fire!");
    
            // 例: 発射直後に一瞬レーザーを消す
            sniperLine.SetActive(false);
        }
    }
        

    このように、射撃ロジックは SimpleSniperGunControllerレーザー表示は SniperLineと役割を分けておくと、後からどちらかだけを差し替えやすくなります。

  4. 敵やギミックにも使い回す
    SniperLine は「銃口Transformさえあれば動く」ように作ってあるので、たとえば:
    • 敵スナイパーの銃口に付けて、「プレイヤーを狙っているときだけ SetActive(true)」にする。
    • レーザーで位置を示す「動く床」や「レーザートラップ」の軌道表示に使う。

    といった形で、プレイヤー以外のオブジェクトにも簡単に転用できます。

メリットと応用

SniperLine をコンポーネントとして切り出すことで、次のようなメリットがあります。

  • 責務が明確:照準線の描画と点滅だけを担当するので、射撃ロジックや入力処理と混ざらない。
  • プレハブ化がしやすい:銃のプレハブに SniperLine をセットしておけば、どのシーンでも同じ挙動のレーザーを再利用できる。
  • レベルデザインが楽:敵スナイパーのプレハブにも同じコンポーネントを付けておけば、マップ上に配置するだけで「どこから狙われているか」が視覚化される。
  • パラメータ調整が簡単:レーザーの距離、太さ、点滅間隔、色などをインスペクターから調整できるので、ゲームデザイナーとのやり取りもスムーズ。

応用として、チャージショットの予告演出にも使えます。
たとえば「発射直前だけレーザーを太く・明るくする」処理を追加してみましょう。


    /// <summary>
    /// 発射直前の「警告演出」として、レーザーを一時的に太くする例。
    /// duration 秒だけ太くしてから元に戻すコルーチン。
    /// (呼び出し側から StartCoroutine(WarningPulse(0.3f)); のように使う)
    /// </summary>
    private System.Collections.IEnumerator WarningPulse(float duration)
    {
        float originalWidth = lineRenderer.startWidth;
        float targetWidth = originalWidth * 2.0f;

        // 太くする
        lineRenderer.startWidth = targetWidth;
        lineRenderer.endWidth = targetWidth;

        yield return new WaitForSeconds(duration);

        // 元に戻す
        lineRenderer.startWidth = originalWidth;
        lineRenderer.endWidth = originalWidth;
    }

このような小さな改造を SniperLine 側に閉じ込めておけば、射撃ロジックのコードを汚さずに演出だけを進化させることができます。
「照準線は SniperLine に任せておく」という役割分担を意識して、コンポーネント指向でスクリプトを組み立てていきましょう。