コード置き場「2DSTG #31 – #56」

コード置き場:2DSTG(#31-#56)
スポンサーリンク

【2DSTG制作】の動画内で表示されるコード:#31 – #56

サムネイルをクリックすると、YouTubeの動画ページに移動します。

#33:レーザーが通過するルートを表示

シェーダーグラフを使用して、レーザーが通過する経路を表示します。

サムネイル#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を使用して、メッセージウィンドウのテキストを切り替えます。

サムネイル#35

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を連動させるスクリプトを改修します。

サムネイル#36

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

ポストプロセスのパラメータを操作するスクリプトを作成します。

サムネイル#40

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:ボスユニットの登場演出

ポストプロセスやシェーダーグラフを使用して、ボスユニットの登場演出を作成します。
サムネイル#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のアニメーション機能を使用して、緊急回避のスキルを実装します。
サムネイル#45
指定した位置まで移動させる
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を使用して、敵ユニットの移動ルートを設定します。
サムネイル#46
移動経路の設定
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:ボスの移動パターン

ボスユニットは移動パターンを複雑にしたいので、複数のパスを切り替える仕組みにしました。
サムネイル#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を使用して、ボスユニットのギミック(地形・バリア)を作成します。
サムネイル#51
氷の設置
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を使用して、キーボードとゲームパッドで遊べるようにします。
サムネイル#52
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」を使用して、プレイヤーの入力に条件を設定します。
サンプルでは「スティックを左右に素早く動かす」ことを条件としました。
サムネイル#54
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()
    {
    }
}
タイトルとURLをコピーしました