基于ComblockEngine+Unity的联机版坦克大战(二)

阶段目标

  • 从大厅进入战场
  • 客户端修改为双人版本
  • 战场内移动同步的实现

效果演示

在这里插入图片描述

单机版的结构

在这里插入图片描述
GameManager作为一个全局的游戏管理器存在,每个具体的坦克是由TankManager类来进行管理,其中,坦克具体的移动和射击逻辑是分别交由TankMovment和TankShooting去完成。

联机版的结构

在这里插入图片描述
联机版和单机版在类的设计实现上是一样的,主要区别也就下面几点:

  • TankManager交由PlayerAvatar来持有,因为联机时,我们每个角色的数据创建时由server来驱动的,与server对应的玩家实体是PlayerAvatar,这个PlayerAvatar相当于是一个真实的服务器一致的逻辑数据实体。
  • 区分出主玩家控制的坦克角色和其他非主玩家坦克

战场的跳转

这类开房间式的游戏,都会涉及到一个从大厅跳转战场的流程。坦克大战也是一样,我们的战场和大厅在这个版本里面都是SpaceRoom这一个类来完成,只是战场和大厅的uType值不同。

class SpaceType(object):
	SPACE_TYPE_NONE = 0
	SPACE_TYPE_HALL = 1
	SPACE_TYPE_BATTLE = 2

在完成匹配后,我们会申请创建一个战场space,并且在space创建完毕后,让base上的playAvatar一个个enter进space,在enter到space上时,会先离开大厅这个space,然后调用playAvatar在cell部分的onTeleportSpaceCB方法,该方法主要做一件事情,就是让玩家teleport到战场所在的space。

跳转战场的时序图如下:

client base_Hall SpaceMgr base_SpaceRoom PlayerAvatar cell_PlayerAvatar cell_SpaceRoom avatarReqMatch 完成匹配 enterSpace 创建space addWaitToEnter onGetCell enter 通知跳转 onTeleportSpaceCB teleport onTeleportSuccess on_enter 是否全部玩家进入 onEnterBattleRoom client base_Hall SpaceMgr base_SpaceRoom PlayerAvatar cell_PlayerAvatar cell_SpaceRoom

说明下,base_Hall就是大厅,base_SpaceRoom是战场,他们都是SpaceRoom这个类,只是图里面为了区分,用了不同的名称。


对应的基本代码(git版本号 459eb4a3980f183d5e8b9b191d91a1845bd7bf1c):

base/SpaceRoom.py

def enter(self, avatar_entity_call):
	if self.uType == SpaceType.SPACE_TYPE_HALL:
		avatar_entity_call.createCellEntity(self.cell)
	else:
		# 从大厅进入战场
		# 先从大厅离开
		avatar_entity_call.curHallSpace.leave(avatar_entity_call.id)
		avatar_entity_call.cell.onTeleportSpaceCB(self, self.cell, avatar_entity_call.born_position, (0, 0, 0))

	self._avatar_dict[avatar_entity_call.id] = avatar_entity_call

	avatar_entity_call.onEnterSpace(self.id, self.uType)
cell/PlayerAvatar.py

def onTeleportSpaceCB(self, spaceBaseEntityCall, spaceCellEntityCall, position, direction):
	"""
	defined.
	baseapp返回teleportSpace的回调
	"""
	DEBUG_MSG("PlayerAvatar::onTeleportSpaceCB. spaceBase:%s...spaceCell:%s" % (spaceBaseEntityCall, spaceCellEntityCall))

	self.curSpaceBaseEntityCall = spaceBaseEntityCall
	self.teleport(spaceCellEntityCall, position, direction)

移动同步

对于View范围内的Entity,其基础的属性,Kbengine引擎底层会自动帮我们做好了属性的同步,包括position和direction等。
但是,对于服务器自动同步下来的position,肯定都是一个个离散的点,同步数据到达客户端的时间也受到当前网络的影响,其次,服务端的tick频率也往往会比客户端低。如果客户端每次收到position的位置,就直接更新模型到对应的点上,看上去的移动一定会是一卡一卡的。
为了解决非主玩家的移动平滑同步,这里采用了简单的影子跟随算法。影子跟随简单理解就是,模型(Gameobject)一直去追随与之关联的逻辑对象(PlayAvatar)位置。这个PlayAvatar是个Entity,也就是和服务端保持一直的一个实体对象,这是个逻辑的数据,其上的position会经由服务器自动同步下发。每个PlayAvatar都有一个与之绑定的Gameobject对象,Gameobject是个渲染模型对象。

追随的核心算法在Scripts/Tank/TankMovement.cs里面。

// 影子追随
private float CalcNewValueByShadow(float curValue, float shadowValue, float deltaTime)
{
	if (curValue == shadowValue)
	    return curValue;
	
	float deltaValue = Mathf.Abs(curValue - shadowValue);
	int ratio = 1;
	if (curValue < shadowValue)
	{
	    // 大于一定阈值,加速
	    if (deltaValue > m_Speed * 40 * deltaTime)
	        ratio = 2;
	    
	    curValue += Mathf.Min(deltaValue, m_Speed * deltaTime * ratio);
	}
	else
	{
	    if (deltaValue > m_Speed * 5 * deltaTime)
	        ratio = 2;
	
	    curValue -= Mathf.Min(deltaValue, m_Speed * deltaTime * ratio);
	}
	
	return curValue;
}

说明: 代码中有个40数值,这个仅仅是个保持相位差的最大阈值,可以根据需要自行调整,所谓相位差,就是每一帧下,实体和影子相对保持的距离。因为我们设定的移速是m_Speed,一帧的时间是deltaTime,40相当于是我们允许模型和影子的最大位移差是40倍的m_Speed*deltaTime。

存在的问题

这个版本其实是相对粗糙的,无论是代码结构还是游戏功能,都还属于功能验证和引擎学习阶段,对于战场也是只能每次只进入1轮便必须结束。这些问题会在下一篇文章里统一解决掉。这里只作为KBEngine和Unity的入门练习准备。

后续内容

  • 战斗同步
  • 代码结构调整,模块解耦(比如space拆分、ui和逻辑拆分)
  • 断线重连和顶号
  • 多房间创建(目前仅支持开一个战场,不然会出问题)
  • KBEngine开发中遇到的坑
  • KBEngine中部分功能的源码分析

源码地址

github地址:地址在这里

个人有点懒,再加上前端时间工作上比较忙,代码和文章会不定时更新哦。得逼自己一把,争取后续内容一个月内更完吧。

发布了47 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/it_wjw/article/details/101163709