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

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.rhkb.cn/news/23270.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker:Docker从入门到精通(一)- Docker简介

一、前言 通过本专栏的学习&#xff0c;我们将了解   1. 掌握Docker基础知识&#xff0c;能够理解Docker镜像与容器的概念   2. 完成Docker安装与启动   3. 掌握Docker镜像与容器相关命令   4. 掌握Tomcat Nginx 等软件的常用应用的安装   5. 掌握docker迁移与备份相…

单机上使用docker搭建minio集群

单机上使用docker搭建minio集群 1.集群安装1.1前提条件1.2步骤指南1.2.1安装 Docker 和 Docker Compose&#xff08;如果尚未安装&#xff09;1.2.2编写docker-compose文件1.2.3启动1.2.4访问 2.使用2.1 mc客户端安装2.2创建一个连接2.3简单使用下 这里在ubuntu上单机安装一个m…

Image Downloader下载文章图片的WordPress插件

源码介绍 一个用于下载图片的WordPress插件&#xff0c;包含下载统计功能&#xff0c;支持任何主题使用 用户点击下载后自动打包该文章所有原始图片&#xff0c;并把文章标题作为压缩包的文件名。 不占用服务器空间&#xff0c;也不占网盘空间&#xff0c;直接利用浏览器的性…

PLC通讯

PPI通讯 是西门子公司专为s7-200系列plc开发的通讯协议。内置于s7-200 CPU中。PPI协议物理上基于RS-485口&#xff0c;通过屏蔽双绞线就可以实现PPI通讯。PPI协议是一种主-从协议。主站设备发送要求到从站设备&#xff0c;从站设备响应&#xff0c;从站不能主动发出信息。主站…

VScode+stfp插件,实现文件远程同步保存【2025实操有效】

目录 1 痛点2 准备工作3 操作步骤3.1 第一步&#xff0c;下载STFP插件3.2 第二步&#xff0c;修改配置文件3.3 第三步&#xff0c;测试是否成功 4 后记 1 痛点 我一直用vscode远程连接服务器&#xff0c;传代码文件等到服务器上面&#xff0c;突然有一次服务器那边尽心维修&am…

【quicker】调节PPT指定字号字体大小/快速调节WPS的PPT字体大小

在quicker的拓展动作中找不到直接指定字号大小方式的动作。 换个思路&#xff0c;既然无法通过alt键模拟&#xff0c;不如模拟右键菜单触发&#xff1f;尝试过失败了 所以有了第三种方法 &#xff0c;首先给字体窗口设置快捷键&#xff0c;此处设置的是altshiftf&#xff0c;然…

Grouped-Query Attention(GQA)详解: Pytorch实现

Grouped-Query Attention&#xff08;GQA&#xff09;详解 Grouped-Query Attention&#xff08;GQA&#xff09; 是 Multi-Query Attention&#xff08;MQA&#xff09; 的改进版&#xff0c;它通过在 多个查询头&#xff08;Query Heads&#xff09;之间共享 Key 和 Value&am…

百度百舸 DeepSeek 一体机发布,支持昆仑芯 P800 单机 8 卡满血版开箱即用

在私有云环境中成功部署 DeepSeek 满血版并实现性能调优&#xff0c;并不是一件容易的事情。选择合适的 GPU 配置、安装相应的环境、成功部署上线业务、加速推理任务加速、支撑多用户并发 …… 完成业务测试&#xff0c;成功融入生产业务中。 为了帮助企业快速实现 DeepSeek 服…

c++入门-------命名空间、缺省参数、函数重载

C系列 文章目录 C系列前言一、命名空间二、缺省参数2.1、缺省参数概念2.2、 缺省参数分类2.2.1、全缺省参数2.2.2、半缺省参数 2.3、缺省参数的特点 三、函数重载3.1、函数重载概念3.2、构成函数重载的条件3.2.1、参数类型不同3.2.2、参数个数不同3.2.3、参数类型顺序不同 前言…

tortoiseGit的使用和上传拉取

tortoiseGit的使用和上传拉取 下载TortoiseGit 通过网盘分享的文件&#xff1a;tortoiseGit.zip 链接: https://pan.baidu.com/s/1EOT_UsM9_OysRqXa8gES4A?pwd1234 提取码: 1234 在电脑桌面新建文件夹并进入 右击鼠标 将网址复制上去 用户名和密码是在git注册的用户名和…

Mybatis学习总结

官网 概念 用于简化JDBC的开发。 在配置mybatis的时候如果没有建立连接识别不了信息&#xff0c;我们需要在idea配置mysql的配置信息 JDBC是一套操作关系数据库的API&#xff0c;有效率&#xff0c;和mybatis比起来资源节约&#xff0c;性能高&#xff0c;不繁琐。 数据库连…

SQL笔记#数据更新

一、数据的插入(INSERT语句的使用方法) 1、什么是INSERT 首先通过CREATE TABLE语句创建表&#xff0c;但创建的表中没有数据&#xff1b;再通过INSERT语句向表中插入数据。 --创建表ProductIns CREATE TABLE ProductIns (product_id CHAR(4) NOT NULL,product_name …

dockerfile构建haproxy

1. 结构目录 [rootlocalhost ~]# tree haproxy/ haproxy/ ├── dockerfile └── files├── haproxy-2.5.0.tar.gz├── haproxy.cfg├── install.sh└── start.sh1 directory, 5 files [rootlocalhost ~]# [rootlocalhost ~]# cd haproxy/ [rootlocalhost haproxy]…

Docker(Nginx)部署Vue

简介&#xff1a;目标使用docker将vue生成的dist文件&#xff0c;结合nginx生成镜像&#xff0c;然后运行&#xff1b; 1、首选确保vue项目正确运行&#xff0c;并能正确打包dist文件&#xff1b; 2、查看已经生成的dist文件 3、将dist文件打包为rar文件或者zip文件&#xf…

C++——模版(二)

前言 我们前面讲过模版的一&#xff0c;不知道大家还有没有所印象&#xff0c;如果大家不太能回忆起来可以再去前面看一下&#xff0c;那通过我们讲解了几个容器之后&#xff0c;相信大家现在应该已经对模版很熟悉了&#xff0c;那模版还剩下一些其他的内容我们就在这里进行讲…

算法与数据结构(旋转链表)

题目 思路 每个节点向右移动k个位置&#xff0c;其实就是从头开始遍历&#xff0c;将n-k个节点顺序插入到链表的尾部。 如上图所示的示例1&#xff0c;先将1插入到5的后面&#xff0c;再将2插入到1的后面&#xff0c;最后将3插入到2的后面即可。 代码详解 定义一个cur变量用…

TOGAF之架构标准规范-信息系统架构 | 应用架构

TOGAF是工业级的企业架构标准规范&#xff0c;信息系统架构阶段是由数据架构阶段以及应用架构阶段构成&#xff0c;本文主要描述信息系统架构阶段中的应用架构阶段。 如上所示&#xff0c;信息系统架构&#xff08;Information Systems Architectures&#xff09;在TOGAF标准规…

智能优化算法:莲花算法(Lotus flower algorithm,LFA)介绍,提供MATLAB代码

一、 莲花算法 1.1 算法原理 莲花算法&#xff08;Lotus flower algorithm&#xff0c;LFA&#xff09;是一种受自然启发的优化算法&#xff0c;其灵感来源于莲花的自清洁特性和授粉过程。莲花的自清洁特性&#xff0c;即所谓的“莲花效应”&#xff0c;是由其叶片表面的微纳…

CSS 媒体查询:从入门到精通,打造跨设备完美体验

在当今移动互联网时代&#xff0c;用户访问网站的设备早已不再局限于桌面电脑&#xff0c;手机、平板等各种屏幕尺寸的设备层出不穷。为了确保用户在不同设备上都能获得良好的浏览体验&#xff0c;响应式网页设计应运而生。而 CSS 媒体查询&#xff0c;正是实现响应式设计的核心…

【Python LeetCode 专题】树

LeetCode 题目104. 二叉树的最大深度(gif 图解)方法一:后序遍历(DFS)方法二:层序遍历(BFS)872. 叶子相似的树(DFS 遍历)1448. 统计二叉树中好节点的数目(DFS 遍历)437. 路径总和 III(前缀和 + DFS 回溯)1372. 二叉树中的最长交错路径(DFS)236. 二叉树的最近公共…