首页 > 其他分享 >如何设计角色属性组件

如何设计角色属性组件

时间:2023-04-12 15:47:33浏览次数:43  
标签:10 角色 max Attack 组件 设计 1000 属性

目标 & 背景

本篇文章是对 ET1 中 NumericComponent 的介绍和补充,会围绕实际开发过程中可能会碰到的一些问题,给一个解题思路,并且会结合 Luban2 给出一个策划和程序都开心的方案

猫大曾经对 NumericComponent 做出过如下评论

单 NumericComponent 就可以完成 80% Moba 类游戏的设计了

属性组件作为游戏战斗中最底层的设计,拥有非常高的设计优先级,一个好的设计可以让你后续节省非常多的时间

下面我们一点点对此组件的设计进行解析,这里要注意,在不同 ET 版本中细节可能稍有不一致,但是整体设计思路都是通的,不一致的部分需要自行鉴别

NumericComponent 内部设计

数据结构

数据结构非常简单,就是一个 Map,key 是当前属性的槽位(NumericType),value 存放当前槽位的具体值

Dictionary<int, Long> dic;

NumericType

这个类型也就是上面说到的槽位,新版的 ET 将 NumericType 改为了 const 实现,这里要注意区分,我们以攻击力为例,最基础的槽位划分如下:

Attack = 1000,
AttackBase = Attack * 10 + 1,
AttackBaseAdd = Attack * 10 + 2,
AttackBasePct = Attack * 10 + 3,
AttackFinalAdd = Attack * 10 + 4,
AttackFinalPct = Attack * 10 + 5,

这里注意区分各个槽位值分配的规律,Attack 这个槽位是只读的,通常我们外部获取当前角色到底有多少攻击力,就是取 dic[1000] 中存放的值

如果你没有接触过这个设计,最开始理解时可能会有一定的困难和费解

而其他的槽位共同组成了 Attack,这里的组成方式需要根据自己项目策划具体的数值规划做相应的调整,假设此时我们希望更新 Attack 的值,其计算过程如下:

final = ((base + baseadd) * (1000 + basepct) / 1000 + finaladd)
* (1000 + finalpct) / 1000

理解这个计算过程非常重要,基于这个计算过程,我们将一个真实属性的值,分散在各个槽位上,简化了后续属性投放,接下来我们模拟一下真实场景,帮助理解这个过程

槽位 来源介绍
AttackBase 100 角色基础属性 100
AttackBaseAdd 20 队友加成 20
AttackBasePct 500 自己的 Buff 增加 50% 的攻击力
AttackFinalAdd 100 战场统一加成 100
AttackFinalPct -500 敌方减少 50% 攻击力 Buff

那么此时角色 最终攻击力 = ((100 + 20) * 1.5 + 100) * 0.5 也就是 140 点,此时我们假设,敌方减少 50% 攻击力的 Buff 到期了,只需要将 AttackFinalPct 回滚 500 即可,再次计算攻击力,我们可以得出结果为 280

基于这种设计,我们可以将属性投放简化至仅有加减法,极大降低了属性投放的设计成本,最终值 Attack 槽位与其他的都是 10 倍关系,所以可以应用到所有属性上,这样我们就几乎完成了所有属性的统一设计

实际开发中可能会遇到的问题

当前血量

当前血量这个槽位很特殊,他并没有 BasePct FinalPct 等槽位,ET 中也给了相应的设计,这里我们单独拎出来解释一下

  • Hp = 1000
  • HpBase = Hp * 10 + 1

此时当前血量仅有两个定义,HpHpBase,第一个槽位 Hp 就是当前血量的最终值,而 HpBase 因为没有定义其他槽位,所以可以粗暴的理解为也是最终值,但是所有属性的计算过程都是一致的,在我们更新 Hp 时,依然会获取 10001~10005 的所有值,只不过除了 HpBase 以外,其他的都是 0

限位

一般在属性投放上,策划会给不同槽位限制上下限,这里分两种情况

  • CD 被限制在 [0, 300] 之间
  • 当前血量 Hp,最小值为 0,但是最大值不能大于 MaxHp

这里我们就需要考虑设计两个 Attribute

[AttributeUsage(AttributeTargets.Field)]
public class MinMaxAttribute : Attribute
{
    public long min;
    public long max;

    public MinMaxAttribute(long min, long max)
    {
        this.min = min;
        this.max = max;
    }
}

[AttributeUsage(AttributeTargets.Field)]
internal class MinNTMaxAttribute : Attribute
{
    public long        min;
    public NumericType max;

    public MinNTMaxAttribute(long min, NumericType max)
    {
        this.min = min;
        this.max = max;
    }
}

此时,针对上面两种情况我们在 NumericType 中追加 Attribute 定义即可

[MinNTMax(0, MaxHp)]
Hp = 1000,
HpBase = Hp * 10 + 1,

[MinMax(0, long.MaxValue)]
MaxHp = 1001,
MaxHpBase = MaxHp * 10 + 1,
MaxHpBaseAdd = MaxHp * 10 + 1
// ...

[MinMax(0, 300)]
CD = 1002,
CDBase = CD * 10 + 1,
CDBaseAdd = CD * 10 + 2,
// ...

这样我们只需要在游戏初始化时,对 NumericType 枚举进行遍历,并存放所有枚举的 attr 定义,并插入到 final 值计算过程中,对其进行裁剪即可

内存加密

如果想要对属性组件进行内存加密,非常简单,只需要引入 值类型内存加密3 这个库,并对数据结构修改一下存放值即可

#if SERVER
Dictionary<int, Long> dic;
#else
Dictionary<int, EncryptLong> dic;
#endif

这里要注意的是,我们仅需要在客户端中对内存进行加密,而服务端并不需要,得益于 C# 的 operator 我们其余的代码可以保持不变

但是基于性能考虑,对加密值的修改最好还是使用 xxx.Set() 这个在 README 中有介绍,这里就不做展开了

Luban 配表设计

假设一件装备可以增加 血量、攻击力、防御力,一个天赋可以增加血量百分比,策划在实际设计属性投放时面对的情况是非常灵活的,我们并不希望策划改了一下属性投放,程序就需要跟着做调整,最终要实现不管是什么系统内的属性投放,开发都不需要关心策划到底投放了那些属性

此时我们需要引入一个新的对象 NumericKV,在 Beans.xlsx 文件中的定义参考如下:

full_name comment name type comment
NumericKV 数值键值对 keys (list#sep=| ),NumericType 属性槽
values (list#sep=| ),long 属性值

注意看这里的定义,keys 和 values 时两个对应的 list,这样对于程序增加属性的过程会很简单,一个 for 循环就能搞定所有属性的投放,但是!对于策划来说,填写的过程就比较痛苦了

此时我们就需要借助辅助列,通过 Excel 中的公式,找到策划和程序都开心的解决方案,这里我们需要使用 TEXTJOIN 这个函数

values 使用同一行的值,而 key 固定使用蓝色属性槽中的定义,这样策划只需要在辅助列中去做属性投放,而程序真正关心的只有 kv 这个对象

最后

ET 中 NumericComponent 的设计是非常优秀的,在实际项目中解决了非常多的问题,整体设计非常统一,做了上文的一些补充后,我们项目的属性组件几乎没有再改动过,即使面对非常复杂的属性修改稳定性也非常高

但是这个设计有一定的理解成本,初次接触可能会有些懵,但请一定耐心尝试去理解,同时对策划也要耐心讲解,很可能最开始策划对这个设计非常抵触,但当一切理顺之后,投入产出比是非常可观的

参考

  1. ET: https://github.com/egametang/ET

  2. Luban: https://github.com/focus-creative-games/luban

  3. 内存加密:https://github.com/LiuOcean/ValueTypes_Memory_Encrypt

标签:10,角色,max,Attack,组件,设计,1000,属性
From: https://www.cnblogs.com/LiuOcean-Blog/p/ru-he-she-ji-jiao-se-shu-xing-zu-jian.html

相关文章

  • C#属性(Attribute)用法实例解析
    属性(Attribute)是C#程序设计中非常重要的一个技术,应用范围广泛,用法灵活多变。本文就以实例形式分析了C#中属性的应用。具体如下:一、运用范围程序集,模块,类型(类,结构,枚举,接口,委托),字段,方法(含构造),方法,参数,方法返回值,属性(property),Attribute[AttributeUsage(AttributeTargets.All)]......
  • .NET 8新预览版本使用 Blazor 组件进行服务器端呈现
    简介此预览版添加了对使用Blazor组件进行服务器端呈现的初始支持。这是Blazor统一工作的开始,旨在使Blazor组件能够满足客户端和服务器端的所有WebUI需求。这是该功能的早期预览版,因此仍然受到一定限制,但我们的目标是无论选择如何构建应用,都能使用可重用的Blazor组件。......
  • vue属性之监听属性(watch)
    目录简介语法示例简介当一个变量的值发生变化时,执行对应的函数语法#在属性中添加watch属性,并以需要监听变量的名字进行定义函数data:{show:'abc'}watch:{show(){我是函数内容}}示例<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8">......
  • vue之组件
    目录简介全局组件语法示例局部组件语法示例组件间通信父子页面通信之父传子语法示例组件通信之子传父语法示例使用ref进行父子组件通信方法示例动态组件(component)普通方法实现使用组件component实现动态组件相关keep-alive组件之插槽(slot)匿名插槽具名插槽简介组件(component......
  • 利用hibernate分析数据库中的表,属性以及对应的类的类名,字段
    2010-08-1109:27hibernate获得数据库的表名列名及其数据@TestpublicvoidtestHIbernateConfig1(){SessionFactoryfactory=newAnnotationConfiguration().configure().buildSessionFactory();AbstractEntityPersisterclassMetadata=......
  • delphi 如何给自开发的组件设置图标?
    经过其他老师指点,自己摸索,发现如何实现,现说明如下,供大家参考。一、建立图标文件1、建立一个24X24的256色BMP格式文件。2、文件命名为该组件的名称。二、建立资源文件:Project→Resource and Images,点击Add将BMP格式图标文件加入,Resource Identifiler 设为组件的名称......
  • 52 openEuler搭建PostgreSQL数据库服务器-管理数据库角色
    52openEuler搭建PostgreSQL数据库服务器-管理数据库角色52.1创建角色可以使用CREATEROLE语句或createuser来创建角色。createuser是对CREATEROLE命令的封装,需要在shell界面执行,而不是在数据库界面。CREATEROLErolename[[WITH]option[...]];createuserrolename......
  • Vue动态创建组件实例并挂载到body
    方式一importVuefrom'vue'/***@paramComponent组件实例的选项对象*@paramprops组件实例中的prop*/exportfunctioncreate(Component,props){constcomp=new(Vue.extend(Component))({propsData:props}).$mount()document.body.appendChild(......
  • 如何判断一个对象的全部属性都是null
    根据反射机制获取对象的所有属性,然后立flag,判断每个属性是否都是nullFieldsfields=obj.getClass().getDeclaredFields;booleanflag=false;for(Fieldfield:fields){field.setAccessible(true);    if(field.get(obj)!=null&&StringUtils.isNotBlank(field.g......
  • Vue3 setup语法糖添加name属性
    1.安装插件vite-plugin-setup-extendnpmivite-plugin-setup-extend-D2.配置vite.config.tsimportvuefrom'@vitejs/plugin-vue'import{defineConfig}from'vite'//引入插件并使用importvueSetupExtendfrom'vite-plugin-vue-setup-extend�......