コード置き場「2DSTG制作 #1 – #30」

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

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

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

#3:自機狙い弾の追加

サムネイル#3

ShotCount
// 敵弾の発射した回数をカウントする。
private int count = 0;

// カウントした数÷5で余りが0となったら、敵弾の発射間隔を長くする。
if (count % 5 == 0) {
  shotDelay = 0.3f;
} else {
  shotDelay = 0.1f;
}

#6:画面外からの敵弾

サムネイル#6

Renderer
Renderer targetRenderer;

void Start()
{
  targetRenderer = GetComponent<Renderer>();
}

void Update()
{
  if (targetRenderer.isVisible) {
    // 画面に表示されている時の処理
  } else {
    // 画面に表示されていない時の処理
  }
}

#7:Live2Dのアニメーションと敵弾の発射位置を連動させる

サムネイル#7

Live2D:パーツに追従
[SerializeField] CubismRenderer _cubismRenderer;

void Update()
{
  var mesh = _cubismRenderer.Mesh;

  // メッシュを囲う矩形の中心
  Vector3 meshPosition = mesh.bounds.center;

  // メッシュの中心座標に追従
  transform.position = meshPosition;
}

#9:Light2Dで光を照らす

サムネイル#9

Light2D:明るさの変更
// DOTweenを使用する際に必要
using DG.Tweening;
// Light2Dを使用する際に必要
using UnityEngine.Rendering.Universal;
--------------------------------------
Light2D light2d; void Start() { light2d = GetComponent<Light2D>(); } public void IntensityChg() { DOTween.To( () => light2d.intensity, // 何を対象にするのか num => light2d.intensity = num, // 値の更新 0.5f, // 最終的な値 10.0f // アニメーション時間 ); }

#12:スクリプトからLive2Dモデルの色を操作

サムネイル#12
Live2Dモデルの色を操作するサンプル
using Live2D.Cubism.Rendering;

------------------------------

CubismRenderController renderController;

void Start()
{
    renderController = GetComponent<CubismRenderController>();
}

private IEnumerator HitColor()
{
    renderController.OverwriteFlagForModelScreenColors = true;

    yield return null;

    // スクリーンで白色を合成
    var screenColor = new Color(0.3f, 0.3f, 0.3f, 1.0f);

    // 全てのパーツに指定した色を適用する
    for (int i = 0; i < renderController.Renderers.Length; i++)
    {
        renderController.Renderers[i].ScreenColor = screenColor;
    }

    yield return new WaitForSeconds(0.03f);

    // 元の色に戻す
    renderController.OverwriteFlagForModelScreenColors = false;
}

#14:Animatorでポストプロセスを操作

サムネイル#14
Animatorの操作
Animator animator;

void Start()
{
    animator = GetComponent<Animator>();
}

// 任意のタイミングでエフェクトを適用
animator.SetTrigger("Monochrome");
// 任意のタイミングでエフェクトを解除
animator.SetTrigger("Color");

#15:レーザーの実装

サムネイル#15
Laserを発射するサンプル
// オブジェクト生成から削除するまでの時間
[SerializeField] float lifeTime = 1.0f;
// 弾の攻撃力
public int power = 1;
// レーザーか否か
public bool laser;

Renderer targetRenderer;

void Start()
{
    // lifeTime秒後に削除
    Destroy(gameObject, lifeTime);
    targetRenderer = GetComponent<Renderer>();
}

void Update()
{
    // 画面に表示されなくなったら
    if (!targetRenderer.isVisible) {
        if (laser == false) {
            // レーザー以外は削除
            Destroy(gameObject);
        } else {
            // レーザーは攻撃力ゼロ
            power = 0;
        }
    }
}

#16:反射レーザー

サムネイル#16
ReflectLaserのサンプル
Vector2 lastVelocity = Vector2.zero;

Rigidbody2D rb2d;

void Start()
{
    rb2d = GetComponent<Rigidbody2D>();
}

void FixedUpdate()
{
    // 次の速度の計算用にVelocityの値を保持しておく
    lastVelocity = rb2d.velocity;
}

void OnCollisionEnter2D(Collision2D collision) {
    // 反射する地形に接触したら
    if (collision.gameObject.tag == "Terrain") {
        // 接触した地形に垂直な単位ベクトル(法線ベクトル)を取得
        Vector2 normalVector = collision.contacts[0].normal;
        // 反射ベクトルを計算
        Vector2 reflectVector = Vector2.Reflect(lastVelocity, normalVector);
        // 速度を更新
        rb2d.velocity = reflectVector;
    }
}

#17:追尾レーザー

サムネイル#17
HomingLaserのサンプル
Vector3 velocity;
// 追尾レーザーの初期位置
Vector3 position;

Vector3 acceleration;
// ターゲットが存在しない時の目標位置
Vector3 randomPos;

Transform target;
// 追尾レーザーの初期位置に最も近いオブジェクト
GameObject searchNearObj;
// 着弾までの時間
[SerializeField] float period;

void Start()
{
    position = transform.position;
    searchNearObj = FindClosestEnemy();
    // ターゲットが存在すれば位置を取得
    if (searchNearObj != null) {
        target = searchNearObj.transform;
    }
    randomPos = new Vector3(4.0f, Random.Range(-1.5f, 1.5f), 0);
    // プレイヤーの斜め後ろにレーザーを発射
    velocity = new Vector3(Random.Range(-5.0f, -2.5f), Random.Range(-6.0f, 6.0f), 0);
}

void Update()
{
    acceleration = Vector3.zero;

    if (searchNearObj != null) {
        Vector3 diff = target.position - position;
        acceleration += (diff - velocity * period) * 2f / (period * period);
    } else {
        // ターゲットが存在しない場合は予備の目標地点に移動
        Vector3 diff = randomPos - position;
        acceleration += (diff - velocity * period) * 2f / (period * period);
    }

    period -= Time.deltaTime;

    if (period < 0f) return;

    velocity += acceleration * Time.deltaTime;
    position += velocity * Time.deltaTime;
    transform.position = position;
}

public GameObject FindClosestEnemy()
{
    // EnemyのTagを持つゲームオブジェクトの配列
    GameObject[] gos;
    gos = GameObject.FindGameObjectsWithTag("Enemy");
    // 最も近い位置に存在するEnemy
    GameObject closest = null;

    float distance = Mathf.Infinity;
    Vector3 position = transform.position;

    foreach (GameObject go in gos)
    {
        Vector3 diff = go.transform.position - position;
        float curDistance = diff.sqrMagnitude;
        if (curDistance < distance)
        {
            closest = go;
            distance = curDistance;
        }
    }
    // 最も近かったEnemyを返す
    return closest;
}

#18:追尾レーザー(同時に複数発射)・ヒットエフェクト

サムネイル#18
画面内の敵ユニットをランダムに取得
public GameObject[] FindEnemy()
{
    // EnemyのTagを持つゲームオブジェクトの配列
    GameObject[] gos;
    gos = GameObject.FindGameObjectsWithTag("Enemy");
    // 画面内の敵ユニットを格納する配列
    enemyList = new GameObject[6];
    int i = 0;
    foreach (GameObject go in gos)
    {
        // 画面外の敵ユニットは除外
        if (go.transform.position.x >= 4 || go.transform.position.x <= -4)
        {
            continue;
        }
        enemyList[i] = go;
        i++;
        if (i >= 6)
        {
            i = 0;
        }
    }
    return enemyList;
}
ヒットエフェクトを生成
[SerializeField] GameObject hitEffect;

// ヒットエフェクトの生成
public void HitEffect()
{
    Instantiate(hitEffect, transform.position, transform.rotation);
}

void OnTriggerEnter2D(Collider2D collision)
{
    // 敵ユニットに当たったら
    if (collision.gameObject.tag == "Enemy")
    {
        // SEを再生
        SoundManager.instance.PlaySE();
        // ヒットエフェクトを生成
        HitEffect();
    }
}

#19:自機狙いの追尾レーザー

敵ユニットがプレイヤーをターゲットにして発射する追尾レーザーを実装します。
追尾性能を調整できるようにして、適切な難易度となるようなパラメータを探してみました。
サムネイル#19
HomingLaser_Enemy:加速度に上限を設ける
// 加速度の上限
[SerializeField] float limit;

void Update()
{
    var acceleration = Vector3.zero;
    var diff = target.position - position;
    acceleration += (diff - velocity * period) * 2f / (period * period);
    // 加速度の上限値を設定する
    if (acceleration.magnitude > limit)
    {
        acceleration = acceleration.normalized * limit;
    }

    period -= Time.deltaTime;

    velocity += acceleration * Time.deltaTime;
    position += velocity * Time.deltaTime;
    transform.position = position;
}

#20:ヒットストップの実装

プレイヤーが被弾したタイミングで画面を停止させる処理を実装します。
サムネイル#20
画面の停止+カメラの揺れ
using DG.Tweening;

-----------------------------------

void Start()
{
    // 初期位置を取得
    initialPos = transform.position;
}

public void SampleB()
{
    // 画面の停止
    Time.timeScale = 0;
    // 対象を揺らす
    transform.DOShakePosition(0.4f, 0.3f, 30, 90)
        // timeScaleに影響されなくなる
        .SetUpdate(true)
        .OnComplete(() =>
        {
            // アニメーションが終了したら時間を元に戻す
            Time.timeScale = 1;
            // カメラの位置も戻す
            transform.position = initialPos;
        });
}

public void SampleC()
{
    Time.timeScale = 0;
    // 画面を強めに揺らす
    transform.DOShakePosition(1.0f, 0.8f, 30, 1)
        .SetUpdate(true)
        .OnComplete(() =>
        {
            transform.position = initialPos;
            // 画面をスローにする関数
            SlowMotion();
        });
}

void SlowMotion()
{
    Time.timeScale = 0.2f;
    // 3秒後にゲームオーバーの処理を実行
    DOVirtual.DelayedCall(3.0f, () => GameOver())
        .OnComplete(() =>
        {
            Time.timeScale = 1.0f;
        });
}

#22:ゲームオーバー画面

UnityのUIシステム(uGUI)を使用して、ゲームオーバー画面を作成します。
サムネイル#22
ボタンがクリックされた時の処理
[SerializeField] CanvasGroup groupRetry;

public void ButtonClick(string button)
{
    switch(button)
    {
        case "Retry":
            // 連打防止のためボタンを無効化
            groupRetry.interactable = false;
            // リトライ用の処理を実行
            Retry();
            break;
        case "Quit":
            groupRetry.interactable = false;
            // タイトル画面に戻る処理を実行
            Quit();
            break;
        default:
            break;
    }
}
UIのフェードイン・フェードアウト
using DG.Tweening;

------------------------

public void FadeInRetry()
{
    groupRetry.DOFade(1, 1.0f).SetUpdate(true)
        .OnComplete(() =>
        {
            // フェードイン完了後にボタンを有効にする
            groupRetry.blocksRaycasts = true;
            groupRetry.interactable = true;
        });
}

public void FadeOutRetry()
{
    groupRetry.DOFade(0, 0.5f)
        .OnComplete(() =>
        {
            groupRetry.blocksRaycasts = false;
        });
}

#23:キャラクターセレクト

ゲーム開始時に、使用するキャラクターを選択できるようにします。
サムネイル#23
CharacterSelect
[SerializeField] CanvasGroup groupTitle;
[SerializeField] CanvasGroup groupRetry;
[SerializeField] CanvasGroup groupSelect;

// 生成するキャラクターの番号
public int characterNum;

public void ButtonClick(string button)
{
    // SE再生
    SoundManager.instance.PlaySE();
    switch(button)
    {
        case "Play":
            // 連打防止
            groupTitle.interactable = false;
            // キャラクターセレクト画面をフェードイン
            FadeInSelect();
            break;
        case "Retry":
            // リトライ用の処理を実行
        case "Quit":
            // タイトル画面に戻る処理を実行
        case "Kohaku":
            characterNum = 0;
            groupSelect.interactable = false;
            // UIをフェードアウト+ゲーム開始
            GameStart();
            break;
        case "Misaki":
            characterNum = 1;
            groupSelect.interactable = false;
            GameStart();
            break;
        default:
            break;
    }
}
プレイヤーユニットの生成
[SerializeField] GameObject playerA;
[SerializeField] GameObject playerB;

public void GameStart()
{
    // クリックしたボタンのキャラクターを生成
    if (characterNum == 0)
    {
        Instantiate(playerA, playerA.transform.position, Quaternion.identity);
    } else {
        Instantiate(playerB, playerB.transform.position, Quaternion.identity);
    }
}

#24:イベントを取得して音声を再生する

Live2Dのアニメーションに設定されたイベントを取得して、音声を再生する仕組みを作成します。
サムネイル#24
イベントの取得(Live2D)
using UnityEngine;

-----------------------------

public class Live2dUserData : MonoBehaviour
{
    public void UserDataEventListener(string value)
    {
        // イベントに入力した内容に応じて分岐
        switch(value)
        {
            case "Voice1":
                // ボイス再生(戦闘開始!)
                SoundManager.instance.PlayVOICE(univ1220);
                break;
            case "Voice2":
                // ボイス再生(まだまだ!)
                SoundManager.instance.PlayVOICE(univ1230);
                break;
            ...省略...
        }
    }
}
CubismMotion3Json.cs
var animationEvent = new AnimationEvent
{
    time = UserData[i].Time,
    functionName = "UserDataEventListener",
    stringParameter = UserData[i].Value,
};

#25:プレイヤーの動きとLive2Dのパラメータをリンク

Live2Dの視線追従コンポーネントを使用して、プレイヤーの動きとLive2Dモデルの動きをリンクさせます。
サムネイル#25
ターゲットの位置を取得
using Live2D.Cubism.Framework.LookAt;
using UnityEngine;

-------------------------------------

public class Live2dTarget : MonoBehaviour, ICubismLookTarget
{
    // ターゲット
    GameObject playerUnit;

    Vector3 targetPosition;

    void Start()
    {
        playerUnit = GameObject.FindWithTag("Player");
    }

    public Vector3 GetPosition()
    {
        // プレイヤーが存在すれば
        if (playerUnit != null)
        {
            // 位置を取得する
            targetPosition = playerUnit.transform.position;
        } else {
            targetPosition = Vector3.zero;
        }
        return targetPosition;
    }

    public bool IsActive()
    {
        return true;
    }
}

#27:難易度の選択

難易度(EASY, NORMAL, HARD)を選択する機能を実装します。
サムネイル#27
難易度に応じてパラメータが変化
// 難易度を管理するスクリプト
[SerializeField] Manager manager;
// ヒットポイント
[SerializeField] int hp;

void Start()
{
    // 難易度に応じて耐久力を変化させる
    if (manager.difficulty == 1)
    {
        // EASYの場合は2分の1(小数点以下切り上げ)
        hp = Mathf.CeilToInt(hp * 0.5f);
    } else if (manager.difficulty == 3) {
        // HARDの場合は2倍
        hp = hp * 2;
    }
}

#29:撃ち返し弾

敵ユニットの撃破を条件に発生する「撃ち返し弾」を実装します。
サムネイル#29
発射角度をランダムにする
// 弾の移動速度
[SerializeField] float speed;
// ランダムな角度の範囲
[SerializeField] int minAngle;
[SerializeField] int maxAngle;

void Start()
{
    // 撃ち返し弾の速度にランダムな値を加算
    speed = speed + Random.Range(-0.5f, 0.5f);
    // 撃ち返し弾の角度をランダムにする
    transform.localRotationo = Quaternion.AngleAxis(Random.Range(minAngle, maxAngle), new Vector3(0, 0, 1));
}

タイトルとURLをコピーしました