📜
【Unity】ラスタースクロール風の演出【GIFアリ】
ラスタースクロールとは何か
- 上記動画 1:36 以降の背景のような演出
-
動画出典
- タイトル :サマーカーニバル'92 烈火(FC) クリア動画
- チャンネル:spm moto
-
ゲーム出典
- タイトル:烈火
- 開発元 :KID
- 販売元 :ナグザット
背景
- ラスタースクロール風のエフェクトを作りたい
- 他サイトでは既に実装した方が!しかし、滑らかすぎる…
- なんかこう…もっとジャギジャギにしたい
実装方針
-
スクロール情報の設定機能
- アニメーションの挙動(分割数,方向,振幅など)をまとめた設定用クラスも作る
- 呼び出し元はこのクラスを通して設定を行い
RasterScroll
クラスに渡す
-
スプライト分割
- 指定スプライトを指定数分、分割して行ごとに独立したスプライトを作成
-
分割スプライトの配置
- 各分割スプライトを表示するため、
GameObject
+SpriteRenderer
を生成 - 元々のスプライトのようになるように、座標を計算して配置
- 各分割スプライトを表示するため、
-
ラスタースクロールアニメーションの適用
-
DOTween
で行ごとに波状アニメーションを付加
-
結果
分割数:12 振幅:5 1振動の時間:0.5 行ごとの遅延:0.5
- 滑らかさがなくてイイ感じ!
シーンビューでの様子
実際のコード
- アニメーション設定用クラス
/// <summary>
/// ラスタースクロールの動作設定をまとめた構造体
/// </summary>
public class RasterScrollConfig
{
public bool CanWaveX = true;
public bool CanWaveY = false;
public int SplitCount = 5;
public float Amplitude = 5f;
public float Duration = 0.5f;
public float Delay = 0.05f;
public Ease Easing = Ease.Linear;
}
- 本体
using DG.Tweening;
using UnityEngine;
/// <summary>
/// スプライトを分割し、各ラインにラスタースクロール風演出を適用するクラス
/// </summary>
public sealed class RasterScroll
{
private RasterScrollConfig config;
private SpriteRenderer usingSprRend; //元スプライト
private SpriteRenderer[] splitSprites; //分割スプライトの描画用SpriteRenderer配列
/// <summary>
/// 指定されたGameObjectにラスタースクロールを適用する
/// </summary>
public static void Create(GameObject target, RasterScrollConfig config)
{
if (!target.TryGetComponent(out SpriteRenderer targetSprRend))
{
Debug.LogError("RasterScroll: SpriteRenderer が必要");
return;
}
//分割スプライト用の親オブジェクトを作成し、Transform状態を複製
GameObject parentObj = new("RasterScroll_" + target.name);
Transform parentT = parentObj.transform;
Transform targetT = target.transform;
parentT.SetParent(targetT.parent, false);
parentT.SetLocalPositionAndRotation(targetT.localPosition, targetT.localRotation);
parentT.localScale = targetT.localScale;
//RasterScrollインスタンスを作成し、初期化を実行
RasterScroll rs = new();
rs.config = config;
rs.usingSprRend = targetSprRend;
rs.SetupSpriteAnimation(parentObj.transform);
}
private void SetupSpriteAnimation(Transform parent)
{
//分割スプライトを生成、GameObject化して配置
Sprite[] sprites = GenerateSplitSprites(usingSprRend.sprite);
CreateSpriteObjects(sprites, parent);
//アニメーション適用
ApplyWaveAnimation();
}
/// <summary>
/// スプライトを上下にN分割し、個別のスプライトを作成する
/// </summary>
private Sprite[] GenerateSplitSprites(Sprite originalSpr)
{
float sprHeight = originalSpr.rect.height;
Vector2 pivot = new(0.5f, 0.5f); //中央基準で生成
Sprite[] result = new Sprite[config.SplitCount];
for (int i = 0; i < config.SplitCount; i++)
{
//rect.y を行ごとにオフセットし、高さを分割
Rect rect = new(
originalSpr.rect.x,
originalSpr.rect.y + (sprHeight / config.SplitCount) * i,
originalSpr.rect.width,
sprHeight / config.SplitCount
);
//新しいスプライトを生成(元テクスチャ共有)
result[i] = Sprite.Create(originalSpr.texture, rect, pivot, originalSpr.pixelsPerUnit);
}
return result;
}
/// <summary>
/// 分割スプライトをGameObjectに変換し、位置に応じて配置
/// </summary>
private void CreateSpriteObjects(Sprite[] sprites, Transform parent)
{
splitSprites = new SpriteRenderer[config.SplitCount];
//元スプライトのサイズを行数で割る
float splitHeight = usingSprRend.size.y / config.SplitCount;
for (int i = 0; i < config.SplitCount; i++)
{
GameObject obj = new($"lineSpr_{i}");
obj.transform.SetParent(parent, false);
SpriteRenderer sr = obj.AddComponent<SpriteRenderer>();
sr.sprite = sprites[i];
//中央基準で配置:+Y方向に上がるように、中央からオフセット計算
float yPos = (i * splitHeight) - (splitHeight * (config.SplitCount - 1) / 2);
obj.transform.localPosition = new Vector3(0, yPos, 0);
splitSprites[i] = sr;
}
}
private void ApplyWaveAnimation()
{
float centerYPos = usingSprRend.transform.localPosition.y;
for (int i = 0; i < config.SplitCount; i++)
{
//移動目標地点は各スプライトの初期位置を基準にする
Vector3 currentPos = splitSprites[i].transform.localPosition;
Vector3 targetPos = currentPos;
//動ける方向に応じて移動目標地点を設定(中心からの相対位置を保持)
if (config.CanWaveY) targetPos.y = centerYPos + (currentPos.y - centerYPos) + config.Amplitude;
if (config.CanWaveX) targetPos.x = currentPos.x + config.Amplitude;
//ラスタースクロールの各行の動きを設定
splitSprites[i].transform.DOLocalMove(targetPos, config.Duration)
.SetDelay(i * config.Delay) //行ごとの遅延を設定
.SetLoops(-1, LoopType.Yoyo) //無限往復運動に設定
.SetEase(config.Easing); //イージング曲線を適用
}
}
}
テストプログラム
using UnityEngine;
public class Test_RasterScroll : MonoBehaviour
{
[Header("対象オブジェクト(SpriteRendererを持つ)")]
public GameObject targetObject;
[Header("ラスタースクロール設定")]
public bool canWaveX = true;
public bool canWaveY = false;
public int splitCount = 8;
public float amplitude = 10f;
public float duration = 0.4f;
public float delay = 0.03f;
public DG.Tweening.Ease easing = DG.Tweening.Ease.InOutSine;
private void Start()
{
//コンフィグを生成して渡す
RasterScrollConfig config = new RasterScrollConfig
{
SplitCount = splitCount,
CanWaveX = canWaveX,
CanWaveY = canWaveY,
Amplitude = amplitude,
Duration = duration,
Delay = delay,
Easing = easing
};
//ラスタースクロールの適用
RasterScroll.Create(targetObject, config);
//元々のスプライトは破棄
Destroy(gameObject);
}
}
あって助かった機能
public static Sprite Create(Texture2D texture, Rect rect, Vector2 pivot, float pixelsPerUnit)
- テクスチャの好きな範囲をスプライトにできる
- 「テクスチャから指定部分を切り取れたらなー」と思ってたらちょうどイイ関数があった
public void SetLocalPositionAndRotation(Vector3 localPosition, Quaternion localRotation)
- 名前の通り、ローカル座標系での位置,回転を設定する
-
SetLocalPosition
,SetLocalRotation
みたいに分かれていない理由は不明
public static T SetDelay<T>(this T t, float delay) where T : Tween
public static T SetLoops<T>(this T t, int loops, LoopType loopType) where T : Tween
public static T SetEase<T>(this T t, Ease ease) where T : Tween
- これらで「行ごとの遅延」,「アニメーションループ設定」,「イージング」が楽にできた
-
DOTween
みんなも使っていこう
使用ライブラリ
- DOTween by Demigiant (MIT License)
参考
- Unity Documentation - Sprite.Create
- Unity Documentation - Transform.SetLocalPositionAndRotation
- DOTween - Documentation
余談
- これシェーダーのほうが向いてない?
Discussion