射线

思考并回答以下问题:

  • 什么是射线?射线技术应用在哪?
  • 射线的基本原理是什么?
  • Vector3.ClampMagnitude是干嘛用的?
  • 开发一个发射子弹击中墙壁的功能。
  • 开发一个点击后走到目标点的功能。

本章涵盖:

  • 射线概述
  • 项目示例讲解
  • 本章练习与总结

点击下载资源

射线是Unity游戏引擎中非常重要的概念,尤其在动作、射击、RPG等游戏中应用广泛。初学者容易把射线技术简单理解成射击游戏的必要实现方式,其实两者有本质的不同。射线技术主要应用于3D虚拟世界中游戏对象的数值定位碰撞检测,以及确定游戏对象之间的前后顺序等方面。

本章在讲解完射线基本知识点后,使用“射击”与“角色寻路”两种典型应用场景来讲解射线的一般应用。

射线概述

射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射。射线的应用范围非常广泛,一般多用于游戏对象数值定位、碰撞检测(如子弹飞行是否击中目标)、角色移动等应用场景。

注意,这里所提到的“射线”是逻辑上的,界面上其实是看不到的,一般使用射线判断是否发射至某个游戏对象上或者获得鼠标单击的游戏对象等。

射线典型代码如代码清单1所示。

代码清单1 射线的基本定义

1
2
3
4
5
6
7
8
9
/* 射线的基本原理 */
// 定义一条从摄像机发射,沿着鼠标的方向无限延长的隐形射线。
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
// 获取射线碰撞到碰撞体的方位
_VecRayPosition = hit.point;
}

代码清单1中的代码是射线的基本定义,最后一行的“_VecRayPosition”是Vector3类型三维向量。

项目示例讲解

现在通过两个具体的示例小项目来演示射线在具体项目中的应用。

射击场景开发

图1显示了一个简易射击积木墙的应用场景,此场景中,玩家单击鼠标左键进行射击,当射击的“炮弹”远离摄像机范围时自动销毁。

核心代码如代码清单2所示。

代码清单2 RayDemo.cs绑定在摄像机或空游戏对象GameManager上

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
using UnityEngine;
using System.Collections;

public class RayDemo : MonoBehaviour
{
public Texture Texture_ShootingCursor; // 射击瞄准星
public GameObject GO_CubeOriginal; // 被射击的原型物体
private Vector3 _VecRayPosition; // 射线投射的物体

void Start ()
{
// 隐藏鼠标
Cursor.visible = false;
// 建立射击目标
for (int j = 1; j <= 5; j++)
{
for (int i = 1; i <=5 ; i++)
{
GameObject goClone = (GameObject)Instantiate(GO_CubeOriginal);
goClone.transform.position = new Vector3(GO_CubeOriginal.transform.position.x + i, GO_CubeOriginal.transform.position.y + j, GO_CubeOriginal.transform.position.z);
}
}
}

/*
另一种更好的写法,因为拖曳不常用,改为预制体直接加载

private GameObject wall;
private Transform wallParentTrs;

Cube变成预制体放进Resources文件夹
wall = Resources.Load<GameObject>("Wall");

然后把Cube放在一起,放在一个空对象下。
GameObject wallParent = GameObject.Find("WallParent");
wallParentTrs = wallParent.GetComponent<Transform>();
*/

void OnGUI()
{
Vector3 vecPos = Input.mousePosition;
GUI.DrawTexture(new Rect(vecPos.x - Texture_ShootingCursor.width / 2, Screen.height - vecPos.y - Texture_ShootingCursor.height / 2, Texture_ShootingCursor.width,Texture_ShootingCursor.height), Texture_ShootingCursor);
}
}

代码清单2中的“Start”事件函数定义了射击积木墙中5x5的目标靶墙,“OnGUI”事件函数使用GUI绘制射击光标。

代码清单3中的4-10行代码定义了从摄像机屏幕到鼠标单击位置的射线。13-26行代码定义了实体子弹,由22行代码的AddForce()方法通过给刚体增加“冲击力”的方式,把实体子弹射击出去。

代码清单3

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
void Update () 
{
/* 射线的基本原理 */
// 定义一条从摄像机发射,沿着鼠标的方向无限延长的隐形射线。
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
// 获取射线碰撞到碰撞体的方位
_VecRayPosition = hit.point;
}

// 如果鼠标点击左键,则发射子弹。
if(Input.GetMouseButtonDown(0))
{
// 创建子弹
GameObject goBullet = GameObject.CreatePrimitive(PrimitiveType.Sphere);
// 添加子弹的刚体
goBullet.AddComponent<Rigidbody>();
// 子弹的位置
goBullet.transform.position = Camera.main.transform.position;
// 给子弹加“力” 两个Vector3向量相减 目标点-出发点
goBullet.GetComponent<Rigidbody>().AddForce((_VecRayPosition - goBullet.transform.position) * 10F, ForceMode.Impulse); // Impulse 冲力
// 添加脚本: 如果子弹超出摄像机的范围,则进行销毁。
goBullet.AddComponent<DestroyGameobject>();
}
}

代码清单4中定义的“OnBecameInvisible()”方法由代码清单3中25行的AddComponent()方法给子弹对象自动赋值。这样,当子弹超出摄像机可视范围时,则进行自动销毁。

代码清单4 DestroyGameobject.cs不需绑定任何对象

1
2
3
4
5
6
7
8
public class DestroyGameobject : MonoBehaviour 
{
// 游戏对象超出摄像机范围,则执行以下代码
void OnBecameInvisible()
{
Destroy(this.gameObject);
}
}

角色寻路开发

图2展示了一个角色寻路的简单示例,其中图中红色部分是人物角色的模拟,其对象加载了“Character Controller”组件。当玩家单击地面的任何一个位置点时,其角色会立即“走”过去,代码实现如代码清单5所示。

图2 角色寻路场景

代码清单5中首先得到角色控制器,在Update()事件函数中使用射线技术确定鼠标在地面上的单击位置点,得到三维向量方位数值,保存到“VecGoalPosition”变量中。然后我们使用Vector3.ClampMagnitude()函数约束“角色”行走的步伐尺度,否则会出现角色移动过快,以及移动不自然等问题。

代码清单5 角色寻路脚本

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
public class RayDemo2_CCWalking : MonoBehaviour
{
private Vector3 VecGoalPosition; // 移动的目标位置
CharacterController CC; // 角色控制器

void Start()
{
// 得到角色控制器
CC = gameObject.GetComponent<CharacterController>();
}
void Update ()
{
// 确定移动位置
if (Input.GetMouseButton(0))
{
// 定义一个射线
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// 如果命中
if (Physics.Raycast(ray, out hit)){
VecGoalPosition = hit.point;
}
}
// 角色移动
if (Vector3.Distance(VecGoalPosition, this.transform.position) >1F)
{
// 移动的步伐
// Clamp 固定 Magnitude 大小
// Returns a copy of vector with its magnitude clamped to maxLength.
Vector3 step = Vector3.ClampMagnitude(VecGoalPosition - this.transform.position, 0.1f);
// 角色控制器的移动
CC.Move(step);
}
}
}

本章练习与总结

本章使用“射击积木墙”与“角色寻路”两个示例,讲解了射线的定义,以及射线在实际项目中的具体运用与作用。射线是一种通过与“碰撞体”进行碰撞检测后确定空间方位的技术,是一种编程的基本手段与技术。大家完全可以进一步扩展,以此开发出更多的游戏技能应用。

0%