[技术交流]不要用海量表项压垮“技能流程”

本文深入剖析WoW技能流程,指出技能不应承载过多实体信息,而是作为流程触发器。讨论了技能表项的简化设计,强调了服务器端和客户端的协作,以及技能效果与复杂应用场景的处理技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很多时候我们会把游戏中的很多东西压给技能,让技能负重累累,比如这个技能打中后产生的Buff的效果,流血、怎么表现的流血、流多少血、流多久等等这些都存在于技能表项中。其实从一个表的表项你可以看出一种思路,就像两军交锋可以从扎营看出主讲的排兵布阵能力,我认为这样过于负重的技能表项体现出来的是一种对于技能本身的曲解——技能,说穿了只是一个流程,而不该是一个实体。我们就详细深入地拆解WoW的技能流程,并且进一步看如何引用到我们的游戏中去。

1,技能的基本流程,也就是技能的本身。
 


先看这个流程图,玩游戏的电脑上没有装Visio,于是就用小画家凑合了一下。这个流程中,绿色的部分代表客户端的,而红色的部分代表了服务器,其实不必太在意他是服务器还是客户端,只是既然我们用WoW的举例,就要还原出他的CS的特性。

1)[c]接收到释放技能的input,就是玩家通过按钮、宏等方式申请释放技能。

2)[c]技能准备阶段,是客户端先行的做法,客户端会预先播放一些准备的动作,当然很多距离的判断也会丢给客户端,虽然科学的说这不好,但是事实上这很有效很棒。在这个阶段如果是需网络的游戏,还需要做一个判定是否发送给服务器,频繁的发送是会轰爆服务器的。

3)判断技能条件是否满足,在WOW中包括蓝条、能量条等是否足够,距离是否OK,目标类型是否对路等等判定。

4)[c]如果技能无法释放,服务器告诉客户端后,客户端终止准备阶段的动作,并且提示错误。

5)[c]服务器和客户端的吟唱阶段,在WOW中很多技能会有吟唱阶段,比如术士的暗影箭就会进入两手冒黑光的阶段。瞬发技能可以跳过这个阶段,在这个阶段中可以设计打断类技能来终止流程等。

6)[c]吟唱完毕后2次检测条件是否满足。

7)技能正式准备释放,服务器告诉客户端可以播放后面的事件了,而自己则根据目标类型等因素获得对应的回调函数参数,并传递给下一个流程——也就是调用策划的脚本,来完成整个技能流程的使命。而这里对于通常理解的技能概念来说,只是一个开始。

8)[c]客户端收到可以释放的信标,则开始释放对应动作,并顺利完成这个技能的释放流程。

9)服务器调用策划对应的脚本函数,你可以看到这是进入了另外一个流程,在这里会有多少种结果,谁都不知道,连策划都不一定知道,因为想法总是在不断扩展的,加入我们抛开合理性看,一个技能能让目标直接下线也是应该要去做到的,因此需要提供出让角色下线的接口。而通常情况下,这里的功能大多是——产生一个AOEObj,买二手手机号码对目标产生BuffObj,直接生效另外一个流程。好吧,前面标上了中括号s就成了删除线了,所以干脆前面不标东西的就做服务器端的解释了。其实到这里为止,整个技能就算是完工了,因此技能只是一个流程,而对应于这个流程,策划需要设计的基本表项的DSL结构就是:

客户端

  1. type c_skill{
  2. id:Int; //这你一定用得着
  3. name:String;
  4. icon:String;
  5. targetType:Dynamic;//目标类型,实际上这个定义方式很多,所以这里就用dynamic偷个懒。
  6. prepareAction:Action; //我们将一组动作包含特效定义为一个Action,假如要通俗的举例,你可以理解为CCSequence,这里是准备阶段释放者的动作。
  7. castingAction:Action;//吟唱阶段
  8. releaseAction:Action;//释放阶段,值得注意的是,这里只是动画,一定要把持住这个概念。
  9. }

复制代码


服务器端

  1. type s_skill{
  2. id:Int;
  3. targetType:Dynamic;
  4. condition:*Character->[Bool];//我们把释放条件定义为一个函数,传入角色指针,返回布尔(满足否)
  5. effect:*Character->Dynamic->[Void];//这是技能释放的效果,除了传入释放者指针,Dynamic用于不同释放目标时采用不同类型的参数,但是这里有一点我要吐槽一下,除了写底层外,像我的这个地方这样写就是一个错误,逻辑代码中不推荐采用 xxx is xxx这种类型判断的写法,常用OOP的人一定会有这个概念问题。
  6. }

复制代码


以上我们可以看出,针对不同的环境,表项内容是不同的,但是都有ID作为连接标识。最关键的是,这样的表项,连10列都不到。

2,技能的效果与典型的极易产生混乱的用法。

在我们说到effect的时候,其实如果你细想下去,在应用的时候的确是会存在一些麻烦,加入你之前了解过我的AOE和Buff机制,这里反而更容产生一些误区,实际的例子之一就是:

我的技能仅限男性角色使用,效果是释放出一个白色的球体,命中目标后堆叠一层buff,当Buff层数达到一定层数时,若目标为女性,则会昏迷3秒(好吧,你要想歪也正常,谁让咱策划素质都很差呢?)

其实这是一段糟糕的技能描述,通常会在项目中带来合作问题,具体问题在——你的白色球体的作用描述不清,是的,乍一看说的很清楚啊,但事实上缺乏关键信息——他是一个什么样攻击范围的技能,他的生效时间是否很严谨?这里会产生的分歧——

1)如果是一个对单体的目标,那么你很在乎它的生效时间么?我是说在客户端表现下,因为延迟会产生一些小麻烦,这不是关键,关键是,如果他有飞行轨迹,并且飞的比较慢的时候,目标中招的位置很重要吗?确切的说,我“猜”很重要,因为会导致目标昏迷。事实上实现的时候这里的分歧在于,如果位置不重要,你可以直接作为另外一个函数来执行,而如果很重要就麻烦了。

2)如果目标受到技能影响的位置很重要,又引申出另外一个问题,他的范围是怎样的,我们知道LoL中小法师的Q技能,那个球如果你死命逃,最后逃不掉,但是会延晚你中弹的时间,虽然是很罕见的现象但这一定存在一个可能——原本我要死了,可是我跑得够快以至于奶上来了,我中弹了却活下来了。如果是这样的技能,很显然我们应该在游戏中设计一个BulletObj,当然这可以用Buff来实现,但如果你真的追求这么严密的效果,你还是应该产生一个子弹去追踪目标,然后将一些信息丢给子弹,当子弹命中后才执行效果函数。那么假如这个技能是一个指向性技能?

3)指向性技能又存在2个情况:如果是炸弹人的炸弹,其实是在你释放的时候会炸到哪儿我早就知道了,但是生效的时间会有延迟,而这个延迟我也知道,因此产生的是一个固定位置的AOEObj,而假如如同EZ、爱射的大,则我们需要一个移动的AOEObj。

这些问题的产生并不是刁难设计者,而是设计者本身并没想清楚这些细节,所以设计不出好技能。那么抛开这些,我们还有一个问题就是,这个技能的效果如何实现?好吧,你该回顾一下我说的Buff机制,我们得有一个白液的Buff,这个Buff在occur的回调中,判断当前层数和目标性别,决定是否上另外一个昏迷的buff就好了,非常简单。


其实你不难发现,真正成为一个你看起来很复杂或者很有趣技能的是buff机制和Aoe机制,而技能本身更象是一个Trigger,所以技能本身就只是一个Procedure,作为策划,首先要从概念上把你肉眼看到的,别人游戏中的技能拆成这样的碎片,你才能有效地组织并设计出更好的技能来。

 

<think>嗯,用户问的是关于仿RabbitMQ实现消息队列的目用了哪些技术。首先,我得先回想一下RabbitMQ的基本架构和核心功能。RabbitMQ是基于AMQP协议的,所以可能需要实现类似的协议或者至少支持消息的路由、队列、交换器等概念。 接下来,用户可能是一个开发者,想要自己动手实现一个简化版的消息队列,了解需要用哪些技术栈。这时候需要分层次来思考,比如网络通信、协议处理、持久化存储、高可用性、集群管理等等。 网络通信方面,RabbitMQ使用Erlang的OTP框架,但如果是仿照实现,可能会选择其他语言,比如Java的Netty或者Go的goroutine来处理高并发连接。需要确定目使用的编程语言,比如Java、Go或者Python,不同语言对应的技术选型会不同。 协议处理部分,AMQP是一个复杂的协议,仿制的话可能需要简化,或者选择实现一个自定义的二进制协议,或者使用HTTP/REST API,但这样可能不符合消息队列的高效要求。或者使用更简单的协议比如STOMP,或者直接基于TCP自定义协议。 存储方面,内存存储可能不够,需要持久化,所以可能会用到数据库,比如MySQL、PostgreSQL,或者更高效的存储系统如RocksDB、LevelDB。另外,消息的持久化可能需要考虑写入磁盘的方式,比如文件系统或者WAL(Write-Ahead Logging)。 高可用和集群方面,可能需要用到Raft或者Paxos共识算法来实现分布式一致性,或者使用ZooKeeper、etcd来做服务发现和配置管理。这部分比较复杂,如果是简化版可能暂时不考虑,但用户可能想知道如何扩展。 消息路由和交换器部分,需要实现不同的交换类型,比如直连、主题、扇出等,这涉及到路由算法的设计和数据结构的选择,比如哈希表、Trie树来高效匹配路由键。 消费者管理方面,需要处理消息的确认机制(ACK/NACK),可能还需要死信队列来处理失败的消息。这部分需要设计消息重发和超时机制。 监控和管理,可能需要提供管理界面或者API,用Web框架比如Spring Boot、Gin或者Echo来暴露管理接口,同时记录日志和指标,使用Prometheus和Grafana进行监控。 测试和性能优化,可能需要压测工具如JMeter,或者自己写基准测试,优化网络和存储性能,比如使用零拷贝技术、批量处理等。 然后,还要考虑目的范围,是单机版还是分布式,是否需要事务支持、优先级队列、延迟队列等高级功能。用户可能希望了解核心技术点,而不是所有细节,所以需要分模块列举,每个模块用到的技术栈。 可能的误区是用户可能混淆了消息队列的不同实现技术,比如Kafka和RabbitMQ的区别,但这里明确是仿RabbitMQ,所以需要强调AMQP相关技术和交换器、队列绑定等概念。 总结下来,技术选型可能包括:网络框架(Netty、Go net)、协议实现(自定义或简化AMQP)、存储引擎(RocksDB、文件系统)、集群协调(Raft、etcd)、路由算法、Web管理(Spring MVC、Gin)、监控(Prometheus)、测试工具等。需要分点说明,用中文清晰解释每个部分的作用和可选技术,同时给出可能的组合示例,比如Java+Netty+RocksDB+Raft,或者Go+goroutine+etcd+LevelDB等。</think>一个仿RabbitMQ实现消息队列的目通常会涉及以下核心技术模块(以典型Java技术栈为例): --- ### **1. 通信协议层** - **自定义TCP协议**:实现类AMQP的二进制协议帧解析 - **Netty框架**:处理高并发网络I/O,实现连接管理、编解码器 - **心跳机制**:通过`IdleStateHandler`实现连接保活检测 - **SSL/TLS**:可选实现安全通信 --- ### **2. 核心消息处理** - **Exchange路由引擎**: - 实现`direct/topic/fanout/headers`四种交换机类型 - 使用Trie树加速topic匹配(如`a.*.c`模式匹配) - **队列管理**: - 内存队列(ConcurrentLinkedQueue) - 持久化队列(基于RocksDB实现磁盘存储) - **消息确认**: - 实现ACK/NACK机制 - 死信队列(DLX)处理未确认消息 --- ### **3. 持久化存储** - **WAL日志**:通过顺序写日志(如Apache BookKeeper)保证消息不丢失 - **消息存储**: - 内存映射文件(MappedByteBuffer)加速磁盘读写 - 消息索引使用跳跃表(ConcurrentSkipListMap)维护 - **元数据存储**: - 使用嵌入式数据库H2存储队列/交换机绑定关系 - 或使用ZooKeeper实现分布式元数据管理 --- ### **4. 高可用设计** - **镜像队列**:基于Raft协议实现多节点数据同步 - **集群管理**: - 服务发现:集成Consul/Etcd - 负载均衡:一致性哈希算法分配队列 - **脑裂防护**:通过Quorum算法保证多数派写入 --- ### **5. 管理控制** - **HTTP API**:使用Spring Boot实现REST管理接口 - **监控系统**: - 埋点指标通过Micrometer对接Prometheus - 日志追踪集成ELK栈 - **Web控制台**:Vue+ElementUI实现管理界面 --- ### **6. 扩展功能** - **延迟队列**:基于时间轮算法(HashedWheelTimer)实现 - **事务消息**:两阶段提交(2PC)实现分布式事务 - **流控机制**:令牌桶算法实现QPS限制 --- ### **典型技术组合示例** ```java // 伪代码示例:Netty处理消息入队 public class MessageHandler extends ChannelInboundHandlerAdapter { private final PersistentQueue storage = RocksDBQueue.getInstance(); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { Message message = (Message) msg; if(message.isPersistent()) { storage.append(message); // 持久化存储 } MemoryQueueRegistry.getQueue(message.getRouteKey()) .offer(message); // 内存队列写入 } } ``` --- ### **实现难点** 1. 网络层背压控制(防止生产者压垮消费者) 2. 内存与磁盘数据的协同管理 3. 集群状态下的数据一致性保证 4. 百万级长连接管理优化 实际开发中建议参考RabbitMQ的[核心设计文档](https://www.rabbitmq.com/resources.html),先实现单机版再逐步扩展分布式能力。可以通过WireShark抓包分析AMQP协议交互流程,这对理解消息队列底层机制非常有帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值