文章目录
人物素材
人物环境素材
https://game-endeavor.itch.io/
敌人素材
https://bdragon1727.itch.io/pixel-character-16x16
敌人
阴影
攻击特效
https://v-ktor.itch.io/pixelated-attackhit-animations
UI
https://mounirtohami.itch.io/pixel-art-gui-elements
物品
简单绘制环境
参考:【推荐100个unity插件之14】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)
环境按自己喜欢的绘制即可,效果
玩家状态机控制
参考:【unity实战】使用unity的新输入系统InputSystem+有限状态机设计一个玩家状态机控制——实现玩家的待机 移动 闪避 连击 受击 死亡状态切换
效果
虚拟相机跟随和区域限制
参考:【推荐100个unity插件之10】Unity最全的最详细的Cinemachine(虚拟相机系统)介绍,详细案例讲解,快速上手
效果
我希望虚拟摄像机跟随玩家,并且之后的每个场景,都能自动跟随玩家,我们就需要添加一个脚本给它里面
public class AutoSetupCamera : MonoBehaviour
{
private PlayerController player; // 玩家对象的引用
private CinemachineVirtualCamera cc; // Cinemachine虚拟摄像机的引用
private void Awake()
{
// 获取Cinemachine虚拟摄像机组件的引用
cc = GetComponent<CinemachineVirtualCamera>();
// 在场景中查找并获取Player类型的对象实例
player = FindObjectOfType<PlayerController>();
// 如果找到了Player对象,则设置摄像机跟随Player的目标
if (player != null)
{
cc.Follow = player.transform;
}
else
{
Debug.LogWarning("未找到Player对象");
}
}
}
有限状态机敌人AI
使用A星插件制作敌人自动寻路功能、避障,参考:【unity实战】Unity中使用A*寻路+有限状态机制作一个俯视角敌人AI
效果
树木排序问题
效果
脚步灰尘
探究
要实现走路灰尘效果,Unity的粒子系统(Particle System)中有属性RateOverDistance:根据移动距离发射粒子,不移动时不发射。恰好可满足当前需求
实际使用时发现,不管怎么移动都不发射粒子,但RateOverTime(随时间推移发射粒子)的功能是正常的
解决方案
粒子系统有一属性:EmitterVelocity(发射器速度模式),它有2种模式
Transform:通过Transform中Position的变化计算粒子系统的移动速度
Rigidbody:将刚体(若有)的速度作为粒子系统的移动速度
看了上述解释即可想到,若EmitterVelocity设置为Rigidbody模式,当该粒子系统没有刚体时,系统会认为该发射器是不动的,因此移动速度为0,因此移动距离为0:因此RateOverDistance不会发射粒子
所以将EmitterVelocity(发射器速度模式)设置为Transform(变换)即可
实现
素材图片
添加粒子效果
配置
效果
生命系统
新增Character属性基类,敌人和玩家甚至可破坏的物品只需要继承这个脚本,就不用再次编写一次生命系统的脚本,来实现相同代码的复用
public class Character : MonoBehaviour
{
[Header("生命值")]
public float maxHealth; // 最大生命值
protected float _currentHealth; // 当前生命值
//生命值修改器
public virtual float CurrentHealth{
get { return _currentHealth; }
set{ _currentHealth = value; }
}
[Header("无敌")]
public float invulnerableDuration; // 无敌持续时间
protected bool invulnerable; // 是否处于无敌状态
private void Start()
{
CurrentHealth = maxHealth;
}
// 受到伤害
public virtual void TakeDamage(Attack attack)
{
if (invulnerable) return; // 如果处于无敌状态,直接返回
SetHealth(-attack.damage);
Hit(attack);// 触发受伤逻辑
StartCoroutine(nameof(InvulnerableCoroutine)); // 启动无敌状态的协程
}
// 设置生命值
public virtual void SetHealth(float value)
{
CurrentHealth += value;
if (CurrentHealth >= maxHealth)//超过最大生命值
{
CurrentHealth = maxHealth;
}
if (CurrentHealth <= 0)// 小于等于0
{
CurrentHealth = 0;
Die(); // 触发死亡逻辑
}
}
// 受伤逻辑
protected virtual void Hit(Attack attack)
{
}
// 死亡逻辑
protected virtual void Die()
{
}
// 无敌状态的协程
private IEnumerator InvulnerableCoroutine()
{
invulnerable = true; // 设置为无敌状态
yield return new WaitForSeconds(invulnerableDuration); // 等待无敌持续时间
invulnerable = false; // 结束无敌状态
}
}
玩家生命系统脚本
public class PlayerCharacter : Character {
// 受伤逻辑
protected override void Hit(Attack attack)
{
GetComponent<PlayerController>().parameter.isHurt = true;
}
// 死亡逻辑
protected override void Die()
{
if(GetComponent<PlayerController>().parameter.isDead) return;
GetComponent<PlayerController>().parameter.isDead = true;
}
}
敌人生命系统脚本
public class EnemyCharacter : Character {
[HideInInspector] public Collider2D coll;
private void Awake() {
coll = GetComponent<Collider2D>();
}
// 受伤逻辑
protected override void Hit(Attack attack)
{
GetComponent<Enemy.FSM>().parameter.isHurt = true;
}
// 死亡逻辑
protected override void Die()
{
GetComponent<Enemy.FSM>().parameter.isDead = true;
//取消碰撞
coll.enabled = false;
Destroy(gameObject, 2f);
}
}
配置,玩家和敌人分别挂载对应的生命系统脚本,并配置对应参数
玩家敌人攻击
新增Attack攻击脚本
public class Attack : MonoBehaviour {
public int damage;
private void OnTriggerStay2D(Collider2D other)
{
other.GetComponent<Character>()?.TakeDamage(this);
}
}
玩家和敌人分别挂载对应的攻击脚本
配置敌人和玩家的不同攻击层
配置玩家只能攻击敌人二号可破坏物(后面会加),敌人只能攻击玩家
配置攻击动画的攻击区域
可以敌人死亡动画,透明度慢慢变为0,效果更好
效果,记得关闭敌人死亡动画循环播放
打击感
轻重攻击
可以给攻击动画配置不同的播放速度,以达到不同 轻重攻击效果
效果
时间管理器 时停效果
public class TimeManager : MonoBehaviour
{
// 单例模式
public static TimeManager Instance { get; private set; }
[Header("默认游戏时间缩放")]
[Range(0f, 2f)] public float defaultTimeScale = 1;
[Header("时间缩放程度")]
[SerializeField, Range(0f, 2f)] private float bulletTimeScale;
[Header("多久恢复到默认游戏时间")]
[SerializeField] private float timeRecoveryDuration;
private void Awake()
{
Instance = this;
Time.timeScale = defaultTimeScale;
}
// 进入减速模式
public void BulletTime()
{
Time.timeScale = bulletTimeScale;
StartCoroutine(nameof(TimeRecoveryCoroutine));
}
// 恢复到默认时间缩放的协程
IEnumerator TimeRecoveryCoroutine()
{
float ratio = 0f;
while (ratio < 1f)
{
ratio += Time.unscaledDeltaTime / timeRecoveryDuration;
// 插值时间缩放从减速时间缩放到默认时间缩放
Time.timeScale = Mathf.Lerp(bulletTimeScale, defaultTimeScale, ratio);
yield return null; // 等待下一帧继续执行
}
}
}
配置
调用,可以在敌人受伤时发起调用,达到短暂时停的效果
TimeManager.Instance.BulletTime();
效果,测试用的玩家受伤
击退
修改EnemyCharacter
[Header("击退")]
public float knokbackForce = 5f;//击退力
private Vector2 direction;//击退方向向量
// 受伤逻辑
protected override void Hit(Attack attack)
{
GetComponent<Enemy.FSM>().parameter.isHurt = true;
//击退
rb.velocity = Vector2.zero;
direction = (transform.position - attack.transform.parent.position).normalized;
rb.AddForce(direction * knokbackForce, ForceMode2D.Impulse);
}
效果
击中特效
敌人上配置特效
特效动画,播放100%自动退出
修改EnemyCharacter,代码控制
[Header("受击特效")]
private Animator hitAnimator; // 敌人的受击特效动画控制器
hitAnimator = transform.GetChild(0).GetComponent<Animator>();
// 播放受击特效动画
hitAnimator.SetTrigger("isHit");
效果
屏幕震动
玩家受伤可以发起屏幕震动,参考:unity实现简单的摄像机震动效果(包括普通摄像机和虚拟摄像机)
效果
敌人管理器控制敌人生成波次
public class EnemyManager : MonoBehaviour
{
// 单例模式
public static EnemyManager Instance { get; private set; }
[Header("刷新点")]
public Transform[] spawnPoints; // 敌人生成点数组
[Header("巡逻点数组")]
public Transform[] patrolPoints; // 敌人巡逻点数组
[Header("敌人波次")]
public List<EnemyWave> enemyWaves; // 敌人波次列表
private int currentWaveIndex = 0; // 当前波次索引
private int enemyCount = 0; // 敌人计数
// 判断是否为最后一波
public bool GetLastWave() => currentWaveIndex == enemyWaves.Count - 1;
private void Awake()
{
Instance = this;
}
private void Start()
{
StartCoroutine(nameof(startNextWaveCoroutine));
}
//杀死敌人方法
public void KillEnemy()
{
enemyCount--;
if (enemyCount == 0)// 敌人死亡
{
//开始下一波敌人
if (GetLastWave())
{
Debug.Log("游戏胜利!");
return;
}
currentWaveIndex++; // 当前波次索引增加
StartCoroutine(nameof(startNextWaveCoroutine));
}
}
// 开始下一波的协程
IEnumerator startNextWaveCoroutine()
{
List<EnemyData> enemies = enemyWaves[currentWaveIndex].enemies; // 获取当前波次对应的敌人列表
foreach (EnemyData enemyData in enemies)
{
for (int i = 0; i < enemyData.waveEnemyCount; i++)
{
enemyCount++;
// 实例化敌人预制体,并设置位置为随机的刷新点
GameObject enemy = Instantiate(enemyData.enemyPrefab, GetRandomSpawnPoint(), Quaternion.identity);
if (patrolPoints != null) // 如果巡逻点数组不为空,将巡逻点数组赋值给敌人的巡逻点数组
{
enemy.GetComponent<Enemy.FSM>().parameter.patrolPoints = patrolPoints;
}
yield return new WaitForSeconds(enemyData.spawnInterval); // 等待生成下一个敌人的间隔时间
}
}
}
// 获取随机刷新点
private Vector3 GetRandomSpawnPoint()
{
int randomIndex = Random.Range(0, spawnPoints.Length); // 随机选择一个刷新点的索引
return spawnPoints[randomIndex].position; // 返回随机刷新点的位置
}
}
// 由于没有继承MonoBehaviour,所以需要加上[System.Serializable]以在Unity编辑器中序列化
[System.Serializable]
public class EnemyData
{
public GameObject enemyPrefab; // 敌人预制体
public float spawnInterval; // 生成间隔
public float waveEnemyCount; // 波次敌人数量
}
[System.Serializable]
public class EnemyWave
{
public List<EnemyData> enemies; // 每波的敌人列表
}
简单配置两波敌人
敌人死亡时调用杀死敌人方法
EnemyManager.Instance.KillEnemy();
效果,可以看到第一波敌人被全部杀死,又生成下一波新的敌人,两波敌人杀完提示游戏胜利
玩家血条
简单绘制UI
使用事件渲染UI数据
这里涉及事件的知识,参数:【unity实战】事件(Event)的基本实战使用
新增PlayerEvents,控制玩家事件
//玩家事件
public class PlayerEvents
{
public static event Action<float, float> onUpdateHP;
//更新血条UI事件
public static void UpdateHP(float currentHealth, float maxHealth)
{
onUpdateHP?.Invoke(currentHealth, maxHealth);
}
}
新增PlayerHealthBar,控制玩家血条UI显示
public class PlayerHealthBar : MonoBehaviour
{
public Image HP; // 即时血量UI
public Image slowHP; // 缓冲血量UI
// 注册事件监听器
private void OnEnable()
{
PlayerEvents.onUpdateHP += UpdateHP;
}
// 注销事件监听器
private void OnDisable()
{
PlayerEvents.onUpdateHP -= UpdateHP;
}
// 更新血条UI事件
public void UpdateHP(float currentHP, float maxHP)
{
HP.fillAmount = currentHP / maxHP;
}
private void Update()
{
// 当缓冲血量大于即时血条时,缓冲血条持续减少
if (slowHP.fillAmount > HP.fillAmount)
{
// 缓冲血量持续减少,Time.daltaTime可换成其他时间参数,如0.01f
slowHP.fillAmount -= Time.deltaTime * 0.1f;
}
else
{
// 使缓冲血量与即时血量相等
slowHP.fillAmount = HP.fillAmount;
}
}
}
修改PlayerCharacter调用血条变化
// 重写生命值属性字段,更改生命值时同步更新玩家血条
public override float CurrentHealth{
get { return _currentHealth; }
set{
_currentHealth = value;
//更新玩家血条UI
PlayerEvents.UpdateHP(_currentHealth, maxHealth);
}
}
配置
效果
翻滚cd条
修改PlayerEvents
public static event Action<float> onUpdateDodgeCD;
//更新血条UI事件
public static void UpdateDodgeCD(float dodgeCooldown)
{
onUpdateDodgeCD?.Invoke(dodgeCooldown);
}
新增PlayerDodgeCDBar ,订阅事件
public class PlayerDodgeCDBar : MonoBehaviour
{
public Image CD;
float dodgeCD;
private void Start() {
CD.fillAmount = 1;
}
// 注册事件监听器
private void OnEnable()
{
PlayerEvents.onUpdateDodgeCD += UpdateDodgeCD;
}
// 注销事件监听器
private void OnDisable()
{
PlayerEvents.onUpdateDodgeCD -= UpdateDodgeCD;
}
//更新CD UI
public void UpdateDodgeCD(float dodgeCooldown)
{
CD.fillAmount = 0;
dodgeCD = dodgeCooldown;
}
private void Update()
{
if(CD.fillAmount == 1) return;
CD.fillAmount += Time.deltaTime / dodgeCD;
}
}
修改DodgeState,闪避时触发更新闪避cd UI
//更新闪避cd UI
PlayerEvents.UpdateDodgeCD(parameter.dodgeCooldown);
效果
伤害飘字
参考:
【推荐100个unity插件之2】DoTween动画插件的安装和使用整合(最全)
【unity造轮子】伤害飘字效果,封装代码+DoTween实现伤害飘字效果(2024/07/08补充)
效果
敌人血条
新增EnemyHealthBar ,控制敌人血条
//敌人血条
public class EnemyHealthBar : MonoBehaviour
{
public Image HP; // 即时血量UI
public Image slowHP; // 缓冲血量UI
private void Start() {
HP.fillAmount = 1;
gameObject.SetActive(false);
}
//更新血条UI
public void UpdateHP(float currentHP, float maxHP)
{
gameObject.SetActive(true);
HP.fillAmount = currentHP / maxHP;
}
private void Update()
{
// 当缓冲血量大于即时血条时,缓冲血条持续减少
if (slowHP.fillAmount > HP.fillAmount)
{
// 缓冲血量持续减少,Time.daltaTime可换成其他时间参数,如0.01f
slowHP.fillAmount -= Time.deltaTime;
}
if(slowHP.fillAmount < HP.fillAmount)
{
// 使缓冲血量与即时血量相等
slowHP.fillAmount = HP.fillAmount;
}
if(slowHP.fillAmount == 0){
gameObject.SetActive(false);
}
}
}
修改EnemyCharacter调用
// 重写生命值属性字段,更改生命值时同步更新敌人血条
public override float CurrentHealth{
get { return _currentHealth; }
set{
_currentHealth = value;
//更新血条UI
GetComponentInChildren<EnemyHealthBar>(true).UpdateHP(_currentHealth, maxHealth);
}
}
配置
效果
可破坏物品
新增爆炸粒子特效
新增DestructibleCharacter
//可破坏物品
public class DestructibleCharacter : Character
{
[Header("爆炸特效")]
public GameObject explosiveEffects ; // 敌人的受击特效动画控制器
// 死亡逻辑
protected override void Die()
{
Destroy(gameObject);
//爆炸特效
Instantiate(explosiveEffects, transform.position, Quaternion.identity);
}
}
配置
效果
随机掉落物品
新增ItemSpawner
using DG.Tweening;
using UnityEngine;
public class ItemSpawner : MonoBehaviour
{
public PropPrefab[] propPrefabs; // 存储不同种类的物品预制体数组
public float maxOffsetDistance = 1f; // 随机偏移的最大距离
// 初始化生成物品的方法
public void DropItems()
{
foreach (var propPrefab in propPrefabs)
{
if (Random.Range(0f, 100f) <= propPrefab.dropPercentage) // 根据掉落概率生成物品
{
//生成随机的数量
int Count = Random.Range(propPrefab.minCount, propPrefab.maxCount + 1);
for (int i = 0; i < Count; i++){
Instantiate(propPrefab.prefab, transform.position, Quaternion.identity);
}
}
}
}
}
[System.Serializable] // Unity 可以序列化该类以在编辑器中显示
public class PropPrefab
{
public GameObject prefab; // 物品的预制体
[Range(0f, 100f)] public float dropPercentage; // 掉落的概率范围 0% 到 100%
public int maxCount = 1;//生成最大数量
public int minCount = 1;//生成最小数量
}
配置
调用生成战利品,敌人和可破坏物品都可以调用
//生成掉落物品
GetComponent<ItemSpawner>().DropItems();
效果
敌人巡逻绕过可破坏物品
放置可破坏物品记得重新生成寻路网格
拾取物品
修改GameManager
private int _coinCount;//金币数
public int CoinCount{
get { return _coinCount; }
set{
_coinCount = value;
}
}
//改变金币数量
public void ChangeCoins(int amount)
{
if (CoinCount + amount < 0)
{
Debug.Log("金币不足");
return;
}
CoinCount += amount;
}
新增Item 物品拾取类
/// <summary>
/// 物品拾取类
/// </summary>
public class Item : MonoBehaviour
{
// 物品类型枚举
public enum ItemType
{
Coin, // 硬币
HealingPotion // 治疗药水
}
[Header("基本属性")]
[SerializeField] private ItemType itemType; // 物品类型
[SerializeField] private int value; // 物品价值
[Header("抛物线属性")]
[SerializeField] private float throwHeight = 1f; // 抛物线高度
[SerializeField] private float throwDuration = 1f; // 抛物时间
public float maxOffsetDistance = 1f; // 随机偏移的最大距离
[Header("拾取范围")]
[SerializeField] private float pickUpDistance = 3f; // 自动拾取距离
[SerializeField] private float moveSpeed = 5f; // 自动拾取速度
private bool canPickUp = false; // 是否可以拾取
private PlayerCharacter playerCharacter; // 玩家对象的引用
private void Awake()
{
playerCharacter = FindObjectOfType<PlayerCharacter>();
}
private void Start()
{
ThrowItem();
}
private void Update()
{
// 如果可以拾取并且玩家在拾取范围内,则向玩家位置移动
if (canPickUp && Vector2.Distance(transform.position, playerCharacter.transform.position) < pickUpDistance)
{
Vector2 dir = (playerCharacter.transform.position - transform.position).normalized;
transform.Translate(dir * moveSpeed * Time.deltaTime);
}
}
// 抛物线动画
private void ThrowItem()
{
// 使用DOJump方法实现物体的弹跳
Vector3 randomOffset = new Vector3(Random.Range(-maxOffsetDistance, maxOffsetDistance), Random.Range(-maxOffsetDistance, maxOffsetDistance), 0f);
transform.DOJump(transform.position + randomOffset, throwHeight, 1, throwDuration).SetEase(Ease.OutSine).OnComplete(() =>
{
canPickUp = true; // 动画完成后可以拾取
});
}
private void OnTriggerStay2D(Collider2D collision)
{
// 如果可以拾取并且碰撞对象是玩家
if (canPickUp && collision.gameObject.GetComponent<PlayerController>())
{
CollectPickup(); // 执行拾取逻辑
}
}
// 根据物品类型执行不同的拾取逻辑
private void CollectPickup()
{
switch (itemType)
{
case ItemType.Coin:
HandleCoinPickup(); // 执行硬币拾取逻辑
break;
case ItemType.HealingPotion:
HandleHealingPotionPickup(); // 执行治疗药水拾取逻辑
break;
}
Destroy(gameObject); // 拾取完成后销毁物品对象
}
// 处理硬币拾取逻辑
private void HandleCoinPickup()
{
GameManager.Instance.ChangeCoins(value); // 修改游戏中的硬币数量
// 在物品位置显示拾取文本效果
GameManager.Instance.ShowText("+" + value, transform.position, Color.yellow);
}
// 处理治疗药水拾取逻辑
private void HandleHealingPotionPickup()
{
playerCharacter.SetHealth(value);// 恢复玩家的生命值
}
}
效果
显示金币数
修改PlayerEvents,新增金币变化更新金币UI事件
public static event Action<int> onUpdateCoin;
//更新金币事件
public static void UpdateCoin(int count)
{
onUpdateCoin?.Invoke(count);
}
新增CoinUI
//金币UI显示
public class CoinUI : MonoBehaviour
{
public TextMeshProUGUI coinText;
// 注册事件监听器
private void OnEnable()
{
PlayerEvents.onUpdateCoin += UpdateCoin;
}
// 注销事件监听器
private void OnDisable()
{
PlayerEvents.onUpdateCoin -= UpdateCoin;
}
public void UpdateCoin(int CoinCount){
coinText.text = CoinCount.ToString();
}
}
修改GameManager,调用
private int _coinCount;//金币数
public int CoinCount{
get { return _coinCount; }
set{
_coinCount = value;
//更新金币文本
PlayerEvents.UpdateCoin(_coinCount);
}
}
private void Start() {
//初始化金币数
CoinCount = _coinCount;
}
配置
效果
添加更多敌人
新增动画器覆盖控制器即可,这里我加入新的骷髅怪
配置
效果
敌人全部死亡,生成传送门
修改EnemyManager
public GameObject portalPrefab; //传送门预制体
public void KillEnemy(Transform tf)
{
enemyCount--;
if (enemyCount == 0)// 敌人死亡
{
if (GetLastWave())
{
if (portalPrefab)
{
Debug.Log("通关");
//生成传送门
Instantiate(portalPrefab, tf.position, Quaternion.identity);
}
else
{
Debug.Log("游戏胜利");
}
return;
}
//开始下一波敌人
currentWaveIndex++; // 当前波次索引增加
StartCoroutine(nameof(startNextWaveCoroutine));
}
}
效果
场景切换
配置新场景Scene2,把需要的组件都迁移过来
回到场景1,新增退出场景脚本,挂载在传送门上
/// <summary>
/// 场景退出逻辑
/// </summary>
public class SceneExit : MonoBehaviour
{
[Tooltip("需要切换到的新场景名称")]
public string newSceneName;
public float timer = 1f; //多久生效
private Collider2D coll;
private void Start() {
coll = GetComponent<Collider2D>();
coll.enabled = false;
Invoke(nameof(SetCollider), timer);
}
void SetCollider(){
coll.enabled = true;
}
// 触发器碰撞检测
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
TransitionInternal();
}
}
// 设置内部切换逻辑
public void TransitionInternal()
{
SceneLoader.Instance.TransitionToScene(newSceneName);
}
}
加载新场景操作
public class SceneLoader : MonoBehaviour
{
public static SceneLoader Instance { get; private set; }
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else if (Instance != this)
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject);
}
public void TransitionToScene(string sceneName)
{
StartCoroutine(TransitionCoroutine(sceneName));
}
// 切换场景的协程
public IEnumerator TransitionCoroutine(string newSceneName)
{
// TODO加载前操作
// 异步加载新场景
yield return SceneManager.LoadSceneAsync(newSceneName);
// TODO加载完成操作
}
}
配置
效果
场景切换淡入淡出效果
新增画布组件,把没用的组件删除,添加CanvasGroup 组件,通过控制它的透明度控制场景当如淡出效果
记得还要在画布下添加一个占满屏幕的黑色图片
新增ScreenFader,使用DOTween实现淡入淡出效果
public class ScreenFader : MonoBehaviour
{
// 单例模式
public static ScreenFader Instance { get; private set; }
public CanvasGroup faderCanvasGroup; // 控制淡入淡出的CanvasGroup组件
public float fadeDuration = 1f; // 淡入淡出持续时间
private void Awake()
{
// 实现单例模式
if (Instance == null)
{
Instance = this;
}
else if (Instance != this)
{
Destroy(gameObject);
}
DontDestroyOnLoad(gameObject); // 切换场景时保持该对象不被销毁
}
// 淡入场景
public IEnumerator FadeSceneIn()
{
yield return StartCoroutine(Fade(0f, faderCanvasGroup));
// 将淡入遮罩的CanvasGroup对象禁用
faderCanvasGroup.gameObject.SetActive(false);
}
// 淡出场景
public IEnumerator FadeSceneOut()
{
// 启用淡出遮罩的CanvasGroup对象
faderCanvasGroup.gameObject.SetActive(true);
yield return StartCoroutine(Fade(1f, faderCanvasGroup));
}
// 淡入淡出实现
public IEnumerator Fade(float finalAlpha, CanvasGroup canvasGroup)
{
// 使用DOTween实现淡入淡出效果
yield return canvasGroup.DOFade(finalAlpha, fadeDuration).WaitForCompletion();
}
}
配置
修改SceneLoader调用淡入淡出效果
// 切换场景的协程
public IEnumerator TransitionCoroutine(string newSceneName)
{
// TODO加载前操作
// 淡出当前场景
yield return StartCoroutine(ScreenFader.Instance.FadeSceneOut());
// 异步加载新场景
yield return SceneManager.LoadSceneAsync(newSceneName);
// TODO加载完成操作
// 淡入新场景
yield return StartCoroutine(ScreenFader.Instance.FadeSceneIn());
}
效果
切换场景后,保存金币和玩家血量
修改GameManager
private void Start() {
CoinCount = _coinCount;
currentHealth = PlayerCharacter.Instance.maxHealth;
}
public void Save()
{
//保存玩家血量
currentHealth = PlayerCharacter.Instance.CurrentHealth;
}
public void Load()
{
//更新金币
CoinCount = _coinCount;
}
修改SceneLoader,调用
// 切换场景的协程
public IEnumerator TransitionCoroutine(string newSceneName)
{
// TODO加载前操作
GameManager.Instance.Save();
// 淡出当前场景
yield return StartCoroutine(ScreenFader.Instance.FadeSceneOut());
// 异步加载新场景
yield return SceneManager.LoadSceneAsync(newSceneName);
//清除 DOTween 库中当前正在进行的所有动画和补间
DOTween.Clear();
// TODO加载完成操作
GameManager.Instance.Load();
// 淡入新场景
yield return StartCoroutine(ScreenFader.Instance.FadeSceneIn());
}
修改PlayerCharacter,默认读取GameManager的生命值
public override void Start()
{
base.Start();
CurrentHealth = GameManager.Instance.currentHealth;
}
效果
持久化存储数据
音乐音效管理
有时候攻击不造成伤害的问题
可以把刚体碰撞检测设置为持续,提供检测的精度
设置从不休眠
源码
整理好我会放上来
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~