Unity教程(二十一)技能系统 基础部分

news/2025/2/21 6:45:13/

Unity开发2D类银河恶魔城游戏学习笔记

Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进

Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景

Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
Unity教程(十五)敌人战斗状态的实现
Unity教程(十六)敌人攻击状态的实现
Unity教程(十七)敌人战斗状态的完善

Unity教程(十八)战斗系统 攻击逻辑
Unity教程(十九)战斗系统 受击反馈
Unity教程(二十)战斗系统 角色反击

Unity教程(二十一)技能系统 基础部分


如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录


文章目录

  • Unity开发2D类银河恶魔城游戏学习笔记
  • 前言
  • 一、概述
  • 二、单例模式
  • 三、玩家管理器PlayerManager
  • 三、技能管理器SkillManager
  • 四、Skill基类
  • 五、创建Dash_Skill冲刺技能
  • 总结 完整代码
    • PlayerManager.cs
    • SkillManager.cs
    • Skill.cs
    • Dash_Skill.cs
    • Player.cs
    • SkeletonBattleState.cs
    • SkeletonGroundedState.cs


前言

本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。

本节实现角色技能系统基础部分。

Udemy课程地址

对应视频:
Concept of a Skill System
Creating Player Manager and Skill Manager
Foundation of Skill System


一、概述

本节开始进入技能系统的实现。

先使用单例模式创建玩家管理器PlayerManager和技能管理器SkillManager用于全局访问。

创建一个技能基类,所有技能都将在此基础上创建。

修改冲刺,将它重写为一个技能。

在这里插入图片描述

二、单例模式

在前面的章节中,许多类中都需要用到player。例如SkeletonBattle中,使用GameObject.Find会在所有对象中遍历查找,效率很低。
在这里插入图片描述
单例模式就可以用来解决这一点,它用来解决频繁创建和销毁全局使用的类实例的问题。
单例模式确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

Unity中单例模式实现方式多种多样,在此处我们使用继承于MonoBehaviour的写法,因为我们需要PlayerManager与Unity的组件系统交互。

单例的写法总结,可以参考以下文章:
Unity单例模式设计和应用
Unity单例模式写法总结
Unity单例有几种写法

unity中最基础的是

public class PlayerManager : MonoBehaviour
{public static PlayerManager instance;private void Awake(){instance = this;}
}

在教程中,为了确保只有一个单例,添加了一些处理:

public class PlayerManager : MonoBehaviour
{public static PlayerManager instance;private void Awake(){if(instance != null)Destroy(instance.gameObject);elseinstance = this;}
}

这样写是为了让运行后多余的PlayerManager被销毁只保留一个。但尝试之后发现由于Awake执行顺序,这导致最后可能无法完全销毁多余的物体。
我做了一些修改,由每次销毁原来的实例改为每次销毁新创建的,这样最后会正常地只剩一个PlayerManager

public class PlayerManager : MonoBehaviour
{public static PlayerManager instance;public Player player;private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}
}

三、玩家管理器PlayerManager

在开始之前我们先整理一下相机。
创建一个空项目命名为Camera,把Main Camera和Virtual Camera都挂在下面。
在这里插入图片描述
把实体特效脚本EntityFX拖到Scripts文件夹下
在这里插入图片描述

正式开始PlayerManager的创建。

在这里插入图片描述
使用单例模式创建PlayerManager,在其中添加变量player,这样就可以在全局以类名.instance.成员名的形式访问它了。

//PlayerManager:玩家管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerManager : MonoBehaviour
{public static PlayerManager instance;public Player player;private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}
}

替换其他脚本中查询获取player的部分。Ctrl+F搜索vs项目找到需要替换的地方。
SkeletonBattleState和SkeletonGroundedState中

    public override void Enter(){base.Enter();player = PlayerManager.instance.player.transform;}

创建空对象并命名为PlayerManager,将脚本PlayerManager挂载到下面。将目前的玩家对象Player赋予变量。

在这里插入图片描述
在这里我们测试一下,把PlayerManager复制几份并运行。
在这里插入图片描述
多余的PlayerManager会销毁,只剩一个。

三、技能管理器SkillManager

同PlayerManager类似,创建一个SkillManager脚本,再创建一个空物体命名为SkillManager,将脚本挂上去。

SkillManager同样使用单例模式,便于全局访问。

//SkillManager:玩家管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkillManager : MonoBehaviour
{public static SkillManager instance;private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}
}

四、Skill基类

我们创建一个Skill基类,所有技能都继承于它。

技能大都有冷却时间,我们把它写到Skill基类里。在基类中添加冷却时间cooldown,然后添加冷却时间计时器cooldownTimer。在Update函数中更新计时器。

    [SerializeField] protected float cooldown;protected float cooldownTimer;protected virtual void Update(){cooldownTimer -= Time.deltaTime;}

我们还需要创建一个CanUseSkill函数判断冷却时间是否结束,技能是否可用。
还需有一个实现技能具体功能的函数UseSkill,在技能可用时,调用UseSkill函数释放技能。

    public virtual bool CanUseSkill(){if(cooldownTimer< 0){UseSkill();cooldownTimer = cooldown;return true;}Debug.Log("Skill is on cooldown");return false;}public virtual void UseSkill(){//技能实现}

这里教程中的逻辑是,只要判断了技能是否可用,在可用时就立即释放并重置计时器。个人认为这里两部分分开写会比较好,但前面章节都是这个逻辑写的,就先按教程中的写吧。

五、创建Dash_Skill冲刺技能

前面的章节中我们已经实现了冲刺状态,现在我们可以把它改成技能。

创建Dash_Skill脚本,它继承自Skill基类,重写UseSkill函数。

//Dash_Skill:冲刺技能
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Dash_Skill : Skill
{public override void UseSkill(){base.UseSkill();}
}

把Dash_Skill脚本挂到SkillManager下

在这里插入图片描述
在SkillManager脚本中创建冲刺技能并赋值。

    public Dash_Skill dash { get; private set; }private void Start(){dash = GetComponent<Dash_Skill>();}

现在可以把冲刺状态实现时的冲刺冷却和计时器删掉修改成调用Dash_Skill了。
在Player的CheckForInput中

    [Header("Dash Info")]public float dashSpeed=25f;public float dashDuration=0.2f;public float dashDir { get; private set; }//检查冲刺输入public void CheckForDashInput(){if (Input.GetKeyDown(KeyCode.LeftShift) && SkillManager.instance.dash.CanUseSkill()){dashDir = Input.GetAxisRaw("Horizontal");if (dashDir == 0)dashDir = facingDir;StateMachine.ChangeState(dashState);}}

给冷却时间重新赋值
在这里插入图片描述
运行查看冲刺是否正常:
在这里插入图片描述

总结 完整代码

PlayerManager.cs

玩家管理器

//PlayerManager:玩家管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerManager : MonoBehaviour
{public static PlayerManager instance;public Player player;private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}
}

SkillManager.cs

技能管理器,创建冲刺技能

//SkillManager:玩家管理器
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkillManager : MonoBehaviour
{public static SkillManager instance;public Dash_Skill dash { get; private set; }private void Awake(){if (instance != null && instance != this){Destroy(this.gameObject);}else{instance = this;}}private void Start(){dash = GetComponent<Dash_Skill>();}
}

Skill.cs

技能基类,包含冷却时间和计时器,包含技能是否可用的判定和技能实现

//Skill:技能基类
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;public class Skill : MonoBehaviour
{[SerializeField] protected float cooldown;protected float cooldownTimer;protected virtual void Update(){cooldownTimer -= Time.deltaTime;}public virtual bool CanUseSkill(){if(cooldownTimer< 0){UseSkill();cooldownTimer = cooldown;return true;}Debug.Log("Skill is on cooldown");return false;}public virtual void UseSkill(){//技能实现}
}

Dash_Skill.cs

冲刺技能

//Dash_Skill:冲刺技能
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Dash_Skill : Skill
{public override void UseSkill(){base.UseSkill();}
}

Player.cs

删除冲刺冷却和计时器,修改冲刺条件

//Player:玩家
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Player : Entity
{[Header("Attack details")]public Vector2[] attackMovement;public float counterAttackDuration = 0.2f;public bool isBusy { get; private set; }[Header("Move Info")]public float moveSpeed = 8f;public float jumpForce = 12f;[Header("Dash Info")]public float dashSpeed=25f;public float dashDuration=0.2f;public float dashDir { get; private set; }#region 状态public PlayerStateMachine StateMachine { get; private set; }public PlayerIdleState idleState { get; private set; }public PlayerMoveState moveState { get; private set; }public PlayerJumpState jumpState { get; private set; }public PlayerAirState airState { get; private set; }public PlayerDashState dashState { get; private set; }public PlayerWallSlideState wallSlideState { get; private set; }public PlayerWallJumpState wallJumpState { get; private set; }public PlayerPrimaryAttackState primaryAttack { get; private set; }public PlayerCounterAttackState counterAttack { get; private set; }#endregion//创建对象protected override void Awake(){base.Awake();StateMachine = new PlayerStateMachine();idleState = new PlayerIdleState(StateMachine, this, "Idle");moveState = new PlayerMoveState(StateMachine, this, "Move");jumpState = new PlayerJumpState(StateMachine, this, "Jump");airState = new PlayerAirState(StateMachine, this, "Jump");dashState = new PlayerDashState(StateMachine, this, "Dash");wallSlideState = new PlayerWallSlideState(StateMachine, this, "WallSlide");wallJumpState = new PlayerWallJumpState(StateMachine, this, "Jump");primaryAttack = new PlayerPrimaryAttackState(StateMachine, this, "Attack");counterAttack = new PlayerCounterAttackState(StateMachine, this, "CounterAttack");}// 设置初始状态protected override void Start(){base.Start();StateMachine.Initialize(idleState);}// 更新protected override void Update(){base.Update();StateMachine.currentState.Update();CheckForDashInput();}public IEnumerator BusyFor(float _seconds){isBusy = true;yield return new WaitForSeconds(_seconds);isBusy = false;}//设置触发器public void AnimationTrigger() => StateMachine.currentState.AnimationFinishTrigger();//检查冲刺输入public void CheckForDashInput(){if (Input.GetKeyDown(KeyCode.LeftShift) && SkillManager.instance.dash.CanUseSkill()){dashDir = Input.GetAxisRaw("Horizontal");if (dashDir == 0)dashDir = facingDir;StateMachine.ChangeState(dashState);}}}

SkeletonBattleState.cs

修改查找Player的部分

//SkeletonBattleState:骷髅战斗状态
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonBattleState : EnemyState
{private Transform player;private Enemy_Skeleton enemy;private int moveDir;public SkeletonBattleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){this.enemy =_enemy;}public override void Enter(){base.Enter();player = PlayerManager.instance.player.transform;}public override void Exit(){base.Exit();}public override void Update(){base.Update();if (enemy.IsPlayerDetected()){stateTimer = enemy.battleTime;if (enemy.IsPlayerDetected().distance < enemy.attackDistance){if(CanAttack())stateMachine.ChangeState(enemy.attackState);}}else{if(stateTimer <0 || Vector2.Distance(enemy.transform.position,player.transform.position)>10)stateMachine.ChangeState(enemy.idleState);}if(player.position.x > enemy.transform.position.x)moveDir = 1;else if(player.position.x < enemy.transform.position.x)moveDir = -1;enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);}private bool CanAttack(){if (Time.time >= enemy.lastTimeAttacked + enemy.attackCoolDown)return true;return false;}
}

SkeletonGroundedState.cs

修改查找Player的部分

//SkeletonGroundedState:骷髅接地状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class SkeletonGroundedState : EnemyState
{protected Enemy_Skeleton enemy;protected Transform player;public SkeletonGroundedState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName){this.enemy=_enemy;}public override void Enter(){base.Enter();//获取Player的Transform组件player = PlayerManager.instance.player.transform;}public override void Exit(){base.Exit();}public override void Update(){base.Update();//转换到战斗状态if (enemy.IsPlayerDetected() || Vector2.Distance(enemy.transform.position,player.position) < 2)stateMachine.ChangeState(enemy.battleState);}
}

http://www.ppmy.cn/news/1573818.html

相关文章

【Linux】序列化、守护进程、应用层协议HTTP、Cookie和Session

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1、序列化和反序列化2、守护进程2.1 什么是进程组&#xff1f;2.2 什么是会话&#xff1f; 3、应用层协议HTTP3.1 HTTP协议3.2 HT…

Mentalab Explore Pro:第三代移动 EEG 设备,开启便携式脑电研究新时代

Mentalab推出的Explore Pro是一款专为研究和工业领域设计的第三代移动脑电图&#xff08;EEG&#xff09;设备。它凭借很高的精度和小巧的尺寸&#xff0c;为脑电研究提供了新的可能性。这款设备凭借强大的功能和灵活的设计&#xff0c;成为研究人员在各种实验环境中重要的工具…

人工智能之视频分割模型sam2源码解读

目前只提供了测试的,没训练的代码这一块。 &#xff08;1&#xff09;下载源码后按下面几步操作&#xff1a; 也许vs版本低一点也行吧,至此视频已经切成图片了。 (2)预测 它是先把视频分割成图像,然后第一帧先指定要分割的那些东西,然后它就是每帧去处理,最后输出分割后的图像…

新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)

视频教程和更多福利在我主页简介或专栏里 &#xff08;不懂都可以来问我 专栏找我哦&#xff09; 如果对你有帮助你可以来专栏找我&#xff0c;我可以无偿分享给你对你更有帮助的一些经验和资料哦 目录&#xff1a; 一、XSS的三种类型&#xff1a; 二、XSS攻击的危害&#x…

基于 Flask 与 MySQL 构建简单的博客系统

引言 在互联网时代&#xff0c;博客是人们分享知识、记录生活的重要平台。借助 Python 的 Flask 框架与 MySQL 数据库&#xff0c;我们可以快速搭建一个简单的博客系统。本文将详细介绍如何从零开始构建这样一个系统&#xff0c;涵盖环境搭建、数据库设计、后端接口实现以及前端…

前端web安全

一、黑盒扫描和白盒扫描 白盒扫描和黑盒扫描都是针对网络安全和应用程序安全的常用测试方法。 1、白盒扫描指的是测试人员具有关于系统内部结构和代码的全部或部分信息&#xff0c;是基于源代码的安全检测方法&#xff0c;它可以对源代码进行深度分析&#xff0c;从而发现潜在…

python解析url参数

python解析url参数 方法一&#xff1a;使用urllib.parse库中的parse_qs函数方法二&#xff1a;使用urllib.parse库中的parse_qsl函数方法三&#xff1a;使用urllib.parse库中的urlsplit函数和parse_qs函数 在Python中&#xff0c;可以使用urllib.parse库来解析URL参数 方法一&a…

罗格科技发布全球首款税务智能合规终端“罗拉DeepTax双引擎AI一体机”

——开启企业级税务智能管理新时代&#xff0c;迈入零部署、AI与场景深度融合新范式 罗格科技于2025年2月18日正式推出全球首款深度集成税务合规与强推理能力的“罗拉DeepTax双引擎AI一体机&#xff08;LoLR DeepTax Dual Engine AI Appliance&#xff09;”。该产品通过融合自…