Week06_LevelUp - M-634/unity-game-dev-tutorial GitHub Wiki
- 敵を倒すと経験値アイテム(Gem)が出現
- プレイヤーが近づくと自動で吸収され、経験値が加算される
- 経験値がたまるとレベルアップ
- レベルに応じてスキル性能(発射間隔や速度)が強化される
- DOTweenを使って吸収演出をなめらかにする
DOTween は Unity 向けのアニメーション/Tween ライブラリで、
「移動・回転・色変更などをスムーズに実装できる」強力なツールです。
今回は経験値アイテムがプレイヤーに吸い寄せられる演出に使用します。
- 以下リンクからDOTweenのAssetをDownload
- Open UnityEditorとテキストが変わるので、クリックするとPackageManagerWindowがひらかられる
- DOTween (HOTween v2)項目でImport
- Unity メニューから
Tools > Demigiant > DOTween Utility Panel
を選択 - パネルが開いたら、右上の 「Setup DOTween...」 をクリック
- Unity が自動でライブラリを初期化・設定してくれます
🔸 パッケージ導入時に
Assets > Plugins > DOTween
フォルダが生成されます
🔸 もしメニューが表示されない場合は 公式サイト から再取得も可能
DOTween を使うスクリプトの先頭で、以下のように名前空間を追加してください。
using DG.Tweening;
transform.DOMove(_player.position, 0.5f).SetEase(Ease.InOutSine);
-
DOMove()
:オブジェクトを指定位置に一定時間で移動させる -
SetEase()
:加速や減速のカーブを設定(自然な動きになる)
🔗 参考リンク
-
Unityメニュー
Tools > Demigiant > DOTween Utility Panel
を開く -
「Setup DOTween...」を実行(初回のみ)
-
スクリプト冒頭に
using DG.Tweening;
を追記
using UnityEngine;
using DG.Tweening;
public class ExpPickup : MonoBehaviour
{
[SerializeField]
private int _expValue = 5;
[SerializeField, Range(0f, 50f)]
private float _distanceToMove = 10f;
[SerializeField, Range(0f, 5f)]
private float _moveTime = 0.3f;
private Transform _player;
private bool _isMoving;
private Tweener _moveTween;
private void Start()
{
_player = GameObject.FindGameObjectWithTag("Player").transform;
}
private void Update()
{
if (_isMoving || _player == null) return;
float distance = Vector2.Distance(transform.position, _player.position);
if (distance < _distanceToMove)
{
_isMoving = true;
StartFollowingPlayer();
Debug.Log($"{gameObject.name} start moving");
}
}
private void StartFollowingPlayer()
{
_moveTween = transform
.DOMove(_player.position, _moveTime)
.SetEase(Ease.Linear)
.SetLoops(-1, LoopType.Restart)
.OnUpdate(() =>
{
if (_moveTween.IsActive())
{
_moveTween.ChangeEndValue(_player.position, true);
}
});
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
if (_moveTween != null && _moveTween.IsActive())
{
_moveTween.Kill(); // Tweenを明示的に終了させる
}
if (PlayerLevelManager.Instance != null)
{
PlayerLevelManager.Instance.AddExp(_expValue);
}
Destroy(gameObject);
}
}
}
[RequireComponent(typeof(Rigidbody2D))]
public class EnemyHealth : MonoBehaviour
{
[SerializeField]
private int _maxHp = 100;
[SerializeField]
private SpriteRenderer _spriteRenderer;
[SerializeField]
private ExpPickup _expPickupItemPrefab;//ドロップアイテムPrefab
private int _currentHp;
private bool _isDead = false;
private void Start()
{
_currentHp = _maxHp;
}
public void OnDamaged(int damageAmount)
{
if(_isDead) return;
DamagePopup.Create(transform.position, damageAmount);
_currentHp -= damageAmount;
StartCoroutine(Flash());
if (_currentHp <= 0)
{
OnDead();
}
}
private void OnDead()
{
_isDead = true;
if (EnemyKillCounter.Instance != null)
{
EnemyKillCounter.Instance.AddKillCount();
}
//今回の追加部分
if (_expPickupItemPrefab != null)
{
Instantiate(_expPickupItemPrefab, transform.position, Quaternion.identity);
}
Destroy(gameObject);
}
private IEnumerator Flash()
{
var tempColor = _spriteRenderer.color;
_spriteRenderer.color = Color.red;
yield return new WaitForSeconds(0.2f);
_spriteRenderer.color = tempColor;
}
}
レベル情報はゲーム中1つだけ存在し、常にどこからでも参照される必要があります。
このようなケースに適しているのが Singleton(シングルトン)パターン です。
- インスタンスが1つだけ存在することを保証する仕組み
- 他のスクリプトから
PlayerLevelManager.Instance
のようにして簡単にアクセスできる - UIやスコア、ゲームマネージャーなどでもよく使われるパターン
using UnityEngine;
using TMPro;
public class PlayerLevelManager : MonoBehaviour
{
public static PlayerLevelManager Instance { get; private set; }
[SerializeField] private TextMeshProUGUI _levelText;
private int _level = 1;
private int _exp = 0;
private int _expToNext = 10;
public int CurrentLevel => _level;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject); // すでに存在している場合は削除
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // シーンを跨いでも消えないようにする(必要に応じて)
}
public void AddExp(int amount)
{
_exp += amount;
if (_exp >= _expToNext)
{
LevelUp();
}
UpdateUI();
}
private void LevelUp()
{
_level++;
_exp = 0;
_expToNext += 10;
Debug.Log("Level Up! 現在のレベル: " + _level);
}
private void UpdateUI()
{
if (_levelText != null)
{
_levelText.text = $"Lv. {_level}";
}
}
}
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "NewSkill", menuName = "Skills/AutoAttackSkill")]
public class AutoAttackSkillData : ScriptableObject
{
public string skillName;
public GameObject bulletPrefab;
public Vector2 direction;
public List<float> intervalPerLevels;
public List<float> speedPerLevels;
public float GetInterval(int level)
{
return intervalPerLevels[Mathf.Clamp(level - 1, 0, intervalPerLevels.Count - 1)];
}
public float GetSpeed(int level)
{
return speedPerLevels[Mathf.Clamp(level - 1, 0, speedPerLevels.Count - 1)];
}
}
using UnityEngine;
using System.Collections.Generic;
public class PlayerAutoAttack : MonoBehaviour
{
[SerializeField]
private List<AutoAttackSkillData> _skills = new();
[SerializeField]
private Transform _muzzle;
private List<float> _timers = new();
private PlayerLevelManager _playerLevelManager;
private void Start()
{
_playerLevelManager = PlayerLevelManager.Instance;
foreach (var _ in _skills)
{
_timers.Add(0f);
}
}
private void Update()
{
//PlayerLevelManager.Instanceがnullなら「level1」固定
int currentLevel = _playerLevelManager?.CurrentLevel ?? 1;
for (int i = 0; i < _skills.Count; i++)
{
_timers[i] += Time.deltaTime;
if (_timers[i] >= _skills[i].GetInterval(currentLevel))
{
Shoot(_skills[i], currentLevel);
_timers[i] = 0f;
}
}
}
private void Shoot(AutoAttackSkillData skill, int currentLevel)
{
GameObject bullet = Instantiate(skill.bulletPrefab, _muzzle.position, Quaternion.identity);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.velocity = skill.direction.normalized *skill.GetSpeed(currentLevel);
}
}
}
- 適当な画像からPrefabを作成する。名前は「ExpPickupItem」とか。
- そのPrefabにExpPickup.csをアタッチする。
- 当たり判定を付けるために画像に合うColliderコンポーネントをアタッチする。IsTriggerにチェックを入れることを忘れずに
- インスペクター上で値を設定。
- Enemy.prefabにアタッチされているEnemyHealth.csコンポーネントの「expPickupItemPrefab」にアタッチ
- ✅ 敵を倒すと宝石型の経験値アイテムが出現する
- ✅ プレイヤーが近づくと DOTween で吸い寄せられるように動く
- ✅ 接触すると経験値が加算され、アイテムは消える
- ✅ レベルアップ時に Console に「Level Up!」が出力される
- ✅ 発射間隔または弾速がレベルに応じて変化する
- ✅ 画面上部に現在の Lv が表示されている
- プレイヤーのレベルがわかるようにUIに表示してみよう。
- レベルアップ時にスキルの性能が変化するように自分なりに調整してみよう。
- プレイヤーの経験値テーブルを作成してレベルデザインをしてみよう。 L 現状次のレベルを上げるのに経験値5が固定で設定されているが、レベルが上昇するにつれて必要となる経験値を増やしてみる。 cf: 【ドラクエウォーク】経験値テーブル(特級職レベル80に対応)【DQウォーク】