Contents
- 【2DSTG制作】の動画内で表示されるコード:#1 – #30
- #3:自機狙い弾の追加
- #6:画面外からの敵弾
- #7:Live2Dのアニメーションと敵弾の発射位置を連動させる
- #9:Light2Dで光を照らす
- #12:スクリプトからLive2Dモデルの色を操作
- #14:Animatorでポストプロセスを操作
- #15:レーザーの実装
- #16:反射レーザー
- #17:追尾レーザー
- #18:追尾レーザー(同時に複数発射)・ヒットエフェクト
- #19:自機狙いの追尾レーザー
- #20:ヒットストップの実装
- #22:ゲームオーバー画面
- #23:キャラクターセレクト
- #24:イベントを取得して音声を再生する
- #25:プレイヤーの動きとLive2Dのパラメータをリンク
- #27:難易度の選択
- #29:撃ち返し弾
【2DSTG制作】の動画内で表示されるコード:#1 – #30
サムネイルをクリックすると、YouTubeの動画ページに移動します。
#3:自機狙い弾の追加
ShotCount
// 敵弾の発射した回数をカウントする。
private int count = 0;
// カウントした数÷5で余りが0となったら、敵弾の発射間隔を長くする。
if (count % 5 == 0) {
shotDelay = 0.3f;
} else {
shotDelay = 0.1f;
}
#6:画面外からの敵弾
Renderer
Renderer targetRenderer;
void Start()
{
targetRenderer = GetComponent<Renderer>();
}
void Update()
{
if (targetRenderer.isVisible) {
// 画面に表示されている時の処理
} else {
// 画面に表示されていない時の処理
}
}
#7:Live2Dのアニメーションと敵弾の発射位置を連動させる
Live2D:パーツに追従
[SerializeField] CubismRenderer _cubismRenderer;
void Update()
{
var mesh = _cubismRenderer.Mesh;
// メッシュを囲う矩形の中心
Vector3 meshPosition = mesh.bounds.center;
// メッシュの中心座標に追従
transform.position = meshPosition;
}
#9:Light2Dで光を照らす
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モデルの色を操作

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でポストプロセスを操作

Animatorの操作
Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
// 任意のタイミングでエフェクトを適用
animator.SetTrigger("Monochrome");
// 任意のタイミングでエフェクトを解除
animator.SetTrigger("Color");
#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:反射レーザー

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:追尾レーザー

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:追尾レーザー(同時に複数発射)・ヒットエフェクト

画面内の敵ユニットをランダムに取得
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:自機狙いの追尾レーザー
敵ユニットがプレイヤーをターゲットにして発射する追尾レーザーを実装します。
追尾性能を調整できるようにして、適切な難易度となるようなパラメータを探してみました。

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:ヒットストップの実装
プレイヤーが被弾したタイミングで画面を停止させる処理を実装します。

画面の停止+カメラの揺れ
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)を使用して、ゲームオーバー画面を作成します。

ボタンがクリックされた時の処理
[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:キャラクターセレクト
ゲーム開始時に、使用するキャラクターを選択できるようにします。

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のアニメーションに設定されたイベントを取得して、音声を再生する仕組みを作成します。

イベントの取得(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モデルの動きをリンクさせます。

ターゲットの位置を取得
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)を選択する機能を実装します。

難易度に応じてパラメータが変化
// 難易度を管理するスクリプト
[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:撃ち返し弾
敵ユニットの撃破を条件に発生する「撃ち返し弾」を実装します。

発射角度をランダムにする
// 弾の移動速度
[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));
}





