暗黑破坏神词缀实现思路2.0

这篇具有很好参考价值的文章主要介绍了暗黑破坏神词缀实现思路2.0。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

代码示例

Github地址:暗黑破坏神词缀实现思路-示例代码

序言

暗黑类游戏非常经典,之前玩过很多,也尝试过写过实现的思路
最近又在之前的思路下有了新的想法。

我们先来分析下该类型游戏的特点和其词缀机制:

暗黑类游戏

我玩过的暗黑类游戏主要有:暗黑破坏神,火炬之光,流放之路。我认为暗黑类游戏的最突出的特点,就是各种各样的词缀,让玩家刷刷刷,按照自己的策略刷出合适的词缀搭配和提升其数值,从而获得割草和挑战更高数值怪物的快感。

词缀

词缀按照我的理解就是修饰器,它可以修饰(或覆盖)原本的各种机制(属性,技能,状态...),下面我们举几个有趣的例子:

  • 属性类:
    • 你的防御力为0,你的攻击力上升原本防御力的1.5倍
    • 你的防御力上升攻击力的10%
    • 你的火焰抗性等于冰冷抗性
    • 你的50%火焰攻击力转换成闪电攻击力
    • ...
  • 机制计算类:
    • 战斗机制:
      • 你不会被暴击
      • 你的伤害是幸运的(比如伤害是20-40,取值时靠近40的概率增加)
      • 你有50%概率避免中毒
      • 你受到的火焰伤害50%使用冰冷抗性抵抗
      • ...
    • 技能/Buff机制:
      • 施加冰缓时,若已被冰缓则施加冰冻
      • 对标记的目标造成额外伤害
      • 你的攻击技能有5%概率追加释放【虚空之雨】
      • ...
    • 地图机制:
      • 你在地图中受到【时空锁链】诅咒
      • 地图中包含一个额外宝箱
      • 你在地图中获得的金币翻倍
      • 地图中有【堕落的叛徒·乌崔德】
      • ...
    • 其他机制:
      • 你不能装备武器,你的攻击力翻倍
      • 你获得主动技能【猫之势】
      • 你可以选择其他职业的一个技能
      • 你从装备中获取的属性提升50%,但你只能装备被【腐化】的装备
      • ...

可以看到,词缀五花八门。有些词缀非属性类型的词缀比如(不会被暴击/50%避免中毒)也是可以通过属性或者状态来实现,但有些还需要其他机制处理(如标记追加伤害,需要在战斗模块进行处理)。

在暗黑类的众多词缀中,其中很多都是关联属性和状态的,而状态和属性在我的实现中比较像(后面会提到),所以这里详细说下我对属性模块和其修改器的实现思路,一些思想会应用于其他模块,并会简要的提出其他模块可能会不同的地方

我使用c++语言进行实现,其实思想都是一样的,使用lua/python等在编码效率等方面会更好些。

需求分析

从上述中,词缀影响到的机制非常的多。在实现时,可以选择更加灵活的语言(lua/python等)进行实现。配置方面,配表+脚本(一般使用配表,一些复杂的效果必要时调用脚本)是可行的,如果编写编辑器的话可能会更好一些(当然,程序侧的开发维护成本会增加,但如果游戏内容多的话,总体成本应当是下降的)。

结构示意图

暗黑破坏神词缀实现思路2.0
Entity下挂载了一组Comp组件,包含属性、状态等。装备、Buff等挂载一组Affix词缀,词缀又包含了一组修改器Modifier(可能有属性、状态、甚至是外貌、动作等修改器),修改器在应用的时候作用到各个组件的业务中(比如,属性修改器作用的属性组件的属性实例中,如增加攻击力)。若是使用观察者模式,则类似图中AttrBinder。外面把Binder注册进来,当属性变化时主动通知各个Binder属性变化

EC模块

在角色相关的系统中,EC模式(Entity-Component)是比较常见且好用的,它把(这里是角色,但是Entity不仅限是角色)Entity的各个业务拆分开来,降低代码的复杂度和耦合性。

这里有一个使用什么作为存component的key的问题,我考虑了三种方式:

  1. 使用枚举,如EComp::Attr
  2. 使用字符串, 如 "Attr"
  3. 使用RTTI(运行时类型信息 Run-time Type Information)生成的类的名字信息的字符串 typeid(Ty).name()

使用RTTI类名字符串

/*取类名String*/
#include <typeinfo> //注意头文件

struct ClassName
{
	template <typename Ty>
	static string Get()
	{
		static string name = typeid(Ty).name();
		return name;
	}
};

/*获取组件*/
template<class T>
std::shared_ptr<T> Entity::GetComp<T>()
{
	string name = ClassName::Get<T>();
	
	return std::dynamic_pointer_cast<T>(comp_map[name])
}

三种方式对比分析
2比较方便,代码量较少,但1更加规范尤其是多人合作项目推荐使用方式1。
方式3同2一样方便(在c++上其实比2更加方便),不像2那样容易出错(有代码检查和提示),但是不像枚举那样罗列了所有组件类型,且RTTI依赖编译器,不确定是否有些情况会有问题。
我总结了下原则:
在跨系统模块中,或者是动态生成的东西,使用字符串作为参数更加灵活和方便,其他情况使用枚举保证方便维护和合作

属性模块

如结构图示:

  1. 有一个属性组件AttrComp挂载在Entity上,管理了一堆属性Attr
  2. Attr可以接收Binder绑定器和Modifier修改器。当Modifier进来会重新收集所有Modifier的数据并计算,并通知Binder。需要说明的是:
    • 在我的设计中Attr没有所谓的默认值,如果角色天生带有一些基础属性,则由角色/职业相关组件添加Modifier进来
    • Binder的思想是观察者模式,Binder是在观察者的回调函数上进一步的封装,以减少重复的逻辑。比如多个面板有属性数值显示,就可以把获取属性数值,赋值给UI控件封装成一个Binder在多个面板上复用,只需传入控件和属性类型。也可以传入lambda表达式作为一般的回调使用,如这里的AttrBinderLambda。注意Binder在刚绑定时也会触发回调
  3. Affix词缀包含了多个Modifier,在Apply函数中应用到Entitt的各个模块中,如属性应用到AttrComp指定类型的属性Attr

应用实例
AttrData示例:

struct AttrData
{
	int fix = 0;
	int more = 0;
	int total = 0;
	int pct = 0;
	int override = 0;
	bool bOverride = false;
	int final = 0;
};

int raw = fix * (1 + more) * (1 + total) + (1 + pct);
int final = bOverride ? override : raw;

词缀效果应用:

  1. 你的攻击力:增加10(fix)/ 增加150%(more)/ 总增50%(total)
  2. 你的攻击力为0,你的防御力为上升原本攻击力的150%
    这里2应用BinderModifier的实现:
int AttrUtil::GetRawOverride(const AttrData& data)
{
    int tmp = GetRawPct(data);
    tmp *= (1 + data.pct / 100.f);
    return tmp;
}

int AttrUtil::GetRawPct(const AttrData& data)
{
    int tmp = 0;
    tmp += data.fix;
    tmp *= (1 + data.more / 100.f);
    tmp *= (1 + data.total / 100.f);
    return tmp;
}

void AttrModifyIncByAttr::Modify(AttrData& data)
{
	data.fix += v;
}

void AttrModifyIncByAttr::Init()
{
	auto func = [this](const AttrData& data)
	{
		if (target == from)
			return;
		int tmp = (AttrUtil::GetRawOverride(data)) * (pct / 100.f);
		SetVal(tmp);
	};

	bind = std::make_shared<AttrBinderLambda>(func);
}

void AttrModifyIncByAttr::Apply(const SP(Entity)& in_ent)
{
	if (in_ent)
	{
		auto comp = in_ent->GetComp<AttrComp>(EComp::Attr);
		if (comp)
		{
			comp->AddBinder(from, bind);
		}
	}
	else
	{
		if (auto lock = ent.lock())
		{
			auto comp = lock->GetComp<AttrComp>(EComp::Attr);
			if (comp)
			{
				comp->RemBinder(from, bind);
			}
		}
	}
	AttrModify::Apply(in_ent);
}

void AttrModify::SetVal(int in)
{
	if (v == in)
	return;
	v = in;
	Upd();
}

void AttrModify::Upd()
{
	if (auto lock = ent.lock())
	{
		auto comp = lock->GetComp<AttrComp>(GetCompTy());
		if (comp)
		{
			comp->UpdMod(target);
		}
	}
}

可以看到:这里在初始化时,创建了一个Binder,在回调时根据攻击力(from)计算修饰的值,SetVal时必要时会通知防御力属性(target)更新属性。
即:攻击力变化->修饰值变化->防御力变化。
诸如其他的属性词缀如一半的闪避值转化成攻击力,同理。
(注意这里防止转化之间的嵌套,比如攻击上升防御的一半,防御又上升攻击的一半,需要根据需求防止循环)

这里的设计主要是考虑复杂的需求和灵活:比如以后有什么获取所有装备提供的攻击力等需求可以快速的拓展。当然如果属性系统没有那么多花样,这里虽然能满足需求,但是在代码复杂度和效率上可能会差一些。

其他系统

多数的情况下,修改器都是更新数据(如属性、状态、标志位等),联动到更新这些数据对应的业务,也有一些是在后续的逻辑中查询这些数据(如战斗系统查询追伤标记位(有可能是某个buf)追加伤害)

状态系统
在我的设计中,状态系统管理的多数是Bool值,如:

  • 是否可以行动?
  • 是否可以释放技能?
  • 是否能够移动?

这些值往往使用乘法运算规则,如原本是可以行动,有个眩晕和封印技能同时添加状态修改器,即val = 1 * 0 * 0 = 0,值为0不能行动。

当然也有一些其他情况(如标记层数、中毒等)使用数字(Number)

战斗系统
在我的设计中,战斗系统和状态、属性系统是紧密关联的。
战斗系统会频繁的查改属性和状态。战斗系统主要负责战斗的流程处理和结算,并调用其他系统进行状态变更和表现处理。如调用伤害计算公式结算伤害,并修改属性系统HP值。文章来源地址https://www.toymoban.com/news/detail-693390.html

到了这里,关于暗黑破坏神词缀实现思路2.0的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【通义千问】大模型Qwen GitHub开源工程学习笔记(2)--使用Qwen进行推理的示例代码解析,及transformers的使用

    如希望使用Qwen-chat进行推理,所需要写的只是如下所示的数行代码。 请确保你使用的是最新代码,并指定正确的模型名称和路径,如 Qwen/Qwen-7B-Chat 和 Qwen/Qwen-14B-Chat 这里给出了一段代码

    2024年02月08日
    浏览(46)
  • 等保2.0下的安全管理中心建设思路及实践

    【摘要】 2019年5月13日,等保2.0“千呼万唤始出来”,此后,关于等保2.0的解读和探讨愈演愈烈。各大安全厂商围绕等保2.0跃跃欲试,其中围绕安全管理中心的建设纷纷布局,频频出招,安全市场上刮起一阵安全管理中心建设风潮。但是,为什么要建设安全管理中心、怎么建

    2024年02月05日
    浏览(32)
  • 基于微信小程序的垃圾分类系统设计与实现(2.0 版本,附前后端代码)

    博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W+、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 视频演示地址: 基于微信小程序的智能垃圾分类回收系统,可作为毕业设计 小程序页面及功能作如下设计: 1 .用户

    2024年02月12日
    浏览(66)
  • Android app的暗黑模式适配实现

    原文地址: Android app的暗黑模式适配实现 - Stars-One的杂货小窝 很久之前放在草稿箱的一篇简单笔记,是之前蓝奏云批量下载工具Android版本实现暗黑主题的适配记录 本文所说的这里的暗黑主题,应该只支持Android10系统,不过我手头的Flyme系统(Android9)上测试也有效果,其他低版本则没

    2024年02月05日
    浏览(48)
  • 成绩排序(思路+代码详解)Python实现

    目录 题目描述 输入 输出 样例输入 样例输出 题目分析:注意两点 思路分析: 代码详解: 给出n个学生的姓名和成绩,将学生成绩从高到低排序,成绩相同的学生,按照姓名拼音从小到大排序 输入包括多行,第一行是一个整数n(0n10),接下来有n行,每一行有学生姓名和两门课

    2024年02月12日
    浏览(29)
  • 大整数加法基本思路和代码(C++实现)

    如果给出两个很大很大的整数,这两个数大到long类型也装不下,比如100位整数,如何求它们的和呢? Java中有无敌的 BigInteger ,而C++只能眼气~ C/C++ 中的int 类型能表示的范围是 − 2 31 − 2 31 – 1 -2^{31}-2^{31} – 1 − 2 31 − 2 31 –1 unsigned 类型能表示的范围是 0 − 2 32 0-2^{32} 0 −

    2024年02月03日
    浏览(16)
  • C# WPF实现动画渐入暗黑明亮主题切换效果

    最近在Bilibili的桌面端看到一个黑白主题切换的效果感觉,挺有意思。于是我使用WPF尝试实现该效果。 主要的切换效果,基本实现不过还存在一些小瑕疵,比如字体等笔刷不能跟随动画进入进行切换。因为Bilibili的客户端是用electron写的,前端使用的html,css确实太强了,这咱只

    2024年02月16日
    浏览(44)
  • 带头节点的单链表的思路及代码实现

    单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) +指针(指示后继元素存储位置,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。) 以上是

    2023年04月08日
    浏览(35)
  • 四叉树图像模糊(C代码及实现思路)

    原创文章,参考文章见末尾,仅供学习交流使用,如果对你有帮助,请一键三连~ 代码如有需要会整理上传~ 能够正确的对图像建立四叉树; 对于输入的图像,四叉树能够输出模糊的结果 对颜色相近的区域进行模糊 背景知识理解 PPM文件格式理解 PPM 是通过RGB三种颜色显现的图

    2024年02月02日
    浏览(35)
  • 短按开机/长按关机的电路和代码实现思路

    在我们实际的项目中,我们做的设备或者自己的DIY一个东西的时候,经常要实现的一个功能是:通过一个按键实现短按开机,长按关机。下面我就给大家简单介绍一下其中一种的实现方法,包含电路和代码的实现 首先是电路图: 先给大家介绍一下上面几个网络标号的意义:

    2024年02月12日
    浏览(80)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包