Unityを触り始めた頃って、ついなんでもかんでも Update() に書きがちですよね。
プレイヤーの移動、入力、カメラ揺れ、エフェクト、UI更新…全部1つのスクリプトに詰め込むと、最初は動いていても、あとから仕様変更が入ったときに「どこを触ればいいのか分からない」「少し直しただけで別の機能が壊れた」という状態になりがちです。
ポストエフェクト系の処理(モーションブラー、色収差、画面フラッシュなど)も、つい「ダメージ判定の中で直接カメラをいじる」ような実装をしてしまうと、どんどん God クラス化していきます。
そこでこの記事では、「衝撃を受けた瞬間だけ、画面全体の色チャンネル(RGB)をずらして歪ませる」という機能を、
1つの責務に絞ったコンポーネント ChromaticAbberation として切り出してみます。
ゲーム側(プレイヤーや敵のスクリプト)は、「ダメージを受けたら PlayEffect() を呼ぶだけ」にして、
色収差の具体的な実装はすべてこのコンポーネントに任せる、という分離を目指しましょう。
【Unity】ダメージ演出を一発追加!「ChromaticAbberation」コンポーネント
ここでは、Unity6標準の URP(Universal Render Pipeline) を前提に、
フルスクリーンのカスタムポストエフェクトとして色収差を実装します。
- C#コンポーネント(
ChromaticAbberation.cs) - フルスクリーンパス用の C#(
ChromaticAbberationRenderFeature.cs) - シェーダ(
ChromaticAbberation.shader)
の3つで構成されますが、すべてこの記事内のコードだけで動作するようにしてあります。
1. 色収差コンポーネント本体(ChromaticAbberation.cs)
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
/// <summary>
/** 衝撃時に画面全体へ色収差エフェクトをかけるコンポーネント。
* - 自身は「いつ・どれくらいの強さで」色収差をかけるかだけを管理
* - 実際の描画処理は ChromaticAbberationRenderFeature 側のフルスクリーンパスが担当
*/
/// </summary>
[DisallowMultipleComponent]
public class ChromaticAbberation : MonoBehaviour
{
// エフェクトの最大強度(RGBのずれ量)
[SerializeField] private float maxIntensity = 0.015f;
// エフェクトが0になるまでの時間(秒)
[SerializeField] private float duration = 0.3f;
// カーブで減衰の仕方を調整できるようにする(0〜1の時間に対して、0〜1の強度)
[SerializeField] private AnimationCurve fadeCurve =
AnimationCurve.EaseInOut(0f, 1f, 1f, 0f);
// 現在の強度(0〜maxIntensity)。RenderFeature 側から参照される
public float CurrentIntensity { get; private set; } = 0f;
// 内部タイマー
private float _timer = 0f;
private bool _isPlaying = false;
private void Awake()
{
// 初期状態ではエフェクトをオフにしておく
CurrentIntensity = 0f;
_timer = 0f;
_isPlaying = false;
}
private void Update()
{
if (!_isPlaying)
{
return;
}
// 経過時間を進める
_timer += Time.unscaledDeltaTime; // 演出なので TimeScale の影響を受けないようにする
// 0〜1に正規化した経過率
float t = Mathf.Clamp01(_timer / duration);
// カーブに沿って 0〜1 の強度を算出
float normalized = fadeCurve.Evaluate(t);
// 実際の強度に変換
CurrentIntensity = maxIntensity * normalized;
// 終了判定
if (t >= 1f)
{
_isPlaying = false;
CurrentIntensity = 0f;
}
}
/// <summary>
/// 衝撃エフェクトを再生する(外部から呼び出す想定のAPI)。
/// 例: プレイヤーがダメージを受けた瞬間に呼ぶ。
/// </summary>
public void PlayEffect()
{
_timer = 0f;
_isPlaying = true;
}
/// <summary>
/// 強度を直接上書きしたい場合に使えるオプションAPI。
/// 0以上の値を渡すと、その値を maxIntensity として扱う。
/// </summary>
public void PlayEffect(float overrideMaxIntensity)
{
maxIntensity = Mathf.Max(0f, overrideMaxIntensity);
PlayEffect();
}
}
2. URPのフルスクリーンパス(ChromaticAbberationRenderFeature.cs)
URP の Renderer に差し込む「Render Feature」です。
シーン内に存在する ChromaticAbberation コンポーネントを探し、その CurrentIntensity に応じてポストエフェクトをかけます。
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
/// <summary>
/// URP の Renderer に追加する Render Feature。
/// 画面全体に色収差シェーダを適用する。
/// </summary>
public class ChromaticAbberationRenderFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
{
// 使用するシェーダ
public Shader shader;
// 描画のタイミング(基本は AfterRendering でOK)
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRendering;
}
public Settings settings = new Settings();
private ChromaticAbberationPass _pass;
private Material _material;
public override void Create()
{
if (settings.shader == null)
{
Debug.LogError("ChromaticAbberationRenderFeature: シェーダが設定されていません。");
return;
}
_material = CoreUtils.CreateEngineMaterial(settings.shader);
_pass = new ChromaticAbberationPass(_material)
{
renderPassEvent = settings.renderPassEvent
};
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (_pass == null || _material == null)
{
return;
}
// カメラのカラーターゲットを設定
_pass.Setup(renderer.cameraColorTargetHandle);
renderer.EnqueuePass(_pass);
}
protected override void Dispose(bool disposing)
{
CoreUtils.Destroy(_material);
}
/// <summary>
/// 実際にフルスクリーン描画を行う ScriptableRenderPass。
/// </summary>
private class ChromaticAbberationPass : ScriptableRenderPass
{
private static readonly string ProfilerTag = "ChromaticAbberationPass";
private static readonly int TempTargetId = Shader.PropertyToID("_ChromaticAbberationTemp");
private static readonly int IntensityId = Shader.PropertyToID("_Intensity");
private Material _material;
private RenderTargetIdentifier _source;
public ChromaticAbberationPass(Material material)
{
_material = material;
}
public void Setup(RenderTargetIdentifier source)
{
_source = source;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (_material == null)
{
return;
}
// シーン上の ChromaticAbberation コンポーネントを探す
ChromaticAbberation effect = Object.FindFirstObjectByType<ChromaticAbberation>();
if (effect == null)
{
// コンポーネントがなければ何もしない
return;
}
float intensity = effect.CurrentIntensity;
if (intensity <= 0f)
{
// 強度0なら何もしない(パフォーマンス節約)
return;
}
CommandBuffer cmd = CommandBufferPool.Get(ProfilerTag);
// 強度をマテリアルへ渡す
_material.SetFloat(IntensityId, intensity);
RenderTextureDescriptor desc = renderingData.cameraData.cameraTargetDescriptor;
desc.depthBufferBits = 0;
// 一時RTを確保
cmd.GetTemporaryRT(TempTargetId, desc, FilterMode.Bilinear);
// 元のカラーを一時RTへコピー
Blit(cmd, _source, TempTargetId);
// 一時RTから元のカラーへ、色収差シェーダを通して描画
Blit(cmd, TempTargetId, _source, _material, 0);
// 一時RT解放
cmd.ReleaseTemporaryRT(TempTargetId);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}
3. シェーダコード(ChromaticAbberation.shader)
RGBそれぞれのサンプル座標を少しだけずらして、色収差っぽいにじみを出します。
Shader "Hidden/Custom/ChromaticAbberation"
{
Properties
{
_MainTex ("MainTex", 2D) = "white" {}
_Intensity ("Intensity", Float) = 0.01
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
Name "ChromaticAbberationPass"
ZTest Always
ZWrite Off
Cull Off
Blend One Zero
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionHCS : SV_POSITION;
float2 uv : TEXCOORD0;
};
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
float _Intensity;
Varyings Vert (Attributes input)
{
Varyings output;
output.positionHCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}
half4 Frag (Varyings input) : SV_Target
{
float2 uv = input.uv;
// 画面中心からの距離に応じてずらし量を増やす(中央はほぼ影響なし、端ほど強く)
float2 center = float2(0.5, 0.5);
float2 dir = uv - center;
float dist = length(dir);
float2 offset = normalize(dir) * _Intensity * dist;
// RGB それぞれのサンプル位置を少しずらす
float3 col;
col.r = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv + offset).r;
col.g = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv).g;
col.b = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv - offset).b;
return half4(col, 1.0);
}
ENDHLSL
}
}
FallBack Off
}
使い方の手順
-
プロジェクトの準備
- Unity6 でプロジェクトを作成し、レンダーパイプラインに URP を使用していることを確認します。
- Assets フォルダ内に適当なフォルダ(例:
Assets/ChromaticAbberation)を作成します。 - そこに以下の3ファイルを作成し、上記コードをそれぞれコピペします。
ChromaticAbberation.csChromaticAbberationRenderFeature.csChromaticAbberation.shader
-
Render Feature の設定
- Project ウィンドウで URP の Renderer Asset(例:
ForwardRendererやUniversalRenderer)を選択します。 - Inspector の「Renderer Features」欄で「Add Renderer Feature」ボタンを押し、
ChromaticAbberationRenderFeatureを追加します。 - 追加された Feature の
Shaderスロットに、作成したChromaticAbberation.shaderをドラッグ&ドロップで割り当てます。
- Project ウィンドウで URP の Renderer Asset(例:
-
色収差コンポーネントをカメラに付ける
- シーン内のメインカメラ(例:
Main Camera)を選択します。 - 「Add Component」で
ChromaticAbberationを追加します。 Max IntensityやDuration、Fade Curveを好みの値に調整しておきます。
(まずはMax Intensity = 0.015,Duration = 0.3くらいがおすすめです)
- シーン内のメインカメラ(例:
-
ゲームロジックから呼び出す(具体例)
例えば「プレイヤーが敵に触れたときに色収差を発生させる」場合は、プレイヤー側のスクリプトで以下のようにします。using UnityEngine; [RequireComponent(typeof(Collider))] public class PlayerDamageExample : MonoBehaviour { [SerializeField] private ChromaticAbberation chromaticAbberation; private void Reset() { // 自動でカメラ上の ChromaticAbberation を探してセットする簡易実装 if (chromaticAbberation == null) { chromaticAbberation = FindFirstObjectByType<ChromaticAbberation>(); } } private void OnTriggerEnter(Collider other) { // 敵に当たったらダメージ演出として色収差を再生 if (other.CompareTag("Enemy")) { if (chromaticAbberation != null) { chromaticAbberation.PlayEffect(); } // ここでHPを減らすなどの処理を行う // TakeDamage(10); } } }- プレイヤー: 上記
PlayerDamageExampleをプレイヤーに付ける。 - 敵:
Enemyタグを付け、Is Triggerな Collider を持たせる。 - 動く床: 「着地の衝撃で色収差を少しだけ発生させる」といった演出も、
着地判定のタイミングでchromaticAbberation.PlayEffect(0.008f);のように呼ぶだけでOKです。
- プレイヤー: 上記
メリットと応用
ChromaticAbberation をコンポーネントとして分離することで、次のようなメリットがあります。
- ダメージ処理と画面演出が分離される
プレイヤーや敵のスクリプトは「いつダメージを受けたか」だけを知っていればよく、
画面をどう歪ませるかといった実装詳細はChromaticAbberation側に閉じ込められます。 - プレハブ管理が楽になる
メインカメラのプレハブにChromaticAbberationを付けておけば、
どのシーンでも同じダメージ演出を再利用できます。
シーンごとにコピーしたスクリプトをいじる必要がなくなります。 - レベルデザイン時に「演出だけ」差し替えられる
たとえば「このボス戦だけは色収差を強めにしたい」場合、
ボス戦シーンのカメラのMax IntensityやDurationを少し変えるだけで調整できます。
ダメージロジックには一切手を入れなくて済むので、安全に演出をいじれます。
さらに、色収差コンポーネント自体も小さくまとまっているので、
別の演出と組み合わせた拡張もしやすくなります。
改造案:色収差と同時に画面を白くフラッシュさせる
たとえば「ダメージ時に色収差+白フラッシュ」を同時に行いたい場合、
こんな感じで ChromaticAbberation に簡易的なフラッシュ値を追加することもできます。
/// 色収差と同時に、画面全体のホワイトアウト量を返す例
public float GetFlashAmount()
{
// 強度が高いほどフラッシュも強くする(適当な係数で調整)
// 0〜1 の範囲にクランプ
float flash = CurrentIntensity * 80f;
return Mathf.Clamp01(flash);
}
この GetFlashAmount() を別のフルスクリーンシェーダに渡して、
「色収差+白フェード」や「色収差+コントラストアップ」など、演出をどんどん足していくこともできます。
大事なのは、「色収差の強度を管理する責務」だけをこのコンポーネントに持たせることですね。
こうして小さなコンポーネントを組み合わせていくと、
後からのメンテナンスや演出調整がかなり楽になります。ぜひ自分のプロジェクト用にカスタマイズしてみてください。
