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


    • 寻路
    -- 角色移动
    --[[ 
    	一般过程: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++ ----------------
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 角色动作状态
    ------状态类------
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 跨区跨地图 自动寻路
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

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

    检测并触发引导的过程,通常放在任务状态下发的相关消息协议中,我们需要一个检测函数检测当前任务是否需要出发引导,有时除了需要判断上面提及的引导是否开启结束的一般状态外,还需要执行其他特殊判断逻辑,此时,为了保证引导系统的统一性,可以将所有判断逻辑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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

  • 相关阅读:
    vue 免费的每天不限次数的调用天气接口
    Springboot启动流程分析(四):完成启动流程
    红外遥控视力自动检测系统的设计与实现
    Springboot实现匹配系统(上)
    python进阶系列 - 11 python随机数
    【多线程】Callable 接口
    详解Java算法之冒泡排序(Bubble Sorting)
    猿创征文|大厂说的 代码门禁如何实现?
    【JavaScript高级】函数相关知识:函数、纯函数、柯里化、严格模式
    低功耗引擎 Cliptrix 有什么价值
  • 原文地址:https://blog.csdn.net/XIANG__jiangsu/article/details/81044863