Unityの2D RendererやSprite Rendererを用いた2Dゲーム開発において、カメラ移動時やキャラクター移動時に「スプライトの輪郭がプルプルと震える(ジッター現象)」「スクロールするとドット絵のピクセルが引き伸ばされたり潰れたりして、形状が歪む」といった描画バグに直面することがよくあります。これらの現象は、プレイヤーが意図するレトロなドット絵(Pixel Art)の美しさを大きく損ねてしまいます。本記事では、このスプライトジッターとドット歪みが発生する画面描画・座標計算の仕組みと、2Dゲームで必須となるPixel Perfect Cameraを用いた根本的な解消手順を解説します。
1. 根本原因:サブピクセル描画とカメラの更新同期ズレ
スプライトのジッターおよびドットの歪みが発生する原因は、主に以下の2点に集約されます。
原因①:サブピクセル(浮動小数点)座標でのラスタライズ
通常、Unityの座標系(World Space)は float(単精度浮動小数点数)で定義されており、オブジェクトやカメラは (x: 10.254, y: 5.672) のような連続値で自由に移動できます。しかし、モニターの画面は「物理的なピクセル(整数グリッド)」で構成されています。
スプライトが物理ピクセルの境界をまたいで移動すると、GPUはピクセルごとにテクスチャのどの部分(テクセル)を描画するかをサンプリングします。この時、フィルターモードが Point (no filter) の場合、最も近いピクセル中心に丸め込まれる(ニアレストネイバー)ため、スプライトが画面上を移動するにつれて、あるピクセルが突然1ピクセル分隣に飛ぶような動きになり、小刻みな震え(ジッター)やドットの歪み(一部のドットが2倍の太さになり、隣が消えかける等)を引き起こします。
原因②:UpdateとFixedUpdateの物理・カメラ更新のズレ
キャラクターの移動処理を FixedUpdate(物理周期:デフォルト50Hz)で行い、カメラの追尾処理を Update や LateUpdate(描画フレームレートに依存)で行っている場合、物理計算とレンダリングのタイミングが同期せず、フレームごとにおけるカメラとキャラクターの相対位置が微小に前後にズレます。これが画面スクロール時に顕著なガタつきとなって現れます。
2. 解決手順①:Pixel Perfect Camera의 導入と設定
Unityが公式に提供する Pixel Perfect Camera コンポーネント(URPパッケージに含まれる)をメインカメラに付与することで、描画バッファを指定したドット絵の基準解像度に合わせてピクセル単位でスナップさせ、描画解像度を完全に制御できます。
- メインカメラ(Main Camera)のオブジェクトを選択します。
- インスペクターで Add Component をクリックし、
Pixel Perfect Cameraを追加します。 - 以下のパラメータを、ゲームのアセット設計に合わせて厳密に設定します。
| プロパティ | 設定値・推奨パラメータ | 効果と詳細 |
|---|---|---|
| Assets Pixels Per Unit | 16 または 32(プロジェクトのPPU) |
スプライトアセットの「Pixels Per Unit」設定と完全に一致させる必要があります。 |
| Reference Resolution | 320 x 180、480 x 270 など(低解像度) |
ゲーム画面のベースとなる仮想解像度です。レトロゲーム風にするには縦横比16:9の低解像度を設定します。 |
| Upscale Render Texture | 有効 (Checked) |
基準解像度のレンダーテクスチャを一度作成してスプライトを描画し、それを画面解像度に一括でニアレストネイバー拡大します。ドット歪みを完全に防ぐために非常に有効です。 |
| Pixel Snap | 有効 (Checked) |
スプライトの描画位置をワールド座標系でピクセル単位に丸め込みます。 |
Upscale Render Texture を有効にすると、回転や物理的な移動も元のドットグリッド(例:320x180内)に拘束されるため、完全にレトロな見た目になります。一方、回転や半透明ブレンドを現代的な高解像度のピクセルで滑らかに表現したい場合は、これを無効にし、カメラのみをピクセル間隔で移動させます。
3. 解決手順②:Cinemachine使用時の Pixel Perfect 設定
2Dカメラワークで標準的に使用される Cinemachine を導入している場合、単にメインカメラに Pixel Perfect Camera を付けるだけでは、Cinemachineの仮想カメラがサブピクセル単位で動作するため、ジッターが再発します。必ず以下の設定を行ってください。
- メインカメラに
CinemachineBrainとPixel Perfect Cameraが両方付いていることを確認します。 - Cinemachineの仮想カメラ(Cinemachine Virtual Camera)オブジェクトを選択します。
- インスペクター最下部の Extensions ドロップダウンから
Cinemachine Pixel Perfectを追加します。
これにより、Cinemachineが算出するカメラ位置が自動的に Pixel Perfect Camera のピクセル単位のスナップ設定と同期し、追従時のジッターが解消されます。
4. 解決手順③:物理・オブジェクト移動とスナップのスクリプト設計
スプライトの位置やカメラの追尾を滑らかにしつつ、ピクセルスナップを徹底するために、プレイヤーの移動は物理エンジン(Rigidbody 2D)の Interpolate(補間) を有効にし、カメラの更新とタイミングを合わせます。
また、C#でスプライトやオブジェクトをピクセル境界にカチッとスナップさせる軽量なヘルパースクリプトを導入するのも効果的です。以下は、スプライトの座標をPPUに基づいて強制的にピクセル単位に丸め込んで描画するスクリプト例です。
using UnityEngine;
[DisallowMultipleComponent]
public class SpritePixelSnapper : MonoBehaviour
{
public int pixelsPerUnit = 16;
private Vector3 originalPos;
private Transform parentTransform;
void Start()
{
parentTransform = transform.parent;
originalPos = transform.localPosition;
}
// LateUpdateで元の物理的なローカル座標からスナップされた座標を計算し適用する
void LateUpdate()
{
Vector3 currentWorldPos = parentTransform != null ? parentTransform.position + originalPos : transform.position;
// ピクセル単位に丸め込む計算
float snapX = Mathf.Round(currentWorldPos.x * pixelsPerUnit) / pixelsPerUnit;
float snapY = Mathf.Round(currentWorldPos.y * pixelsPerUnit) / pixelsPerUnit;
transform.position = new Vector3(snapX, snapY, transform.position.z);
}
}
※このスクリプトは、キャラクターの移動やアニメーション自体は通常の浮動小数座標で行つつ、最終的なレンダリング座標のみをピクセルグリッドに適合させたい場合に非常に有効です。