【Unity】RPG2D龙城纷争(三)角色、角色数据集

news2024/10/7 8:21:45

更新日期:2024年6月18日。
项目源码:后续章节发布

索引

  • 简介
    • 角色数据集(RoleDataSet)
      • 一、定义角色数据集类
      • 二、角色基础数据(公共数据)
    • 角色(Role)
      • 一、定义角色类
      • 二、角色其他数据(专有数据)
      • 三、角色状态
      • 四、角色移动与停留
      • 五、角色攻击与被击
      • 六、角色剧情对话
      • 七、角色经验与升级
      • 六、角色死亡

简介

本章我们将实现角色类,角色类主要用以装载角色数据集,从而用以驱动角色(移动、剧情对话、攻击等),不过角色驱动逻辑的主场在寻路系统战斗系统模块,这里我们就不将跨度拉得太大了,以免闪了腰。

角色数据集(RoleDataSet)

在考虑角色之前,我们必须先考虑角色数据集(可以理解为角色的一些重要、公共数据的存储集)

试想一下,某一关敌方出现了20个士兵,这些士兵的基础属性需要我们每一个都为其单独定义吗?

肯定不能,这不科学!所以,对于士兵的一些公共数据,我们应当单独定义为角色数据集,然后给所有士兵使用相同的数据集即可。

一、定义角色数据集类

首先,我们定义角色数据集类RoleDataSet

    /// <summary>
    /// 角色数据集
    /// </summary>
    [Serializable]
    [CreateAssetMenu(menuName = "HTFramework/★ GameComponent/RPG2D/Role Asset", order = 300)]
    public sealed class RoleDataSet : DataSetBase
    {
        
    }

CreateAssetMenu使得他可以通过指定菜单路径创建。

二、角色基础数据(公共数据)

角色的基础数据(公共数据:不同的角色之间共用)我们决定采用六边形属性:速度、生命、攻击、防御、敏捷、会心

  • 速度:决定了角色每一次移动跑得有多远。
  • 生命:决定了角色是否活着;
  • 攻击:决定了角色攻击别人时产生的伤害值;
  • 防御:决定了角色被别人攻击时受到的伤害值;
  • 敏捷:决定了攻击别人时的命中率;
  • 会心:决定了攻击别人时的暴击率。
    public sealed class RoleDataSet : DataSetBase
    {
        /// <summary>
        /// 速度
        /// </summary>
        public int Speed;
        /// <summary>
        /// 生命
        /// </summary>
        public int HP;
        /// <summary>
        /// 攻击
        /// </summary>
        public int ATK;
        /// <summary>
        /// 防御
        /// </summary>
        public int DEF;
        /// <summary>
        /// 敏捷
        /// </summary>
        public int DEX;
        /// <summary>
        /// 会心
        /// </summary>
        public int CRI;
    }

不过,思索了2分钟后,我们发觉这样的基础属性好像拉不开等级差距啊,100级跟1级竟然是一样的生命值!打起架来不分上下!?

不行不行,上面这些只能算是初始属性,还得定义相应的随等级增加的属性才行(成长属性),唰唰唰敲完代码:

    public sealed class RoleDataSet : DataSetBase
    {
        /// <summary>
        /// 初始速度
        /// </summary>
        [Label("初始速度"), Drawer("初始属性", true)] public int BasicSpeed;
        /// <summary>
        /// 初始生命
        /// </summary>
        [Label("初始生命")] public int BasicHP;
        /// <summary>
        /// 初始攻击
        /// </summary>
        [Label("初始攻击")] public int BasicATK;
        /// <summary>
        /// 初始防御
        /// </summary>
        [Label("初始防御")] public int BasicDEF;
        /// <summary>
        /// 初始敏捷
        /// </summary>
        [Label("初始敏捷")] public int BasicDEX;
        /// <summary>
        /// 初始会心
        /// </summary>
        [Label("初始会心")] public int BasicCRI;
        /// <summary>
        /// 生命成长值
        /// </summary>
        [Label("生命成长值"), Drawer("属性成长值", true)] public int GrowthHP;
        /// <summary>
        /// 攻击成长值
        /// </summary>
        [Label("攻击成长值")] public int GrowthATK;
        /// <summary>
        /// 防御成长值
        /// </summary>
        [Label("防御成长值")] public int GrowthDEF;
        /// <summary>
        /// 敏捷成长值
        /// </summary>
        [Label("敏捷成长值")] public int GrowthDEX;
        /// <summary>
        /// 会心成长值
        /// </summary>
        [Label("会心成长值")] public int GrowthCRI;
    }

这下看起来合理了,我们将初始属性成长属性分开,使得不同类型的角色、不同等级的角色拥有了各自的属性成长曲线。

考虑到速度属性的独特性,比如速度也有成长的话,哪怕是最小值1,50级后也能一回合移动50格,这是要逆天啊!所以必须剥夺他的成长权利。

然后注意,使用Drawer特性可以将序列化字段分段整合进可折叠的抽屉中,大大提升了可读性,他的检视面板将是这样的:
在这里插入图片描述

角色(Role)

一、定义角色类

然后,我们定义角色类Role(很明显一个Role将持有一个RoleDataSet数据集):

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
        /// <summary>
		/// 角色数据集
		/// </summary>
		[Label("角色数据集"), SerializeField] internal RoleDataSet DataSet;
	}

二、角色其他数据(专有数据)

角色的其他数据(专有数据:每一个角色专有的)我们先定义如下这些:

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
		/// <summary>
		/// 头像顶部(一个位置标记,用以标记角色顶部,一些功能可能会用到,比如剧情对话、头顶飘字)
		/// </summary>
		[Label("头像顶部")] public Transform Top;
		/// <summary>
		/// 头像渲染器(角色在场景中显示为一个头像方块,这就是渲染器)
		/// </summary>
		[Label("头像渲染器")] public SpriteRenderer Head;
		/// <summary>
		/// 边框渲染器(角色头像的边框,用以区分阵营,比如青色为己方,红色为敌方)
		/// </summary>
		[Label("边框渲染器")] public SpriteRenderer Border;
		/// <summary>
		/// 阴影渲染器(角色移动时,身下显示的阴影,用以识别角色当前所在地块)
		/// </summary>
		[Label("阴影渲染器")] public SpriteRenderer Shadow;
		/// <summary>
		/// 角色头像(角色默认显示头像)
		/// </summary>
		[Label("角色头像")] public Sprite HeadImage;
		/// <summary>
		/// 角色头像(灰色)(角色禁用时显示头像,比如本回合已行动)
		/// </summary>
		[Label("角色头像(灰色)")] public Sprite HeadImage_Gray;
		/// <summary>
		/// 角色ID(角色唯一标识符,不能重复)
		/// </summary>
		[Label("角色ID")] public string ID;
		/// <summary>
		/// 角色姓名
		/// </summary>
		[Label("角色姓名")] public string Name;
		/// <summary>
		/// 角色等级
		/// </summary>
		[Label("角色等级")] public int Grade = 1;
		/// <summary>
		/// 角色阵营
		/// </summary>
		[Label("角色阵营")] public RoleCamp Camp = RoleCamp.Player;
	}
	
    /// <summary>
    /// 角色阵营
    /// </summary>
    public enum RoleCamp
    {
        /// <summary>
        /// 玩家
        /// </summary>
        Player = 0,
        /// <summary>
        /// 敌人
        /// </summary>
        Enemy = 1
    }

三、角色状态

然后考虑到某些角色一开始并不存在于场景中(可能存在但不可见),到达指定回合后才支援登场,所以再定义如下属性:

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
        /// <summary>
        /// 角色状态(还未登场的不可进行交互,活动中的可以进行交互,即便死亡的角色,也属于活动中)
        /// </summary>
        [Label("角色状态")] public RoleState State = RoleState.Actived;
        /// <summary>
        /// 登场回合(初始状态为NotYetOnStage的角色)
        /// </summary>
        [Label("登场回合")] public int ComeOnStageRound = 2;
        /// <summary>
        /// 登场地块(初始状态为NotYetOnStage的角色)
        /// </summary>
        [Label("登场地块")] public Block ComeOnStageBlock;
        /// <summary>
        /// 是否限制移动(如果为true,角色将无法移动,比如某些大BOSS,强大的实力不允许他们下场参战:你们这些喽啰尽管上前送死便是!)
        /// </summary>
        [Label("是否限制移动")] public bool IsRestrictMove = false;
	}

    /// <summary>
    /// 角色状态
    /// </summary>
    public enum RoleState
    {
        /// <summary>
        /// 还未登场
        /// </summary>
        NotYetOnStage = 0,
        /// <summary>
        /// 活动中
        /// </summary>
        Actived = 1
    }

四、角色移动与停留

很明显的是角色当前停留在哪个地块是一个重要的数据,即便我们还没有规划战斗系统寻路系统该怎么写,仅仅考虑到角色按地块移动、按地块距离攻击等逻辑,也必须知道角色当前在哪里(也即是角色坐标):

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
        /// <summary>
		/// 停留的地块
		/// </summary>
		[Label("停留的地块")] public Block StayBlock;
	}

不过,回想起上一篇的地块类(Block),我想如果我们拿到一个地块,也应该能够知道他上面是否站有角色(一个地块只能站一个角色)才对,这应当也是一个重要的数据,所以我们添加代码到Block:

    /// <summary>
    /// 地块
    /// </summary>
    [DisallowMultipleComponent]
    public class Block : HTBehaviour
    {
        /// <summary>
        /// 停留的角色
        /// </summary>
        [Label("停留的角色")] public Role StayRole;
    }

如此便将RoleBlock建立双向关联,无论我们拿到其中哪一个,都能进一步判断到角色当前的位置。

五、角色攻击与被击

经过深思,这里不再需要相关属性,角色数据集已完成所有定义。

六、角色剧情对话

经过深思,这里不再需要相关属性(因为剧情对话应该与关卡绑定,不属于任何一个角色)。

七、角色经验与升级

角色应当能够积累经验值,用以升级(至于如何升级,这也应当不是角色自身能管的,他只管积累经验,如何升级定然是交给升级系统):

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
        /// <summary>
		/// 拥有经验值
		/// </summary>
		[Label("拥有经验值")] public int HaveExp;
	}

六、角色死亡

我们来理一理角色的业务逻辑:

1.还未登场的时候是看不见的。

2.登场后,活着的时候可以:移动、攻击、剧情对话等。

3.如果死了(生命小于等于0),角色也将不可见,且不再执行活着时候的逻辑(也即是需要知道角色是否已经死亡)。

那么加入如下属性(这里定义为property,一是这些属性无需序列化,二是他们的值在改变时方便做出一些操作):

    [DisallowMultipleComponent]
    public class Role : HTBehaviour
    {
		/// <summary>
        /// 死亡音效(死亡时,将如何惨叫)
        /// </summary>
        [Label("死亡音效")] public AudioClip DeadAudio;

        /// <summary>
        /// 是否死亡
        /// </summary>
        [PropertyDisplay("是否死亡")]
        public bool IsDead
        {
            get
            {
                return _isDead;
            }
            private set
            {
                _isDead = value;
                if (_isDead)
                {
                		//死亡后,应当与站立的地块断开关联
                    if (StayBlock != null)
                    {
                        StayBlock.StayRole = null;
                        StayBlock = null;
                    }

					//且不再显示角色
                    IsShow = false;
                    //_isTriggerDeadEvent,在某些时候我们不想让角色死亡时触发事件和惨叫
                    //,比如重载存档,已经被打死的角色不可能再让他们全部惨叫一遍
                    if (_isTriggerDeadEvent)
                    {
                    	//如果可以惨叫,则惨叫
                        if (DeadAudio != null) Main.m_Audio.PlayOneShoot(DeadAudio);
                        //抛出角色死亡事件
                        Main.m_Event.Throw(Main.m_ReferencePool.Spawn<EventRoleDead>().Fill(LastOpponent, this));
                    }
                }
            }
        }
        /// <summary>
        /// 是否显示角色(未登场角色,死亡角色,都使用此属性隐藏自己)
        /// </summary>
        [PropertyDisplay("是否显示角色")]
        public bool IsShow
        {
            get
            {
                return gameObject.activeSelf;
            }
            set
            {
                gameObject.SetActive(value);
            }
        }
        /// <summary>
		/// 最后一次攻击自己的对手(让别人知道是谁kill了你,以免报仇时杀错人)
		/// </summary>
		public Role LastOpponent { get; set; }
	}

然后是一个简单的角色死亡事件定义:

    /// <summary>
    /// 角色死亡事件
    /// </summary>
    public sealed class EventRoleDead : EventHandlerBase
    {
        /// <summary>
        /// 击杀他的角色
        /// </summary>
        public Role Killer { get; private set; }
        /// <summary>
        /// 死亡的目标角色
        /// </summary>
        public Role Target { get; private set; }

        /// <summary>
        /// 填充数据,所有属性、字段的初始化工作可以在这里完成
        /// </summary>
        public EventRoleDead Fill(Role killer, Role target)
        {
            Killer = killer;
            Target = target;
            return this;
        }
        /// <summary>
        /// 重置引用,当被引用池回收时调用
        /// </summary>
        public override void Reset()
        {

        }
    }

需要注意的是,目前我们仅在数据结构设计阶段,先不用考虑这些数据如何赋值,我想我们后面总会实现他的。

不过,回想上一章的结尾,我们计划中准备写的回合制逻辑依然不知如何下手,好吧,正所谓计划赶不上变化,而且凡事不能一蹴而就,这俩理由足够给自己一个交代了,闪人便是。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1835594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python构造TCP三次握手、传输数据、四次挥手pcap数据包并打乱顺序

Python构造数据包&#xff0c;包含&#xff1a; TCP三次握手、 传输数据、 四次挥手 实现 随机乱序TCP数据包 from scapy.all import * from scapy.all import Ether, IP, TCP, UDP, wrpcap from abc import ABC, abstractmethod import random import dpkt from scapy.all…

全氟己酮自动灭火材料表现亮眼!手把手教你自动灭火毯的使用方法

灭火毯的使用方法是什么&#xff1f;很多朋友在购买灭火毯之前&#xff0c;都比较关心这个问题。在这里&#xff0c;我们可以把灭火毯分为两种。一种是传统灭火毯&#xff0c;还有一种是近年来兴起的高科技产品—全氟己酮自动灭火毯。这两种灭火毯的使用方法大有不同&#xff0…

图解ZGC

ZGC&#xff08;Z Garbage Collector&#xff09; 是一款性能比 G1 更加优秀的垃圾收集器。ZGC 第一次出现是在 JDK 11 中以实验性的特性引入&#xff0c;这也是 JDK 11 中最大的亮点。在 JDK 15 中 ZGC 不再是实验功能&#xff0c;可以正式投入生产使用了&#xff0c;使用 –X…

智能网站管理系统

智能网站管理系统&#xff0c;即智能化的网站管理工具&#xff0c;是为了提高网站管理效率和简化操作流程而开发的一种软件系统。它集合了各种先进的技术和功能&#xff0c;为网站管理员提供了一套强大而可靠的解决方案。 智能网站管理系统的核心功能是网站内容管理。传统的网站…

tqdm 进度可视化

下载安装包 pip install tqdmor conda install tqdm代码案例 from tqdm import tqdm # 直接传入参数 for i in tqdm([1s,2s,3s,4s]):print(i)# 结合range for i in tqdm(range(100)):print(i)应用 可视效果

idea intellij 2023打开微服务项目部分module未在左侧项目目录展示(如何重新自动加载所有maven项目model)

项目场景&#xff1a; springcloud微服务项目,部分模块暂时不需要用到&#xff0c;就在pom.xml文件中注释掉相应的模块&#xff0c;突然有一天打开项目&#xff0c;部分项目module 在idea intellij工具左侧文件夹找不到了&#xff0c;重新file->open本地项目也还是部分模块…

最短路径和最小生成树

一眼看&#xff0c;求最小生成树的 prim 算法和求单源最短路径的 dijkstra 算法非常像&#xff0c;事实上它们也确实是一回事&#xff0c;贪心策略&#xff0c;不同的是&#xff0c;dijkstra 算法每次加入一个到达源 S 最短的点&#xff0c;而 prim 则加入到达已生成 tree 最短…

【C语言】初阶指针

目录 Ⅰ、指针是什么&#xff1f; 总结&#xff1a; Ⅱ、指针和指针类型 1 .指针-整数 2.指针的解引用 Ⅲ、野指针 1 .野指针成因 2 如何规避野指针 Ⅳ、指针运算 1 .指针 - 整数 2. 指针 - 指针 3. 指针的关系运算 Ⅴ、指针和数组 Ⅵ、二级指针 Ⅶ、指针数组 指针 1. 指针是…

mysql下载安装教程(图文详细版)

如果一次没成功的话&#xff0c;就删掉重安&#xff08;前提是清理干净&#xff09;&#xff08;up就下了好几次&#xff0c;在错误中找到答案&#xff09; navicat(可视化工具)在其他文章里 一、mysql下载 进入官网地址https://www.mysql.com/downloads/ 然后就开始下载了&…

Git的3个主要区域

一般来说&#xff0c;日常使用只要记住下图6个命令&#xff0c;就可以了。但是熟练使用&#xff0c;恐怕要记住60&#xff5e;100个命令。 下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。 Workspace&#xff1a;工作区 Index / Stage&#xff1a;暂存区 Reposito…

Nginx 搭建 lnmp

一.编译安装Nginx 1.新建用户前期准备 官网下载nginx安装包 https://nginx.org/en/download.html yum -y install gcc pcre-devel openssl-devel zlib-devel openssl openssl-devel #安装依赖包 useradd -M -s /sbin/nologin nginx #新建nginx用户便于管理 2.切换到/opt…

idea插件开发之一起来开发个map转对象的插件吧!

写在前面 源码 。 在实际工作中&#xff0c;经常有这样的场景&#xff0c;从map中获取值来赋值到某个对象上&#xff0c;这无疑是一个重复的劳动&#xff0c;本文来尝试通过编写一个这样的插件&#xff0c;让插件来帮我们写代码&#xff0c;不管是有1个属性&#xff0c;还是有…

创维超充车辆交付仪式暨参观座谈会圆满举行

6月14日&#xff0c;创维超充车辆交付仪式暨参观座谈会在南京成功举行。苏舜集团副总经理程璟一行以及近多出行东部大区总经理张显春一行齐聚一堂。创维汽车总裁、联合创始人吴龙八等领导亲临现场&#xff0c;对各位尊贵嘉宾的到来表示热烈欢迎&#xff0c;并与众人共同见证了这…

RAM和ROM

1&#xff0c;RAM和ROM区别 RAM和ROM都是由来存储的&#xff0c;比如CPU缓存&#xff0c;电脑和手机内存等属于RAM,而固态硬盘&#xff0c;U盘&#xff0c;手机的128G,256G存储空间等都属于ROM。他们的最主要区别是RAM在断电后存储数据就没有了&#xff0c;而ROM在断电后存储数…

Java基础面试题自测

文章目录 一、Java 中有哪 8 种基本数据类型&#xff1f;说说这 8 种基本数据类型对应的包装类型&#xff1f;二、包装类型的常量池技术了解么&#xff1f;三、为什么要有包装类型&#xff1f;四、什么是自动拆装箱&#xff1f;原理&#xff1f;四、遇到过自动拆箱引发的 NPE 问…

Ps:脚本与动作

有三种脚本语言可用于编写 Photoshop 脚本&#xff1a;AppleScript&#xff08;macOS&#xff09;、JavaScript 和 VBScript&#xff08;Windows&#xff09;。 Photoshop 脚本文件默认文件夹 Win&#xff1a;C:\Program Files\Adobe\Adobe Photoshop 2024\Presets\Scripts Mac…

无线麦克风推荐哪些品牌?一文读懂家用无线麦克风哪个牌子好!

​在这个充满创意与表达的时代&#xff0c;无线领夹麦克风以其独特的魅力&#xff0c;成为了声音创作者们的得力助手。它小巧便携&#xff0c;功能强大&#xff0c;无论是日常拍摄、直播互动还是专业演出&#xff0c;都能轻松应对&#xff0c;让你的声音随时随地清晰传递。那么…

PIL保存后的图像莫名的失真,部分不失真部分很失真

原图片是这样的&#xff1a; PIL会自行**“自救”被正则化的图片&#xff0c;导致自救过曝&#xff0c;部分颜色非常失真&#xff0c;但是部分又保存的还行。现象如下&#xff1a; 这里你检查一下你保存的是不是被正则化的图片**&#xff0c;如果是&#xff0c;改改。 查看一…

长难句打卡6.17

At a time when Thomas Piketty and other economists are warning of rising inequality and the increasing power of inherited wealth, it is bizarre that wealthy aristocratic families should still be the symbolic heart of modern democratic states. 在托马斯皮凯…

基于Java的二手手机回收平台系统

开头语&#xff1a; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JavaJSPServlet 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展…