游戏中的各种简单实现方案

  • 寻路
-- 角色移动
--[[ 
	一般过程:1)lua层发起寻路;2)c++层利用AStar计算路线,run每帧执行一步
]]
--------------- lua ----------------
function moveToDes(targetx,targety)
	local result = self.m_char:requestPath(targetx,targety) -- 在C++层寻路
	return result
end
--------------- lua ----------------


--------------- c++层 ----------------
function cChar:requestPath(targetx,targety,sync) -- 请求路径 结果存于AStar
	if isChar() then -- 先判断是否为主角
		local targetPos = cc.p(targetx,targety)
		AStar.break() -- 重新寻路前设置为停止状态
		AStar.FreePath() -- 清空之前缓存的路径

		local srcPos = self.m_char:getCurPosition() -- 起点
		if sync then -- 不使用线程寻路 适用于短距离反复点击屏幕时使用
			AStar.findPath(srcPos,targetPos) -- 手动执行路径搜索
		else -- 使用线程寻路
			AStar.isOpenSyncfind = true -- AStar线程开始执行路径搜索
		end
		return true
	end
	return false
end

function cChar:run() -- 角色移动时调用 每帧执行
	self:walkPath()
	-- 其他移动时需要做的操作
end

function cChar:walkPath() -- 角色执行路径
	if AStar.isOpenSyncfind then -- 还在搜索路径
		return
	end
	if self.m_char:isRun() then
		return
	end
	local prePos = self:getCurPosition() -- 当前位置
	local preAStarNodePos = AStar.getPathPreNode() -- 寻路所记录的前一个位置
	if prePos != preAStarNodePos then -- 检校位置发生是否发生瞬移,如发生中途服务器强制拉人 重新寻路
		-- 现在g_AStar path中找看看有没有prePos 有的话直接使用
		while AStar.isHasPath() do -- 如果当前有寻路缓存结果,先从已有结果中查找
			local curNodePos = AStar.NodeGetPos()
			AStar.PathNextNode()
			if curNodePos == prePos then -- 找到 后面直接使用
				break
			end
		end

		if not AStar.isHasPath() then -- 缓存路径中已经没有节点数据 说明上面的while内未找到匹配的位置节点
			local nodeDestPos = AStar.realDstPos() -- 目标位置
			if math.abs(nodeDestPos - prePos) <= 0 then -- 正好被拉到目标点 如传送类型的道具
				self:onPathEnd() -- 寻路结束
			else
				self:onPathCancel() 
				requestPath(nodeDestPos.x,nodeDestPos.y) -- 重新寻路
			end
		end
	end

	local destPos = AStar.PathNextNode()
	self:renderMoveTo(destPos) -- 渲染层移动主角
	self:setSendMoveCmd(destPos) -- 同步给服务器
end
--------------- c++ ----------------
  • 角色动作状态
------状态类------
ActionState = class("ActionState") -- 角色动作状态
function ActionState:ctor(obj) -- 做一些初始化工作
	self.m_Obj = obj -- 状态对象
end
function ActionState:enter() -- 进入状态
	-- body
end
function ActionState:begin() -- 开始执行
	-- body
end
function ActionState:end() -- 结束执行
	-- body
end
function ActionState:exit() -- 退出状态
	-- body
end


AttackState = class("AttackState")
function AttackState:ctor(obj)
	ActionState.ctor(self,obj)
end
function AttackState:enter( targetObj ) -- 状态切换时触发
	lookAt(targetObj) -- 调整方向
	self.m_Obj:playAni(Enum.ActionState.AttackState) -- 播放动作
	-- 其他逻辑
end
function AttackState:begin() -- 在播放开始动作时调用 
	doAttack()
end
function AttackState:end() -- 在播放结束动作时调用
	stopAttack()
	local nextState = Enum.ActionState.Rest
	self.m_Obj:setActionState(nextState) -- 退出时切换至下一个状态
end
function AttackState:exit() -- 状态切换时触发
end
------状态使用------
function NPC:ctor()
	self.m_stateLst = {} -- 生成NPC时 创建所有状态
	self.m_stateLst[Enum.ActionState.Attack] = AttackState.new(self)
	self.m_curState = Enum.ActionState.NULL
end
function NPC:setActionState(newState)
	if newState == self.m_curState then
		return
	end
	-- 退出当前状态
	if self.m_curState ~= Enum.ActionState.NULL and self.m_stateLst[self.m_curState] then
		self.m_stateLst[self.m_curState]:exit() -- 退出当前状态 
	end
	self.m_curState = newState
	-- 进入新状态
	if self.m_curState ~= Enum.ActionState.NULL and self.m_stateLst[self.m_curState] then
		self.m_stateLst[self.m_curState]:enter() -- 进入新状态
	end
end
function NPC:playAni(aniType)
	-- 执行一个序列帧动作 char里包含一个每帧都调用的run函数
	-- 函数体内判断当前动作是否为开始或结束帧,并相应触发begin或end
	self.char:doAction(aniType)
end
  • 跨区跨地图 自动寻路
autoWalk = function (regionid, mapid, posx, posy, endCallback) -- 寻路至某区某张地图的某位置
	local curRegionId = getCurRegionId()
	targetPos = cc.p(posx,posy)
	if regionid ~= curRegionId then -- 不在同一区
		if curMapId == transMapId then -- 跨区传送位置正好位于当前地图中
			eventQueue.push(moveToTransPointEvent) -- 移动至传送位置(所有事件均存至队列中 每帧处理)
			eventQueue.push(transEvent) -- 移至传送位置后发起传送
		else -- 传送位置不位于当前地图 首先移至当前地图的跨场景位置 
			eventQueue.push(moveToCurMapTransPointEvent) -- 移至跨场景位置
			eventQueue.push(reqTransEvent) -- 到了相关位置 向服务器发起转移请求
		end
		-- 经过上面的过程 角色总会发生一次场景转移 但是还未到达目的地 所以要递归执行autoWalk
		autoWalk(regionid,mapid,posx,posy,endCallbackEvent)
	else -- 目的场景与当前场景在同一个区
		if curMapId == mapid then
			-- 待寻路地图正是本地图
			eventQueue.push(moveToTargetPointEvent) -- 直接移至目的地
			eventQueue.push(endCallbackEvent) -- 寻路结束 执行一个回调函数
		else
			-- 同一个国家不同地图
			eventQueue.push(moveToCurMapTransPointEvent) -- 移至跨场景位置
			eventQueue.push(reqTransEvent) -- 到了相关位置 向服务器发起转移请求
			autoWalk(regionid,mapid,posx,posy,endCallbackEvent)
		end
	end
end

总结来说,当区不同时首先移至当前地图跨场景位置,如果该位置是一个跨区传送点,则执行一个跨区操作(请求服务器完成);如果是普通跨场景点,则执行一个跨场景操作。这两种情形执行完一定还未到达目标位置,所以都需要执行寻路的递归操作。当在同一区时,如果也在同一地图,则直接寻路至目标位置,并执行一个最终寻路结束的回调,如果地图不同,则进行跨场景并递归寻路。理论上,所有递归的最后一步都会执行至第三种情形。

  • 游戏引导
    实现引导,需要解决几个问题:如何设计引导数据结构;如果检测并触发引导;引导后如何响应操作。一般引导与任务配搭,任务有开始、执行、结束三个过程,同时任务有不同的子步骤组成,因此引导数据结构如下:
self.guideStruct = {
	[taskid] = { -- 以任务id作为key
		isStart = false,
		isFinish = false,
		childTask = { -- 子任务
			[id] = {state}, -- 记录每个子任务的状态等信息 
		}
	}
}

检测并触发引导的过程,通常放在任务状态下发的相关消息协议中,我们需要一个检测函数检测当前任务是否需要出发引导,有时除了需要判断上面提及的引导是否开启结束的一般状态外,还需要执行其他特殊判断逻辑,此时,为了保证引导系统的统一性,可以将所有判断逻辑conditionFunc放于配置当中。
当开始引导后,从配置中读取当前任务及其状态需要引导的相关界面及按钮元素,当然,实际的配置将会更加复杂,可能包含具体的引导表现方式,比如是上下提示引导还是左右提示引导,矩形引导还是椭圆形引导等。读到所有需要的引导信息后,将其传送至引导panel以显示引导效果。

function checkGuide(taskid,otherData)
	-- 每个任务都配备了是否需要执行引导的判断函数
	local needCheck = (not guideStruct[taskid].isStart) or (guideStruct[taskid].isStart and not guideStruct[taskid].isFinish)
	if not needCheck then return end -- 如果任务已经结束 则不再引导
	local conditionFunc = GuideConf[taskid].conditionFunc -- 每个引导都定义了各自的判断函数
	needCheck = conditionFunc and conditionFunc(otherData)
	if not needCheck then return end -- 如果任务不满足既定条件 则不再引导
	-- 满足引导条件 开启引导
	local stateid = GuideConf[taskid].stateidLst[1] -- 取第一个stateid
	startGuide(taskid,stateid,otherData) -- 开始引导
	saveGuide(taskid,stateid) -- 将引导进度保存至本地
end
function startGuide(taskid,stateid,otherData)
	local ui,spEle = getGuideUI(taskid,stateid) -- 获取当前需要引导的界面元素
	createGuidePanel(ui,spEle) -- 在需要引导的对象上构建引导界面(层级最高 使用浙江将touch的有效区域设置为spEle区域)
end

上面完成了引导的触发,那么引导的响应如何处理。最粗暴的实现方式是对于每个可能响应引导的界面元素,都进行引导处理检查。如某英雄角色的升级按钮在升级时会被引导,那么就在按钮的触控世界上进行引导响应检查。这种做法会使得引导处理的逻辑结构过于分散。另一种做法可能更为简单且更统一,即直接重写Button的触控事件,然后在事件内部判断当前的引导过程是否是针对该button。如果是的话,则标记该引导为已完成状态并关闭引导界面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值