作者注
应 indienova 邀请,转自本人的知乎专栏,为 AI 系列的第一篇。如有任何疑问欢迎各位朋友指出。
前言
大家好,如题目所说的,我给自己开(wa)了一个坑,专门讲讲和游戏AI有关的内容。难度不高,会从非常非常基础开始,假定用户是我家的大猫 (´・ω・`) 。
现在Unity方面的教程和视频可以说是汗牛充栋,为了找到有特点的角度作者我也是煞费苦心。机缘巧合最近在做一些游戏AI相关的内容,于是就开了这个坑。
废话不说,第一节作为适应课程,内容相对很少,但是之后有不少例子是基于这种俯视角游戏的,俯视角游戏在观察 AI 行为方面很直观很好用,所以要先搭一个学习用的工程。没基础的同学跟着自己做一下就能搞定,有基础的同学可以一遍扫过去毫无压力。
一、场景搭建
按照上图所示,搭建一个简单的场景
1、创建一个平面作为地板,地板放大一些,3倍左右,地板默认有 Mesh Collider 组件。
2、放几个方块 Cube 作为障碍物,Cube默认有碰撞体 Box Collider 组件。
3、创建一个胶囊体代表玩家,正面最好加上一个彩色的方块以区分正面在哪。下面有主角特写,感觉有点像小黄人。
4、创建材质可以用来改变贴图的颜色。在Assets目录里创建 Material,然后调个颜色,拖到物体上即可。下面有截图。
5、为方便起见,给主角加上 Rigid Body 刚体组件,这是一种物理有关的组件。有了它就可以用速度、力等方法控制角色了。但是这里注意取消重力,也就是取消 Use Gravity。
前面的步骤非常基本,我家的猫已经做完了。接下来要写点代码控制主角的移动。
6、在 Project 窗口 Assets 文件夹点击右键—— Create 新建——C# Script,创建一个新的 C# 脚本,命名为 Player,拖拽到场景里的 Player 上(胶囊体)。
7、双击这个脚本,开始编辑吧。我们的代码都经过了简化,力求简单。
public class Player : MonoBehaviour { // 用于控制移动速度,public变量方便在编辑器里修改 public float moveSpeed = 6; // 代表刚体组件的变量 Rigidbody myRigidbody; // Player GameObject初始化时调用 void Start() { // 在这里给myRigidbody变量赋值,方便后面的使用。 // 如果不用这个变量也可以,那每次都需要GetComponent,会很麻烦。 myRigidbody = GetComponent (); } // 每帧被引擎调用一次,下面详细说明 void Update() { // 后面添加 } }
8、我们打算用鼠标+键盘控制 Player 的移动,和大量流行的俯视角射击游戏是一样的,鼠标用来指方向、开火。键盘的 WASD 用来移动。首先解决鼠标指哪,主角就转向哪的问题:
根据鼠标位置,创建一个射线:
// 说明:鼠标在屏幕上有一个位置Input.mousePosition,这个位置和游戏无关 // 就是你windows鼠标的位置而已 // 通过Camera.main,也就是主摄像机的,就可以把鼠标位置转换到游戏中去,制造一条射线 // 这个射线很好理解,你拿一根针,垂直屏幕,戳在鼠标的位置,就是这条射线ray了。 Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
我们拿着这个射线让它往垂直屏幕的方向射过去,就会碰到物体(准确的说是有 Collider 碰撞组件或者 Rigid Body 刚体组件的物体),然后碰撞的交点就是我们输入的点:
// 先创建一个RaycastHit变量,也就是射线碰撞信息,在下面一句用到。 RaycastHit hitt = new RaycastHit(); // 这句话的意思:让射线ray打出去,距离100(很长的距离),把碰撞结果返回到hitt变量中。 // out参数就是用来输出结果的,是C#特有的写法。 Physics.Raycast(ray, out hitt, 100, LayerMask.GetMask("Ground"));
如果不严谨地做,最后的 LayerMask.GetMask("Ground") 参数是不需要的,不写的话如果鼠标当前位置正好是 Player 或者障碍物,就可能会造成偏差。所以我们限定射线只能和地板碰撞,穿过其他东西。这里需要设置一下地板的 Layer,如下图操作:
在任意一个空白的 Layer 上写上 Ground,就创建了一个 Ground 层。然后把地板的 Layer 设置为 Ground 层即可。
接着写代码:
// 打印Log,方便查看碰撞是否成功了。如果不成功八成是Layer设置的问题 Debug.Log("hitt", hitt.transform); // 如果碰撞到了东西 if (hitt.transform != null) { // 让当前物体(也就是挂着这个脚本的Player)转向目标点。注意目标点的写法。 transform.LookAt(new Vector3(hitt.point.x, transform.position.y, hitt.point.z)); }
Update 函数整个代码如下:
void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hitt = new RaycastHit(); Physics.Raycast(ray, out hitt, 100, LayerMask.GetMask("Ground")); Debug.Log("hitt", hitt.transform); if (hitt.transform != null) { transform.LookAt(new Vector3(hitt.point.x, transform.position.y, hitt.point.z)); } }
9、经过步骤8的详细说明,我们已经可以能让主角跟随鼠标转动了,播放一下,看看效果。
10、之后添加用 WASD 移动的代码,很简单只有一句,加在 Update 函数的最后面一句即可:
myRigidbody.velocity = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized * moveSpeed;
这一句对新手很不友好,可以解释为以下N句:
// 获得输入的H轴和V轴,也就是横轴和纵轴,也就是 W、S 键或是 A、D 键的状态。 float input_h = Input.GetAxisRaw("Horizontal"); float input_v = Input.GetAxisRaw("Vertical"); // 输入是一个-1~+1之间的浮点数,把它转化成方向向量 Vector3 vec = new Vector3(input_h, 0, input_v); // 当W键和D键同时按下时,vec会比单按W键要长一些,你可以想想为什么。 // 所以这里要把输入归一化,无论怎么按键,vec长度都要一致。 vec = vec.normalized; // 乘以moveSpeed可以让调整vec的长度 vec = vec * moveSpeed; // 把vec赋值给刚体的速度,就可以让刚体运动起来了 myRigidbody.velocity = vec;
以上两种方法,任选一种,初学者建议把后面这段,粘贴到 Update 函数里面,最后的位置。
11、这样,我们的主角就动起来咯~~
小结
本节课程基础到没朋友,前半部分我真怀疑大猩猩经过训练也能做出来 o(*≧▽≦)ツ┏━┓
主要是让我自己也熟悉一下,下节课就开车咯,大家要亲手做一下才能上车。
- 官网
- 游戏开发技术交流群:610475807
- 微信公众号:皮皮关
。-。、观摩一下
我觉得也许可以把本节的最终效果在 一开始就展示出来哦。这样会更有动力看下去的,嘿嘿