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

更新日期: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.mfbz.cn/a/725209.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

警惕!新增4本SCI/SSCI被剔除!6月WOS更新(附下载)

本周投稿推荐 SSCI • 中科院2区&#xff0c;6.0-7.0&#xff08;录用友好&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.5-1.0&#xff08;录用…

【UE数字孪生学习笔记】 Apifox一体化接口测试平台

声明&#xff1a;部分内容来自于b站&#xff0c;知乎&#xff0c;慕课&#xff0c;公开课等的课件&#xff0c;仅供学习使用。如有问题&#xff0c;请联系删除。 部分内容来自UE官方文档&#xff0c;博客等 Apifox接口测试 Apifox 是集 API 文档、API 调试、API Mock、API 自动…

深度学习(十二)——神经网络:搭建小实战和Sequential的使用

一、torch.nn.Sequential代码栗子 官方文档&#xff1a;Sequential — PyTorch 2.0 documentation # Using Sequential to create a small model. When model is run, # input will first be passed to Conv2d(1,20,5). The output of # Conv2d(1,20,5) will be used as the in…

【unity笔记】二、海洋系统Crest Ocean System插件使用

一、介绍 Crest 是 Unity 技术先进的海洋系统。 它专为性能而设计&#xff0c;并大量使用细节级别 &#xff08;LOD&#xff09; 策略和 GPU 加速来实现快速更新和渲染。它还具有高度的灵活性&#xff0c;允许对水形状/泡沫/动态波浪/等进行任何自定义输入&#xff0c;并具有直…

基于Matlab的人脸表情识别系统(GUI界面)【W5】

简介&#xff1a; 该系统是一个基于Matlab开发的人脸表情识别应用程序&#xff0c;旨在识别输入图像中的人脸表情&#xff0c;并通过直观的图形用户界面&#xff08;GUI&#xff09;向用户展示识别结果。系统结合了图像处理、机器学习和用户交互技术&#xff0c;使用户能够轻松…

(论文翻译)ViM Out-Of-Distribution with Virtual-logit Matching(CVPR2022)

ViM: Out-Of-Distribution with Virtual-logit Matching&#xff08;CVPR2022&#xff09; 文章目录 ViM: Out-Of-Distribution with Virtual-logit Matching&#xff08;CVPR2022&#xff09;Abstract1.Introduction2.Related Work3.Motivation: The Missing Info in Logits4.…

SUSE linux的快照和恢复

snapper用于创建和管理文件系统快照&#xff0c;并在需要时实现回滚&#xff0c;它还可以用于创建用户数据的磁盘备份。snapper使用btrfs文件系统或者精简配置的被格式化成XFS或EXT4的LVM卷。snapper可以通过命令行或YaST来进行管理。 btrfs是一种copy-on-write文件系统&#x…

Linux网络命令:网络速度测试工具 speedtest-cli 详解

目录 一、概述 二、安装 speedtest-cli 1、在基于 Debian 的系统上安装 2、在基于 Red Hat 的系统上安装 三、命令语法 1、基本命令语法 2、查看帮助 3、常用选项 四、 speedtest-cli使用示例 1. 基本测试 2. 列出所有可用的服务器 3. 指定服务器进行测试 …

kotlin集合框架

1、集合框架的接口类型对比 2、不可变和可变List fun main() {// 不可变List - 不能删除或添加元素val intList: List<Int> listOf(1,2,3)intList.forEach{println(it) // 1 2 3}println("")// 可变List - 可以删除或添加元素val mutableList mutableListO…

【SpringBoot】RSA加密(非对称加密)

一、关于RSA RSA是一种非对称加密算法&#xff0c;广泛应用于数据加密和数字签名领域。 RSA算法是由罗纳德李维斯特&#xff08;Ron Rivest&#xff09;、阿迪萨莫尔&#xff08;Adi Shamir&#xff09;和伦纳德阿德曼&#xff08;Leonard Adleman&#xff09;在1977年提出的。…

单元测试很难么?

前言 你可能会用单元测试框架&#xff0c;python的unittest、pytest&#xff0c;Java的Junit、testNG等。 那么你会做单元测试么&#xff01;当然了&#xff0c;这有什么难的&#xff1f; test_demo.py def inc(x): return x 1 def test_answer(): assert inc(3) 4 i…

BLE-蓝牙广播

蓝牙广播&#xff1a;

KVB投资安全小知识:你知道情绪面、技术面与基本面的关系吗?

摘要&#xff1a;当涉及到金融市场分析时&#xff0c;情绪面、技术面和基本面是三个重要的方面。它们相互交织&#xff0c;共同影响着市场的走势和投资者的决策。下面我来详细解释它们之间的关系。 情绪面的影响 情绪面指的是投资者情绪和市场情绪&#xff0c;它反映了市场参与…

基于自编码器的滚动轴承异常检测方法(NASA-IMS数据,Python)

代码较为简单。 import numpy as np import pandas as pd from tensorflow import keras from tensorflow.keras import layers from matplotlib import pyplot as plt df_stats_Ch1_test2 pd.read_csv("estadisticos_test2_ch1.csv" , sep ,) X_Ch1 df_stats_Ch…

scratch编程03-反弹球

这篇文章和上一篇文章《scratch3编程02-使用克隆来编写小游戏》类似&#xff08;已经完全掌握了克隆的可以忽略这篇文章&#xff09;&#xff0c;两篇文章都使用到了克隆来编写一个小游戏&#xff0c;这篇文章与上篇文章不同的是&#xff0c;本体在进行克隆操作时&#xff0c;不…

Linux系统安装Ruby语言

Ruby是一种面向对象的脚本语言&#xff0c;由日本的计算机科学家松本行弘设计并开发&#xff0c;Ruby的设计哲学强调程序员的幸福感&#xff0c;致力于简化编程的复杂性&#xff0c;并提供一种既强大又易于使用的工具。其语法简洁优雅&#xff0c;易于阅读和书写&#xff0c;使…

FQC外检机使用Profibus转Modbus网关提升工作效率

一、简介 控制器通过Profibus转Modbus网关&#xff08;XD-MDPB100&#xff09;与视觉传感器实现通讯&#xff0c;在FQC外检机的应用为生产流程的自动化和优化提供了重要支持。在工业自动化领域&#xff0c;PLC常被用作控制器&#xff0c;通过采用在PLC与执行设备中间添加Profi…

WordPress模板推荐

WordPress外贸主题 wordpress跨境电商独立站主题&#xff0c;wordpress外贸建站模板。 手机配件wordpress外贸网站模板 充电器、移动电源、手机膜、手机电池、手机壳、手机转接头等手机配件wordpress外贸网站模板。 毛巾WordPress外贸主题 毛巾、面巾、婴童毛巾、浴巾、方巾、…

服务器SSH 免密码登录

1. 背景 为了服务器的安全着想&#xff0c;设置的服务器密钥非常长。但是这导致每次连接服务器都需要输入一长串的密码&#xff0c;把人折腾的很痛苦&#xff0c;所以我就在想&#xff0c;能不能在终端SSH的时候无需输入密码。 windows 可以使用 xshell 软件&#xff0c;会自…

得物面试:什么是零复制?说说 零复制 底层原理?(吊打面试官)

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的零复制的问题&#xff1a; 说一说Rocketmq、是如何实现每秒上百万数据的超…