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;
}
}
}
使い方の手順
ここでは、スナイパーライフルの銃に照準レーザーを付ける例で説明します。
-
銃モデルに「銃口用の子オブジェクト」を作る
- ヒエラルキーで銃オブジェクトを選択し、右クリック → Create Empty で子オブジェクトを作成。
- 名前を
Muzzleなどに変更。 - 銃の先端位置に移動させ、前方向(Z軸)が弾が飛ぶ向きになるように回転を合わせておきます。
-
銃オブジェクトに SniperLine コンポーネントを追加
- 銃オブジェクトを選択し、「Add Component」から
SniperLineを追加。 Muzzle Transformに、さきほど作成したMuzzleオブジェクトをドラッグ&ドロップ。- 必要に応じて
Max Distance(例: 100)、Laser Width(例: 0.02)、Blink Interval(例: 0.15)などを調整。 - シーン開始時から照準線を出したくない場合は、
Start Enabledをオフにしておきます。
- 銃オブジェクトを選択し、「Add Component」から
-
簡単な射撃スクリプトから 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と役割を分けておくと、後からどちらかだけを差し替えやすくなります。
-
敵やギミックにも使い回す
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 に任せておく」という役割分担を意識して、コンポーネント指向でスクリプトを組み立てていきましょう。
