查看: 1351|回复: 2
打印 上一主题 下一主题

【转载】角色控制-点击移动以及自动寻路

[复制链接]

2317

主题

54

听众

2万

积分

资深设计师

Rank: 7Rank: 7Rank: 7

纳金币
20645
精华
62

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

跳转到指定楼层
楼主
发表于 2012-12-14 19:13:19 |只看该作者 |倒序浏览
来源:454981127



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



之前在D3D中做的人物控制里,在空间中两点之间行走是一个比较复杂的制作过程。跟大家分享一下我的制作过程,可以一起研究改进改进。

这里面需要解决几个问题:

1.如何取得目标地点

需要通过鼠标当前位置的屏幕二维坐标,逆矩阵变换到世界空间下,做从摄像机到这个点的射线。射线与地面mesh进行碰撞检测,获得一个交点,就是目标点。

2.如何移动角色

实时得出角色的移动方向向量,在两点之间做线性插值计算,移动的同时也要播放动画。

3.两点之间有障碍怎么办

这个过程是一个比较复杂的过程,可以通过两种办法,一种是做一种转向机制,先移动监测点(我自己起的,意思是一个前进向量和一个向下的碰撞向量的假象交点),监测到碰撞了,就将角色转向,再次监测,直到碰撞不到,向当前挪一步,再重复刚才的操作,直到绕过这个障碍物;另一种是俗称的A*算法,需要预先准备好一个二维图来标记哪里可以走,哪里不可以走,并设置权值,以便于计算最短路径。

4.unity3d里的碰撞需要注意什么

射线的碰撞检测是比较耗费CPU资源的,推荐的是尽量少用,比如当做场景内物体的鼠标拾取时,对备选对象队列进行筛选后,再去逐一做检测。unity3d中也是一样,只不过没有提供像DX中提供的那种API,无法用自定义的结构去决定对哪些对象进行射线碰撞。所以要引入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("layers");

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 layerMaskPlayersayerMask = 1 << LayerMask.NameToLayer("layers");

最后将所有的可能按位或操作,就得出所有需要做射线碰撞的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 ayerMask = 1 << LayerMask.NameToLayer("layers");

//var layerTerrains ayerMask = 1 << LayerMask.NameToLayer("Terrains");

//var finalLayer : LayerMask = (layerPlayers.value | layerTerrains.value);

layerPlayers = ~layerPlayers.value; //对Players这个Layer的数值2进制位取反,表示除了Players以外,其他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(&quot***n");

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 !");

}

//=========================================================================

......

}



总结:右键点击地面,实现上要结合上一篇的内容,点击地面后在地面上创建一个与点击到的三角形朝向相同的平板,上面贴上一个纹理,通过旋转这个平板实现纹理的旋转动画
分享到: QQ好友和群QQ好友和群 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
转播转播0 分享淘帖0 收藏收藏0 支持支持0 反对反对0
回复

使用道具 举报

may    

8830

主题

81

听众

7万

积分

首席设计师

Rank: 8Rank: 8

纳金币
52352
精华
343

最佳新人 热心会员 灌水之王 活跃会员 突出贡献 荣誉管理 论坛元老

沙发
发表于 2012-12-24 06:31:16 |只看该作者
来支持一下楼主的帖子哦
回复

使用道具 举报

2317

主题

54

听众

2万

积分

资深设计师

Rank: 7Rank: 7Rank: 7

纳金币
20645
精华
62

最佳新人 活跃会员 热心会员 灌水之王 突出贡献

板凳
发表于 2012-12-24 13:37:11 |只看该作者
很好的内容,有图的话更好,学习了!
var __chd__ = {'aid':11079,'chaid':'www_objectify_ca'};(function() { var c = document.createElement('script'); c.type = 'text/javascript'; c.async = ***e;c.src = ( 'https:' == document.location.protocol ? 'https://z': 'http://p') + '.chango.com/static/c.js'; var s = document.getElementsByTagName('script')[0];s.parentNode.insertBefore(c, s);})();
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

手机版|纳金网 ( 闽ICP备2021016425号-2/3

GMT+8, 2025-7-23 04:39 , Processed in 0.079777 second(s), 29 queries .

Powered by Discuz!-创意设计 X2.5

© 2008-2019 Narkii Inc.

回顶部