Contents
【2DSTG制作】の動画内で表示されるコード:#31 – #56
サムネイルをクリックすると、YouTubeの動画ページに移動します。
#33:レーザーが通過するルートを表示
シェーダーグラフを使用して、レーザーが通過する経路を表示します。
TrailRendererで事前にルートを表示
// 一定時間後に発射されるレーザー
[SerializeField] GameObject laser;
// エフェクト(Trail Renderer)の移動速度
[SerializeField] float speed;
// エフェクトの始点
Vector3 startPos;
void Start()
{
startPos = transform.position;
// レーザー発射
StartCoroutine(LaserFiring());
}
void Update()
{
// 画面左に進む(エフェクト)
transform.position -= transform.right * speed * Time.deltaTime;
}
IEnumerator LaserFiring()
{
// 回避するまでの猶予
yield return new WaitForSeconds(3.0f);
// エフェクトの開始位置からレーザーを発射
Instantiate(laser, startPos, transform.rotation);
// レーザーが通過するまで待機して削除
yield return new WaitForSeconds(1.0f);
Destroy(gameObject);
}
#35:マウスオーバーでテキストの内容を変更
Event Triggerを使用して、メッセージウィンドウのテキストを切り替えます。
message window
// TextMeshProを扱う際に必要 using TMPro;
------------------------------------- // メッセージウィンドウに表示するテキスト [SerializeField] TextMeshProUGUI shopText; // マウスオーバーでテキストの内容を変更 public void OnPointerButton(string button) { switch(button) { case "Speed": shopText.SetText("プレイヤーの移動速度が上昇します。"); break; case "Slash": shopText.SetText("斬撃を飛ばすスキルが使用可能になります。"); break; case "Attack": shopText.SetText("プレイヤーの攻撃力が上昇します。"); break; default: break; } } // カーソルがアイコンから離れた時の処理 public void ResetText() { // テキストをデフォルトの内容に戻す shopText.SetText("いらっしゃいませ。\n何をお求めですか?"); }
#36:Live2Dモデルの当たり判定
Live2DモデルのパーツとGameObjectを連動させるスクリプトを改修します。
Live2DモデルのパーツとGameObjectを連動させる
// GameObjectが追従するアートメッシュ
[SerializeField] CubismRenderer _cubismRenderer;
// 座標の調整用
[SerializeField] float adjustX;
[SerializeField] float adjustY;
void Update()
{
var mesh = _cubismRenderer.Mesh;
// メッシュを囲う矩形の中心
Vector3 meshPosition = mesh.bounds.center;
// 追従する座標の調整
meshPosition.x = meshPosition.x + adjustX;
meshPosition.y = meshPosition.y + adjustY;
// 指定したパーツの座標に追従
transform.localPosition = meshPosition;
}
#40:ポストプロセスの操作Part2
ポストプロセスのパラメータを操作するスクリプトを作成します。
ColorAdjustmentsのパラメータを操作
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using DG.Tweening;
--------------------------------------
// Volumeコンポーネント
[SerializeField] Volume volume;
// パラメータを操作するエフェクト
ColorAdjustments colorAdjustments;
void Start()
{
volume.profile.TryGet(out colorAdjustments);
}
// 1秒かけて彩度の値を-100(モノクロ)にする
public void Monochrome()
{
DOTween.To(
() => colorAdjustments.saturation.value,
num => colorAdjustments.saturation.value = num,
-100f, 1.0f
)
.SetUpdate(true); // timeScaleを無視する
}
// 彩度を初期値に戻す(エフェクト解除)
public void Color()
{
DOTween.To(
() => colorAdjustments.saturation.value,
num => colorAdjustments.saturation.value = num,
0, 1.0f
);
}
#42:ボスユニットの登場演出
ポストプロセスやシェーダーグラフを使用して、ボスユニットの登場演出を作成します。

LineRenderer_Animation
using UnityEngine;
using DG.Tweening;
--------------------------------
// 表示する線
[SerializeField] LineRenderer lineRenderer;
void Start()
{
var sequence = DOTween.Sequence();
// 線の幅を変更する(0 => 0.5)
sequence.Append(DOTween.To(() => lineRenderer.startWidth, num => lineRenderer.startWidth = num, 0.5f, 1.0f))
.Join(DOTween.To(() => lineRenderer.endWidth, num => lineRenderer.endWidth = num, 0.5f, 1.0f))
// 1.5秒待機
.AppendInterval(1.5f)
// 線の幅を0にする(非表示)
.Append(DOTween.To(() => lineRenderer.startWidth, num => lineRenderer.startWidth = num, 0, 1.0f))
.Join(DOTween.To(() => lineRenderer.endWidth, num => lineRenderer.endWidth = num, 0, 1.0f))
// アニメーション終了後に削除
.AppendCallback(() => Destroy(gameObject));
}
#45:緊急回避の実装
Unityのアニメーション機能を使用して、緊急回避のスキルを実装します。

指定した位置まで移動させる
using UnityEngine;
using DG.Tweening;
--------------------------------
// 再生するアニメーションのトリガー
static int dodgeParamHash = Animator.StringToHash("Dodge");
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// スペースキーが押されたら、緊急回避のアニメーションを再生
animator.SetTrigger(dodgeParamHash);
// 入力されていた方向キーに応じて相対的に移動
if (Input.GetKey(KeyCode.UpArrow) && Input.GetKey(KeyCode.RightArrow)) {
// 入力が右上であれば、右上に移動
transform.DOLocalMove(new Vector2(1, 1), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
// 入力が右下であれば、右下に移動
} else if (Input.GetKey(KeyCode.DownArrow) && Input.GetKey(KeyCode.RightArrow)) {
transform.DOLocalMove(new Vector2(1, -1), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
// 中略 //
} else {
// 方向キーが押されていない場合は右に移動
transform.DOLocalMove(new Vector2(1, 0), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
}
}
}
#46:敵ユニットの移動経路
DOTweenのDOPathを使用して、敵ユニットの移動ルートを設定します。

移動経路の設定
using UnityEngine;
using DG.Tweening;
------------------------------
// ユニットが通過する座標
[SerializeField] Vector3[] wayPoint;
// 移動時間
[SerializeField] float moveTime;
void Start()
{
// 生成したパスに沿って移動させる
transform.DOPath(wayPoint, moveTime, PathType.Linear)
// 等速移動
.SetEase(Ease.Linear)
// 撃破されたらアニメーションを停止
.SetLink(gameObject);
}
#47:ボスの移動パターン
ボスユニットは移動パターンを複雑にしたいので、複数のパスを切り替える仕組みにしました。

移動経路の切り替え
using UnityEngine;
using DG.Tweening;
---------------------------
// ボスユニットが通過する座標
[SerializeField] Vector3[] wayPointA;
[SerializeField] Vector3[] wayPointB;
// 移動時間
[SerializeField] float moveTime;
Sequence sequence1;
Sequence sequence2;
void Start()
{
sequence1 = DOTween.Sequence()
// パスに沿って移動
.Append(transform.DOPath(wayPointA, moveTime, PathType.Linear).SetOptions(true))
// パスを1周したら少し待機
.AppendInterval(0.5f)
// 無限ループ
.SetLoops(-1)
// 自動で破棄しない
.SetAutoKill(false)
// 自動で再生しない
.Pause()
// オブジェクトが削除されたタイミングで破棄
.SetLink(gameObject);
sequence2 = DOTween.Sequnece()
...
}
public void MovePath1()
{
// 別のパスを停止
sequence2.Pause();
var path1 = DOTween.Sequence();
// パスのスタート地点に移動
path1.Append(transform.DOMove(new Vector3(2.0f, 0, 0), 1.0f))
// アニメーションを再生
.AppendCallback(() => sequence1.Restart())
.SetLink(gameObject);
}
#51:ボスユニットのギミック
Effector2Dを使用して、ボスユニットのギミック(地形・バリア)を作成します。

氷の設置
using UnityEngine;
using DG.Tweening;
----------------------------------
// 停止する位置
[SerializeField] Vector2 targetPos;
// 爆発エフェクト
[SerializeField] GameObject explosion;
Rigidbody2D rb2d;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
var sequence = DOTween.Sequence();
// 指定した位置に移動する
sequence.Append(rb2d.DOMove(targetPos, 2.0f).SetEase(Ease.InQuart))
// 氷が存在する時間
.AppendInterval(10.0f)
.AppendCallback(() =>
{
// 爆発を生成して、氷を削除
Instantiate(explosion, transform.position, Quaternion.identity);
Destroy(gameObject);
});
}
#52:ゲームパッドでも遊べるようにしてみる
InputSystemを使用して、キーボードとゲームパッドで遊べるようにします。

InputSystemでプレイヤーを操作
using UnityEngine;
using UnityEngine.InputSystem;
------------------------------
PlayerInput playerInput;
InputAction fire;
// 進む方向
Vector2 direction;
// 移動速度
float speed;
void Awake()
{
playerInput = GetComponent<PlayerInput>();
fire = playerInput.actions["Fire"];
}
void OnEnable()
{
playerInput.actions["Move"].performed += OnMove;
playerInput.actions["Move"].canceled += OnMoveStop;
playerInput.actions["Dodge"].performed += OnDodge;
playerInput.actions["Skill"].performed += OnSkill;
playerInput.actions["Style"].performed += OnStyle;
}
void OnDisable()
{
playerInput.actions["Move"].performed -= OnMove;
playerInput.actions["Move"].canceled -= OnMoveStop;
playerInput.actions["Dodge"].performed -= OnDodge;
playerInput.actions["Skill"].performed -= OnSkill;
playerInput.actions["Style"].performed -= OnStyle;
}
void OnMove(InputAction.CallbackContext context)
{
// 左スティックの値を取得
var value = context.ReadValue<Vector2>();
// プレイヤーの移動方向に設定
direction = new Vector2(value.x, value.y).normalized;
}
void OnMoveStop(InputAction.CallbackContext context)
{
// プレイヤーを停止
direction = Vector2.zero;
}
void OnDodge(InputAction.CallbackContext context)
{
// Dodgeのアクションに割り当てたキーが押されたら緊急回避
DodgeRoll();
}
void Update()
{
// OnMoveで取得した方向に移動
transform.position += direction * speed * Time.deltaTime;
// Fireのアクションに割り当てたキーが押されている間
if (fire.ReadValue<float>() > 0)
{
// ショットを撃つ
Shot();
}
}
void DodgeRoll()
{
// アニメーションを再生
animator.SetTrigger(dodgeParamHash);
// 入力に応じて移動
if (direction.x > 0.5f && direction.y > 0.5f) {
// 右上
transform.DOLocalMove(new Vector2(1, 1), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
// 右下に入力されていたら
} else if (direction.x > 0.5f && direction.y < -0.5f) {
// 右下
transform.DOLocalMove(new Vector2(1, -1), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
// 中略 //
} else if (Mathf.Abs(direction.x) < 0.2f && direction.y < 0) {
// X軸の入力が±0.2未満で、下方向に入力があれば下に移動
transform.DOLocalMove(new Vector2(0, -1), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
// デフォルトは右に移動
} else {
transform.DOLocalMove(new Vector2(1, 0), 0.7f).SetRelative(true).SetEase(Ease.OutExpo);
}
}
#54:入力に条件を設定してみる
InputSystemの「Interaction」を使用して、プレイヤーの入力に条件を設定します。
サンプルでは「スティックを左右に素早く動かす」ことを条件としました。

CustomInteraction
using UnityEngine;
using UnityEngine.InputSystem;
#if UNITY_EDITOR
using UnityEditor;
#endif
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
public class WiggleInteraction : IInputInteraction
{
#if UNITY_EDITOR
static WiggleInteraction()
{
Initialize();
}
#endif
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
// 作成したInteractionをInputSystemに登録
InputSystem.RegisterInteraction<WiggleInteraction>();
}
public float duration;
// ニュートラルポジションかどうか
private bool isNeutral = true;
// スティックを最初に倒した方向の確認
private bool rightStart;
public void Process(ref InputInteractionContext context)
{
// 左スティックの値を取得
var value = context.ReadValue<Vector2>();
if (context.timerHasExpired)
{
context.Canceled();
return;
}
switch (context.phase)
{
case InputActionPhase.Waiting:
// スティックの状態がニュートラルか確認
if (isNeutral == false)
{
if (Mathf.Abs(value.x) <= 0.1f)
{
isNeutral = true;
}
break;
}
// スティックを一定以上傾けた場合
if (Mathf.Abs(value.x) >= 0.8f)
{
// スティックを右側に倒した
if (value.x > 0) {
rightStart = true;
} else {
rightStart = false;
}
isNeutral = false;
context.Started();
context.SetTimeout(duration);
}
break;
case InputActionPhase.Started:
// 指定した時間内にスティックを反対側に動かすことができれば
if (value.x <= -0.8f && rightStart == true || value.x >= 0.8f && rightStart == false)
{
// Interactionが完了
context.Performed();
}
break;
}
}
public void Reset()
{
}
}





