Unity暑期萌新入门:静态敌人篇

作者:Truly
大渣好。
经过前几篇的学习,我们已经可以控制John在关卡里自由地行走,并且到达出口时能显示通关的结束界面了。接下来我们会在关卡里添加敌人,当玩家被敌人发现时,就会重新开始关卡。今天,我们先添加虽然不会动,但是喜欢在角落里蹲人的石像鬼。

一、设置石像鬼(Gargoyle)的预制体(推荐参照GIF快速操作)
制作敌人的方法跟我们制作主角的方法有点相似,就当作重温制作角色的步骤吧。我们要先把模型实例化为对象(GameObject)再进行操作。
流程:
1.创建石像鬼(Gargoyle)预制体
(1)在Project窗口中,打开文件夹Assets > Models > Characters,并找到Gargoyle 模型。
(2)把Gargoyle 模型拖动到Hierarchy窗口中,创建它的实例。
(3)把Hierarchy窗口中的Gargoyle GameObject拖到Assets> Prefabs文件夹中,创建预制体。此时会弹出对话框,选择Original Prefab。

2.编辑预制体
我们将会为Gargoyle预制体添加动画、碰撞体(Collider)和视野检测模拟(Trigger)。
在Hierarchy窗口中,点击Gargoyle GameObject右侧的箭头,打开预制体模式进行编辑。

(1)添加动画
石像鬼只有一个站立观看的Idle动画,把设置好动画的Animator Controller拖进预制体的Animator组件中的Controller属性栏中即可。
①在Project窗口中打开Assets > Animation > Animators文件夹。创建Animator Controller,命名为Gargoyle。

②双击新建的Gargoyle Animator Controller打开Animator视窗。
③在Project窗口中打开Assets > Animation > Animation文件夹。展开Gargoyle @ Idle,把Idle动画拖进Animator视窗中。
④把设置好的Gargoyle(Animator Controller)拖进Gargoyle预制体的Animator组件的Controller属性框。

(2)添加Collider(碰撞体)
①在Inspector窗口中,点击Add Component,搜索并添加Capsule Collider。
②设置Collider参数,使碰撞体更加贴合石像鬼:
Center:(0, 0.9, 0)
Radius:0.3
Height:1.8

(3)添加模拟检测范围的Trigger(触发器)
在通关界面篇,我们设置Trigger来检测玩家是否到达出口,这里我们用相同的方法给石像鬼设置Trigger模拟检测范围。
①在Gargoyle GameObject下新建一个空的子对象(Create Empty),按F2重命名为PointOfView。
②设置PointOfView的Transform组件,调节模拟视线的位置和旋转:
Position(位置) : ( 0, 1.4, 0.4)
Rotation(旋转) : ( 20, 0, 0)
设置完成后,确保旋转切换工具是Local的情况下,可以看到PointOfViewde坐标的Z轴会从石像鬼的头部指向斜下。

③在PointOfView的Inspector中点击Add Component,搜索并添加Capsule Collider。
④设置Collider参数:
在Capsule Collider组件里勾选Is Trigger,转换为触发器。
Center(中心) :( 0, 0, 0.95)
Radius(半径) :0.7
Height(高) :2
Direction(方向):下拉菜单改为Z-Axis

(4)场景中摆放Gargoyle GameObject
①点击Hierarchy窗口中,Gargoyle左侧的 < 退出预制体模式。

②我们返回Scene场景中,设置Gargoyle GameObject的Transform组件:
Position:(-15.2 ,0 ,0.8 )
Rotation:(0 ,135 ,0 )

完成后Ctrl+S保存场景。我们为石像鬼的预制体添加并设置了动画、碰撞体以及模拟检测范围的触发器,已经完成了大部分的基本设置。

二、设置被抓界面
当判定玩家被抓时,会淡入一个被抓的界面,做法跟上一篇通关界面相似。我们偷懒一下,复制一份胜利界面,把通关前景图替换为被抓的前景图就可以了~
1.展开FaderCanvas,选中ExitImageBackground GameObject,Ctrl + D进行复制。
2.把复制出来的ExitImageBackground(1) 重命名为CaughtImageBackground。展开子项,把ExitImage 重命名为CaughtImage。

3.在Inspector窗口中找到CaughtImage 的Image组件,点击Source Image右侧圆圈,选择Caught。完成后Ctrl+S保存场景。

三、编写脚本
1.思路
(1)判断玩家是否进入检测范围。
(2)如果玩家在检测范围内,进行视线模拟,避免隔着障碍物(墙)还能看到玩家。
(3)玩家被发现后,淡入被抓的结束界面。
(4)结束界面淡入完成后,重新加载游戏。
2.新建脚本并添加为组件
(1)打开Asset > Scripts文件夹,新建脚本并命名为Observer。
(2)打开Gargoyle预制体模式,把Observer脚本拖到Hierarchy里的PointOfView GameObject中,使其添加为组件。
3.检测玩家
(1)双击打开Observer脚本。
(2)范围检测
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Observer : MonoBehaviour
{
public Transform player; //玩家
public GameEnding gameEnding; //GameEnding脚本
bool m_IsPlayerInRange; //玩家是否在探测范围
//玩家进入检测范围
private void OnTriggerEnter(Collider other)
{
if (other.transform == player)
{
m_IsPlayerInRange = true;
}
}
//玩家离开检测范围
private void OnTriggerExit(Collider other)
{
if (other.transform == player)
{
m_IsPlayerInRange = false;
}
}
(3)视线模拟
当玩家进入检测范围时,为了避免在墙后也能发现玩家的情况,因此在Update()里我们用射线(Ray)进行视线模拟。射线从敌人的位置射向玩家,当射线直接打到玩家时,淡入结束界面。由于玩家的position在脚底,所以我们加上Vector3.up(0,1,0),让射线能够往玩家身上打。
//每帧执行
private void Update()
{
//如果玩家进入灯光范围,用射线模拟视线检测
if (m_IsPlayerInRange)
{
//射线方向为从敌人指向玩家
Vector3 direction = player.position + Vector3.up - transform.position ;
Ray ray = new Ray(transform.position, direction); //(射线的起点,方向)
RaycastHit raycastHit; //储存射线的碰撞信息
if (Physics.Raycast(ray, out raycastHit))
{
if (raycastHit.collider.transform == player) //如果射线打中的是玩家
{
gameEnding.CaughtPlayer(); //玩家被抓
}
}
}
}
}
gameEnding.CaughtPlayer(); 这里暂时会报红,完成下边的步骤后便会解决。
到这一步我们完成了视线检测,Ctrl + S保存脚本。
4.修改GameEnding脚本(建议直接看修改后脚本)
当玩家在检测范围内,并且能被射线能直接打中时,淡入被抓的结束界面,这时候我们需要回到GameEnding脚本进行编辑。
(1)增加变量
public CanvasGroup CaughtImageBackgroud:被抓的界面
bool m_IsPlayerCaught :玩家是否被抓

(2)为EndLevel()添加参数
当玩家通关或者被抓时,控制对应的界面淡入,淡入完成后判断是否重新载入游戏,因此我们需用CanvasGroup(胜利/失败界面)和bool(是否重载游戏)变量作为参数。
void EndLevel(CanvasGroup imageCanvasGroup,bool doRestart)
(3)重载场景
重载场景需要在脚本上方添加命名空间:using UnityEngine.SceneManagement;
场景的索引从0开始数且我们只有一个场景,因此我们重启的场景用:SceneManager.LoadScene(0);

(4)Update()
每帧检测时,我们需要判断玩家是到达终点还是被抓,然后给EndLevel()输入对应的参数。

修改后的脚本:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameEnding : MonoBehaviour
{
public float fadeDuration = 1f; //淡入淡出时间1S
public float displayImageDuration = 1f; //淡入完毕后等待时间1S
public GameObject player; //玩家
public CanvasGroup exitBackgroundImageCanvasGroup; //用于调节通关界面alpha值的CanvasGroup
public CanvasGroup caughtBackgroundImageCanvasGroup;//用于调节被抓界面alpha值的CanvasGroup
bool m_IsPlayerAtExit; //玩家是否到终点
bool m_IsPlayerCaught; //玩家是否被抓
float m_Timer; //计时器
/// <summary>
/// 达到出口,进入触发器时调用
/// </summary>
/// <param name="other"></param>
private void OnTriggerEnter(Collider other)
{
//如果进入触发器范围的是玩家,改变布尔变量
if (other.gameObject == player)
{
m_IsPlayerAtExit = true;
}
}
//玩家被抓时在Observer脚本里调用
public void CaughtPlayer()
{
m_IsPlayerCaught = true;
}
/// <summary>
/// 每帧调用
/// </summary>
private void Update()
{
if (m_IsPlayerAtExit) //如果玩家到达出口,执行EndLevel();
{
EndLevel(exitBackgroundImageCanvasGroup,false);
}
else if (m_IsPlayerCaught) //如果玩家被抓,执行重新加载场景;
{
EndLevel(caughtBackgroundImageCanvasGroup,true);
}
}
/// <summary>
/// 关卡结束
/// </summary>
void EndLevel(CanvasGroup imageCanvasGroup,bool doRestart)
{
m_Timer = m_Timer + Time.deltaTime; //计时器,每帧累加时间
//alpha值随着百分比改变
imageCanvasGroup.alpha = m_Timer / fadeDuration;
//当计时器时间大于总等待时间
if (m_Timer > fadeDuration + displayImageDuration)
{
if (doRestart)
{
SceneManager.LoadScene(0);//场景载入,只有一个场景,下标为0
}
else
{
Application.Quit();//退出游戏,这个要游戏打包后才会执行,在Unity里不会关闭游戏。
}
}
}
}
修改完成后保存脚本。
四、在Unity中为脚本引用赋值
(1)选中PointOfView GameObject,为Observer脚本的引用赋值。

(2)选中GameEnding GameObject,为GameEnding脚本的引用赋值。

进行到这里,石像鬼设置基本完成,Ctrl + S保存场景。
结语:这一篇中我们添加并设置了石像鬼的预制体(动画、碰撞盒和模拟检测范围的触发器),在脚本中用射线模拟视线检测。运行游戏,试试石像鬼的检测是否奏效吧~下一篇,我们将添加动态的敌人---幽灵,结合在环境篇中设置的NavMesh,使幽灵可以在设定的路线上巡逻。
迫不及待想自行开始制作的小伙伴,可以浏览John Lemon's Haunted Jaunt官方教程:https://learn.unity.com/project/john-lemon-s-haunted-jaunt-3d-beginner
咱们的游戏开发交流群也欢迎强势插入:869551769
希望参与线下游戏开发学习的,欢~~~~~~迎访问:http://levelpp.com/