为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

角色控制-点击移动以及自动寻路

2017-11-12 11页 doc 64KB 30阅读

用户头像

is_591683

暂无简介

举报
角色控制-点击移动以及自动寻路角色控制-点击移动以及自动寻路 角色控制-点击移动以及自动寻路 角色控制-点击移动以及自动寻路(一) 之前在D3D中做的人物控制里,在空间中两点之间行走是一个比较复杂的制作过程。跟大家分享一下我的制作过程,可以一起研究改进改进。 这里面需要解决几个问题: 1.如何取得目标地点 需要通过鼠标当前位置的屏幕二维坐标,逆矩阵变换到世界空间下,做从摄像机到这个点的射线。射线与地面mesh进行碰撞检测,获得一个交点,就是目标点。 2.如何移动角色 实时得出角色的移动方向向量,在两点之间做线性插值计算,移动的同时也要播放动...
角色控制-点击移动以及自动寻路
角色控制-点击移动以及自动寻路 角色控制-点击移动以及自动寻路 角色控制-点击移动以及自动寻路(一) 之前在D3D中做的人物控制里,在空间中两点之间行走是一个比较复杂的制作过程。跟大家分享一下我的制作过程,可以一起研究改进改进。 这里面需要解决几个问题: 1.如何取得目标地点 需要通过鼠标当前位置的屏幕二维坐标,逆矩阵变换到世界空间下,做从摄像机到这个点的射线。射线与地面mesh进行碰撞检测,获得一个交点,就是目标点。 2.如何移动角色 实时得出角色的移动方向向量,在两点之间做线性插值计算,移动的同时也要播放动画。 3.两点之间有障碍怎么办 这个过程是一个比较复杂的过程,可以通过两种办法,一种是做一种转向机制,先移动监测点(我自己起的,意思是一个前进向量和一个向下的碰撞向量的假象交点),监测到碰撞了,就将角色转向,再次监测,直到碰撞不到,向当前挪一步,再重复刚才的操作,直到绕过这个障碍物;另一种是俗称的A*算法,需要预先准备好一个二维图来标记哪里可以走,哪里不可以走,并设置权值,以便于计算最短路径。 4.Unity3D里的碰撞需要注意什么 射线的碰撞检测是比较耗费CPU资源的,推荐的是尽量少用,比如当做场景内物体的鼠标拾取时,对备选对象队列进行筛选后,再去逐一做检测。unity3d中也是一样,只不过没有 API,无法用自定义的结构去决定对哪些对象进行射线碰撞。所以提供像DX中提供的那种 要引入Layer的概念,具体的操作下节说。角色控制-点击移动以及自动寻路(二) 针对上一节中第四点--筛选待测物体来说一下U3D中Layer的用法。 U3D中的Layer可以简单地理解为一个集合,所有的GameObject都可以设置Layer,且只能设置一个Layer。 设置方法: 1.Edit->roject Setting->Tags(tag和layer在同一个设置面板上) 2.前几个Builtin Layer是系统默认的,不能修改,可以在后面几个Layer中,设置Layer的名称,比如"layers" 3.在Hierarchy面板中选择GameObject,在Inspector面板上面的Layer下拉框中选择刚才设置好的Layer 从此,该GameObject就属于这个Layer集合中的一员了。 Layer的应用:LayerMask LayerMask可以在射线碰撞过程中屏蔽掉一些GameObject。 LayerMask的方法: LayerMask.NameToLayer("layers") 返回该Layer的编号 LayerMask.LayerToName(8) 返回该Layer的名称 var layerMaskPlayersayerMask = 1 << LayerMask.NameToLayer("Players"); var layerMaskTerrainsayerMask = 1 << LayerMask.NameToLayer("Terrains"); var FinalMaskayerMask = (layerMaskPlayers.value | layerMaskTerrains.value); var hitt : RaycastHit; var ray : Ray = mainCamera.ScreenPointToRay(Input.mousePosition); if( Physics.Raycast(ray, hitt, 600, FinalMask.value)) { ... } 上面代码描述一个鼠标拾取规则,射线仅与Layer属于Players和Terrains的GameObject做碰撞。 LayerMask的使用是按位操作的,在设置新的Layer时可以看到一共只有32个,应该是因为记录Layer的变量是四个字节。LayerMask.NameToLayer方法返回的LayerId是所要移位的位数,比如在设置Layer时,Players这个Layer的ID是8,则 var layerMaskPlayers:LayerMask = 1 << LayerMask.NameToLayer("Players"); 最后将所有的可能按位或操作,就得出所有需要做射线碰撞的GameObject。 如果想取“除了XXXLayer”的都显示,则可以用取反操作。 layerMaskPlayers = ~layerMaskPlayers.value; 比如layerMaskPlayers = 1<<8; 则layerMaskPlayers = 100000000; 在双字长的情况下,~layerMaskPlayers.value = -257 二进制示:11111111111111111111111011111111角色控制-点击移动以及自动寻路(三) 为鼠标点击的位置增加一个光圈。需求如下: 鼠标右键点击地面,射线与地面的交点处出现一个光圈(纹理动画)。 该纹理动画会旋转,跟游戏中一样。 玩家移动到该光圈处,或者进入了其他状态,光圈消失。 玩家点击新的目标点,原目标点光圈消失。 光圈要随着地形的起伏改变自己的朝向,这样自然。 制作过程: 首先create一个Plane,去掉这个Plane的Collider Component,否则玩家会和光圈点发生碰撞。并为这个Plane指定一个纹理贴图,shader方式选择Particles/Alpha Blended Premultiply 颜色相乘,显得光圈亮一些。注意光圈图片必须要有‎‎通道,才能进行透Alpha明处理。 (注:另一种可选的shader方式是FX/Flare,与前面的那种不同,这个绘制出来的纹理将会一直显示在前端,相当于关闭了屏幕的深度缓冲区后再绘制。效果就是:即使在障碍物后面,也能看到它,比较不太符合现实逻辑,但是考虑到地形的起伏,光圈平面有可能会嵌入到地形里面,所以有时这个又比较合适,具体是不是有更好的办法处理这个问题,稍后再细细追究,或者大家可以提出更好的意见供参考。主要是U3D中不好确定渲染过程,不能动态操纵何时关闭屏幕深度缓冲区,其实怎么关闭屏幕的深度缓冲区也不清楚。。。) 然后在全局空GameObject(空的对象,不做任何逻辑处理,只为了挂接全局的脚本--不依附于任何Object的脚本)中增加一个鼠标相关控制脚本。加上前面所说过的改变鼠标指针UI操作,整个脚本如下: 这个脚本要完成的工作是: 通过传进来的prefab(transform类型),将光圈点放置在指定位置,并且根据射线与地形交点的面片的法线,调整这个光圈的朝向。还有一个在创建时要注意的是,将光圈的坐标沿着Y轴正向提高了一个数值,是为了避免纹理动画与地面纹理重合造成撕裂现象。 其中定义的两个方法CreateDestinationTex和DestroyDestinationTex将会在人物控制脚本中调用。 注:虽然Instantiate方法在手册中显示返回的类型是Object,但实际上是返回跟prefab同类型的一个对象,比如prefab是个transform类型,那么Instantiate返回的就是一个transform类型的对象,在Destroy一个prefeb对象时,Destroy(...) 括号里只能写Object或GameObject, 如果是Transform,则会因为有其他Component依附于这个transform,而无法删除掉。 最后修改角色控制脚本,在其中获得鼠标控制脚本的对象: private var _ScriptObj_cursorControl : Script_Cursor; _ScriptObj_cursorControl = GameObject.Find("GameObject_GlobalController").GetComponent(Script_Cursor); //获得鼠标图标脚本对象 然后在鼠标右键点击地面后,进入STATE_LERPWALK状态,同时调用鼠标控制脚本中的函数CreateDestinationTex在指定给位置创建一个光圈对象,而当不在STATE_LERPWALK状态下时,调用鼠标控制脚本中的函数DestroyDestinationTex删除该对象。 if (Input.GetMouseButtonUp (1) && _grounded) { ...... //调用鼠标处理脚本 if(_ScriptObj_cursorControl != null) //创建目标点光圈 _ScriptObj_cursorControl.CreateDestinationTex(_endPos,TextureOnFloor,_hit.normal); ...... } if(_playerstate == PlayerState.STATE_LERPWALK && _grounded) {...} else { if(_ScriptObj_cursorControl != null) _ScriptObj_cursorControl.DestroyDestinationTex(); // 删除目标点光圈 else Debug.Log("cursor scirpt error !"); }角色控制-点击移动以及自动寻路(四) ... var _ray : Ray; //从摄像机发射到鼠标位置的射线 var _hit : RaycastHit ; //射线与目标网格的交点数据 var _startPos : Vector3; //当前位置,出发点 var _endPos : Vector3; //鼠标点击位置,目标点 var _lerp : float; //插值系数 var _deltaLerp : float; //插值系数递增步长 var _rotation : Quaternion; //从当前朝向(本地空间Z轴)转向目标方向的旋转四元数值 var _slerpRotation : float ; //旋转插值系数 var _deltaSlerpRotation : float; //旋转插值系数递增步长 var _moveDirectionLerpWalk : Vector3; //鼠标点击地面后,应该移动的方向 var _movePrecision : float; //当前点与目标点的距离误差 private var _lastDistance: float ; //暂时未制作自动寻路系统,地图上未划分网格,如果目标点玩家上不去,有可能会一直跑,玩家与目标点的距离会呈现“先缩小距离,后拉长距离”的结果,所以用此变量记录上一步的距离,当距离开始拉长时,就停下。 ..... enum PlayerState { STATE_READY = 0, STATE_RUN = 1, STATE_IDLE = 2, STATE_ATTACK = 3, STATE_JUMP = 4, STATE_LERPWALK = 5,//增加一个状态,标示处于右键点击地面后的移动状态 } ....... function Update() { ...... //===================================================================== ==== //鼠标操作 - 右键按下后弹起 var layerPlayers :LayerMask = 1 << LayerMask.NameToLayer("Players"); //var layerTerrains :LayerMask = 1 << LayerMask.NameToLayer("Terrains"); //var finalLayer : LayerMask = (layerPlayers.value | layerTerrains.value); layerPlayers = ~layerPlayers.value; //对Players这个Layer的数值2进制位取反,表示除了Player s以外,其他Layer都可以与射线进行碰撞 if (Input.GetMouseButtonUp (1) && _grounded) { if(_cameraCurrent == null) { print(null); return; } _ray = _cameraCurrent.ScreenPointToRay (Input.mousePosition); if (Physics.Raycast (_ray, _hit, Mathf.Infinity , layerPlayers.value)) { if(_hit.collider.gameObject.tag == "Terrains") { //Debug.DrawLine (_ray.origin, _hit.point); _playerstate = PlayerState.STATE_LERPWALK; _lerp = 0; _startPos = transform.position; var tempVec = _hit.point; tempVec.y = _startPos.y; //高度拉齐,为了算出方向向量 _endPos = _hit.point; print("*****the endpos is : " + _endPos); _lastDistance = Vector3.Distance(_startPos , _endPos); _deltaLerp = 1/Vector3.Distance(_startPos , _endPos); _slerpRotation = 0; var relativePos = tempVec - _startPos; _rotation = Quaternion.LookRotation(relativePos); //这个函数很方便,计算出从当前朝向转向 目标方向的旋转四元数 _moveDirectionLerpWalk = relativePos.normalized; _moveDirectionLerpWalk *= _speed; //调用鼠标处理脚本 if(_ScriptObj_cursorControl != null) //创建目标点光圈 _ScriptObj_cursorControl.CreateDestinationTex(_endPos,TextureOnFloor,_hit.normal); } } } if(_playerstate == PlayerState.STATE_LERPWALK && _grounded) { _lerp += _deltaLerp; //if(_lerp >= 1) //{ // _playerstate = PlayerState.STATE_IDLE; // transform.rotation = _rotation; // return; //} _slerpRotation += _deltaSlerpRotation; if(_slerpRotation >= 1) { transform.rotation = _rotation; } //平滑旋转人物的朝向 transform.rotation = Quaternion.Slerp(transform.rotation, _rotation, _slerpRotation); //插值虽然计算得准,但直接用插值就无法做碰撞了,这里只用来保存坐标 //先取出移动方向向量,用Move方法移动,可以顺便做碰撞 var position = Vector3.Lerp(_startPos , _endPos , _lerp); var _flagsLerp : CollisionFlags = _controller.Move(_moveDirectionLerpWalk * Time.deltaTime); _animation.Play("run"); var distance = Vector3.Distance(transform.position,_endPos); //以当前坐标和目标坐标的距离作为结束条件,小于误差范围值则停下 if(distance <= _movePrecision) _playerstate = PlayerState.STATE_IDLE; //当距离由大变小,又开始由小变大时,也要停下 else if(distance >= _lastDistance) _playerstate = PlayerState.STATE_IDLE; //记录上一帧中,玩家与目标点的距离 else _lastDistance = distance; } else { if(_ScriptObj_cursorControl != null) _ScriptObj_cursorControl.DestroyDestinationTex(); // 删除目标点光圈 else Debug.Log("cursor scirpt error !"); } //===================================================================== ==== ...... }
/
本文档为【角色控制-点击移动以及自动寻路】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索