博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
一种手游中实时战斗系统的设计思路
阅读量:7000 次
发布时间:2019-06-27

本文共 4244 字,大约阅读时间需要 14 分钟。

hot3.png

  • 引言

现在的手游玩法越来越复杂,特别是战斗系统,再也不是以前那种简单的回合制模式。越来越多的手游采用了实时战斗的模式(如刀塔传奇),玩法有点类似于以前的即时战略游戏,这对于程序设计提出了更高的要求。本文提出了一种手游中实时战斗系统可行的设计思路。

  • 设计需求

实时战斗,不同于早期页游和手游单纯的看战报或回合制模式,整个战斗过程是流畅和连贯的,人物的移动、攻击、技能释放都不会让玩家感觉到停滞,整体感觉类似于传统的即时战略游戏(魔兽、星际等),玩家在游戏中的指令(如释放技能)可以实时得到执行。

这里带来的问题是,如何设计一个稳定且高效的战斗系统,来满足多人战斗时可能的高并发;不会因为高并发对服务器造成过重的负担,不会对玩家带来糟糕的延时体验;同时数目繁多的兵种和技能要能够稳定有序地工作在这个系统中,不会让程序员疲于应付而无所适从。

下面针对这些需求提出了一种设计思路。

  • 设计要点:

    • 内存化

当玩家在线人数很多时,如果还是将每次数据修改入库,势必会带来很大的cpu开销。笔者曾经参与一个项目,当同时在线人数达到500时,服务器用于mysql的cpu占用率飙到了800%。后来经过分析,有很大一部分数据没必要实时入库,例如战场上的NPC数据,相对不敏感,即使服务器重启也无所谓,这部分数据可以全走内存;另有一部分玩家相关数据可以采用异步存储的方式,战斗线程直接操作内存,另有一监控线程视情况每隔一段时间将内存数据刷入数据库。

  • 单线程

也许你会说,现在的多核服务器为什么还要用单线程?这是因为单线程有它的好处,一是不用费心费力去解决死锁等并发问题,通常一个先后关系造成的死锁问题会占用程序员大量的解决时间;二是有了前面的内存化,战斗线程不再会因为数据库读写等耗时操作而卡帧,所以我们完全可以用这样一个模型来解决问题:只有一个后台线程在逐帧循环,每帧的战斗数据推送前端;玩家的操作(如释放技能)不直接执行,而是交由后台线程排队后逐个执行。执行的时刻可能是当前帧,或者推迟到下一帧,总之由后台线程统筹规划。这样就避免了因为并发带来的一些未知问题。

  • 分解

我们来比较一下两种程序设计思路:一是把所有的战斗逻辑都写在后台线程里,一大堆if-else和for循环耦合在一起;二是将复杂问题分解到很多类中,每个类只负责处理它应该处理的事情,后台线程做的事情只是按一定的顺序把这些类组织起来。可以明显看到第二种方法更加清爽,代码可维护性更好,程序员也更喜欢。事实上,很多战斗系统都同样可以分解为battleUnit, state, skill, buff等基本的单元。下面会专门举例说明。

  • 举例

    下面这个例子假定战斗发生在一个战场(FightScene)中,战场中有许多战斗单位(FightUnit),有一个战斗引擎(FightEngine)负责开启后台线程,每帧遍历一次战场中的各个战斗单位,进行相应的动作。玩家释放技能的操作,由事件(Event)的方式通知对应的战斗单位,更改它的状态机使之进入技能状态(SkillState)并执行释放技能和添加buff(Buff)的操作。整个系统只有一个线程,战斗过程模块化,结构清晰,易于扩展。

// buffpublic interface Buff {		// 进入时调用	public void enter();		// 退出时调用	public void exit();		// 每帧执行	public void tick(long interval);}

// 事件public interface Event {}

import java.util.List;// 战场public class FightScene {		// 攻方列表	private List
 attList; // 守方列表 private List
 defList; // 后台线程每隔一帧执行一次 public void tick(long interval) { for (FightUnit unit : attList) unit.tick(interval); for (FightUnit unit : defList) unit.tick(interval); } // 寻找指定战斗单位 public FightUnit findUnit(int side, int index) { if (side == 1) { return attList.get(index); } else { return defList.get(index); } }}

import java.util.List;// 战斗单位(玩家或者npc)public class FightUnit {		// 事件列表	private List
 eventList; // buff列表 private List
 buffList; // 当前状态(状态机) private State state; // 添加事件 public void addEvent(Event event) { eventList.add(event); } // 添加buff public void addBuff(Buff buff) { buffList.add(buff); } // 每帧执行 public void tick(long interval) { for (Event event : eventList) { if (event instanceof SkillEvent){ int skillId = ((SkillEvent)event).getSkillId(); // 退出旧的状态,进入新的状态 state.exit(); state = new SkillState(this, skillId); state.enter(); } } for (Buff buff : buffList) { buff.tick(interval); } state.tick(interval); }}

// 战斗引擎public class FightEngine {		private FightScene fightScene;		// 帧间隔	public static final long TICK_INTERVAL = 50;		// 启动战斗线程	public void startFightThread() {		new Thread(){			public void run() {				while (true) {					try {						long startTime = System.currentTimeMillis();						fightScene.tick(TICK_INTERVAL);						long endTime = System.currentTimeMillis();						// 补足一帧剩余时间						Thread.sleep(TICK_INTERVAL - (endTime - startTime));					} catch (Exception e) {						// TODO					}				}			}		}.start();	}		// 释放技能(这里是玩家操作)	public void releaseSkill(int skillId, int side, int index) {		/*		 * 判定条件(能量不足、战斗已结束等)		 * TODO		 * ...		 * 		 * */				// 添加事件到战斗单元		FightUnit unit = fightScene.findUnit(side, index);		unit.addEvent(new SkillEvent(skillId));	}}

// 技能事件(一种事件的类型)public class SkillEvent implements Event{		// 技能id	private int skillId;		public SkillEvent(int skillId) {		this.skillId = skillId;	}		public int getSkillId(){		return skillId;	}}

// 技能状态(一种状态类型)public class SkillState implements State{		// 技能id	private int skillId;		// 战斗单位	private FightUnit unit;		public SkillState(FightUnit unit, int skillId) {		this.unit = unit;		this.skillId = skillId;	}		// 进入时调用	public void enter() {			}		// 离开时调用	public void exit() {			}	// 每帧执行	public void tick(long interval) {		/*		 * 释放技能的逻辑(一连串令人眼花缭乱的效果...)		 * TODO		 * ...		 * 		 * */				// 添加buff(假定这个技能会给自己加buff)		Buff buff = /*...*/		unit.addBuff(buff);		buff.enter();	}	}

// 战斗单位的状态public interface State {		// 每帧执行	public void tick(long interval);		// 进入时调用	public void enter();		// 离开时调用	public void exit();}

转载于:https://my.oschina.net/roll1987/blog/550227

你可能感兴趣的文章
在Sql语句中使用正则表达式来查找你所要的字符
查看>>
18种最实用的网站推广方法大全
查看>>
浅谈C/C++中的typedef和#define
查看>>
浅谈C/C++中的指针和数组(一)
查看>>
选出数组中指定小的元素
查看>>
这该死的数字化生活
查看>>
[New Portal]Windows Azure Virtual Machine (23) 使用Storage Space,提高Virtual Machine磁盘的IOPS...
查看>>
matlab练习程序(圆柱投影)
查看>>
谈谈Memcached与Redis
查看>>
将链表逆序(Revert)的C#实现
查看>>
需要谨记的产品设计原则
查看>>
checkbox实现单选多选
查看>>
billing是如何的拆分的?
查看>>
Linux平台开发技术指南
查看>>
flag标志什么?哦,它标志代码馊了——(三)
查看>>
Android进阶2之WebView(浏览器)
查看>>
基于Pixel Shader的FFT已经完成
查看>>
android ListView布局之三(使用自定义的Adapter绑定数据,通过contextView.setTag绑定数据)有按钮的ListView...
查看>>
调用函数时,寄存器到底发生了那些变化?
查看>>
ASP.NET中常用的三十三种代码(C#版)
查看>>