网盘存档
start方法里的代码会在运行第一帧被执行
而update里的方法会被一直执行
GameObject-Align with views,可即时地切换摄像机角度,快捷键crtl+SHIFT+F
在项目运行期间进行的修改不会被记录
按住alt键点击小三角能直接打开全部目录
直接吧资源拖到prefab文件夹就能创建预制体
在状态机右键可以设定默认状态
在inspector界面点击open可以关闭自动保存
通过左上角方块回到正常页面
手动保存的方法
刚体rigidbody
选中模型,add component-rigidbody
这两个有冲突,会造成物理重力不正常
为此关掉root motion
寻路
需要把整个场景都烘焙,在此之前需要在inspector里先把整个level勾选上static,因为烘焙只能烘焙静态物品,但是天花板的文件涉及npc活动,因此需要接触天花板的static,然后再烘焙,烘焙之前吧radius调整成0.25
07-游戏人物的控制(上)
private void FixedUpdate()
这个FIxedUpdate是以固定帧时长去执行,有利于动画更流畅
定义获取一系列组件
//定义一个旋转的速度
public float turnspeed = 20f;
//定义游戏人物上的组件,至少有状态机和刚体
Animator m_Animator;
Rigidbody m_Rigidbody;
//定义游戏人物移动的矢量
Vector3 m_Movemont;
//定义游戏人物旋转的角度
Quaternion m_Quaternion=Quaternion.identity;
// Start is called before the first frame update
void Start()
{
//这一步是为了获取人物上的刚体,动画状态机组件
m_Animator = GetComponent<Animator>();
m_Rigidbody = GetComponent<Rigidbody>();
}
控制3D角色移动的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
//定义一个旋转的速度
public float turnspeed = 20f;
//定义游戏人物上的组件,至少有状态机和刚体
Animator m_Animator;
Rigidbody m_Rigidbody;
//定义游戏人物移动的矢量
Vector3 m_Movemont;
//定义游戏人物旋转的角度
Quaternion m_Quaternion=Quaternion.identity;
// Start is called before the first frame update
void Start()
{
//这一步是为了获取人物上的刚体,动画状态机组件
m_Animator = GetComponent<Animator>();
m_Rigidbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
private void FixedUpdate()
{
//获取水平方向输入
float horizontal = Input.GetAxis("Horizontal");
//获取竖直方向输入
float vertical = Input.GetAxis("Vertical");
//设置游戏人物移动的方向
m_Movemont.Set(horizontal, 0f, vertical);
m_Movemont.Normalize();//这一步是为了让方向归一化
//定义人物是否移动的bool值
bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);//0f是false,horizontal为true,加感叹号是为了负负得正
bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);
//定义不管哪个方向有输入,都会播放动画,使用与方法
bool iswalking = hasHorizontalInput||hasVerticalInput;
m_Animator.SetBool("IsWalking", iswalking);//主要这里写的字符参数是和动画状态机里的命名一致的
//旋转的过渡 第一个参数是当前的位置,第二个是目标的位置,第三个是旋转的速度
//通过三元数转四元数的方法,获取游戏人物当前应有的角度
Vector3 desirForward = Vector3.RotateTowards(transform.forward,m_Movemont,turnspeed*Time.deltaTime,0f);
//定义一个新的变量,每帧到达的旋转位置
m_Quaternion = Quaternion.LookRotation(desirForward);
}
//做游戏人物的移动,这个OnAnimatorMove的方法前提是人物有动画状态机
private void OnAnimatorMove()
{//当前位置加上方向乘以动画根运动的大小
m_Rigidbody.MovePosition(m_Rigidbody.position + m_Movemont * m_Animator.deltaPosition.magnitude);
//现在把旋转的角度指定到人物上
m_Rigidbody.MoveRotation(m_Quaternion);//括号里写的是当前人物的角度
}
}
摄像机跟随
1 像机跟随脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFollow1 : MonoBehaviour
{
//定义跟随的对象:游戏人物
private Transform player;
//定义相机与任务之间的距离
Vector3 offset;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("JohnLemon").transform;
offset = transform.position - player.position;
}
// Update is called once per frame
void Update()
{
//更新相机的位置
transform.position = offset + player.position;
}
}
直接用官方Cinemachine插件
先设置follow
这里改成do nothing
body改成framing transposer
摄像机角度也需要改一下
这里是摄像机距离
10-游戏结束界面的基本设置
游戏胜利触发的设定
使用3Dobject-cube做触发
去掉材质选项就能作为透明/空气触发器存在
作为触发器需要勾选上这个
按住alt点击这个技能让画面铺满画布
添加这个组件
作为背景,颜色设置为黑色
组件的Alpha值能改变不透明度
勾选这个功能避免拉伸
接下来通过脚本控制Alpha值控制渐变弹出效果
注意Alpha值会一并改变子物体的不透明度
结束动画脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameEnding : MonoBehaviour
{
//定义游戏胜利时图片淡入淡出的时间
public float fadeDuration = 1f;
//游戏胜利图片显示的时间
public float displayDuration = 1f;
//定义一个游戏人物变量
public GameObject Player;
//定义画布背景
public CanvasGroup ExitBK;
//定义一个游戏胜利时的bool值
bool IsExit;
//定义计时器,用于图片的渐变与完全显示
public float timer=0f;
//使用触发器方法OnTriggerEnter
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
{
//检测到玩家
//游戏胜利
IsExit = true;
}
}
// Update is called once per frame
void Update()
{
if (IsExit)
{
//检测执行游戏胜利的方法
EndLevel();
}
}
//游戏胜利的方法
void EndLevel()
{
//玩家碰到触发器时,计时器开始计时
timer += Time.deltaTime;
//控制CanvasGroup的不透明度
ExitBK.alpha = timer / fadeDuration;
//图片显示两秒(渐变一秒,显示一秒)之后游戏结束
if (timer>fadeDuration+displayDuration)
{
Application.Quit();
}
}
}
12-静态敌人的创建
给雕塑添加碰撞体
创建空物体做触发器
接下来给雕像加动画,然后做成预制体
动态敌人的创建(上)
幽灵需要在刚体里勾上这个,这样的话幽灵就可以穿过玩家,但即使穿过了玩家,仍然可以造成碰撞效果
以下为全部脚本
摄像机跟随
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFollow1 : MonoBehaviour
{
//定义跟随的对象:游戏人物
private Transform player;
//定义相机与任务之间的距离
Vector3 offset;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("JohnLemon").transform;
offset = transform.position - player.position;
}
// Update is called once per frame
void Update()
{
//更新相机的位置
transform.position = offset + player.position;
}
}
结束,重生与音效
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameEnding : MonoBehaviour
{
//定义游戏胜利时图片淡入淡出的时间
public float fadeDuration = 1f;
//游戏胜利图片显示的时间
public float displayDuration = 1f;
//定义一个游戏人物变量
public GameObject Player;
//定义画布背景
public CanvasGroup ExitBK;
//定义一个游戏胜利时的bool值
bool IsExit = false;
//定义计时器,用于图片的渐变与完全显示
public float timer=0f;
//游戏失败的画布背景
public CanvasGroup FailBK;
//游戏失败时的bool值
bool IsPlay = false;
//音频组件
public AudioSource winaudio;
public AudioSource failaudio;
//bool控制音效只播放一次
bool Isolay = false;
//使用触发器方法OnTriggerEnter
private void OnTriggerEnter(Collider other)
{
if (other.gameObject == Player)
{
//检测到玩家
//游戏胜利
IsExit = true;
}
}
//游戏失败时的控制函数
public void Caught()
{
IsPlay = true;
}
// Update is called once per frame
void Update()
{
if (IsExit)
{
//检测执行游戏胜利或者失败的方法
EndLevel(ExitBK, false,winaudio);
}else if (IsPlay)
{
EndLevel(FailBK, true,failaudio);
}
}
//游戏胜利或失败的方法
void EndLevel(CanvasGroup igCanvasGroup, bool doRestart,AudioSource playaudio)
{
//游戏胜利或失败的音效播放
if (!IsPlay)
{
playaudio.Play();
IsPlay = true;
}
//玩家碰到触发器时,计时器开始计时
timer += Time.deltaTime;
//控制CanvasGroup的不透明度
igCanvasGroup.alpha = timer / fadeDuration;
//图片显示两秒(渐变一秒,显示一秒)之后游戏结束
if (timer > fadeDuration + displayDuration)
{
//游戏失败,重启游戏
if (doRestart)
{
SceneManager.LoadScene("SampleScene");
}
else if (!doRestart)//游戏胜利,退出游戏
{
Application.Quit();
}
}
}
}
光照
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class LightFlicker : MonoBehaviour
{
public enum FlickerMode
{
Random,
AnimationCurve
}
public Light flickeringLight;
public Renderer flickeringRenderer;
public FlickerMode flickerMode;
public float lightIntensityMin = 1.25f;
public float lightIntensityMax = 2.25f;
public float flickerDuration = 0.075f;
public AnimationCurve intensityCurve;
Material m_FlickeringMaterial;
Color m_EmissionColor;
float m_Timer;
float m_FlickerLightIntensity;
static readonly int k_EmissionColorID = Shader.PropertyToID (k_EmissiveColorName);
const string k_EmissiveColorName = "_EmissionColor";
const string k_EmissionName = "_Emission";
const float k_LightIntensityToEmission = 2f / 3f;
void Start()
{
m_FlickeringMaterial = flickeringRenderer.material;
m_FlickeringMaterial.EnableKeyword(k_EmissionName);
m_EmissionColor = m_FlickeringMaterial.GetColor(k_EmissionColorID);
}
void Update()
{
m_Timer += Time.deltaTime;
if (flickerMode == FlickerMode.Random)
{
if (m_Timer >= flickerDuration)
{
ChangeRandomFlickerLightIntensity ();
}
}
else if(flickerMode == FlickerMode.AnimationCurve)
{
ChangeAnimatedFlickerLightIntensity ();
}
flickeringLight.intensity = m_FlickerLightIntensity;
m_FlickeringMaterial.SetColor (k_EmissionColorID, m_EmissionColor * m_FlickerLightIntensity * k_LightIntensityToEmission);
}
void ChangeRandomFlickerLightIntensity ()
{
m_FlickerLightIntensity = Random.Range(lightIntensityMin, lightIntensityMax);
m_Timer = 0f;
}
void ChangeAnimatedFlickerLightIntensity ()
{
m_FlickerLightIntensity = intensityCurve.Evaluate (m_Timer);
if (m_Timer >= intensityCurve[intensityCurve.length - 1].time)
m_Timer = intensityCurve[0].time;
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(LightFlicker))]
public class LightFlickerEditor : Editor
{
SerializedProperty m_ScriptProp;
SerializedProperty m_FlickeringLightProp;
SerializedProperty m_FlickeringRendererProp;
SerializedProperty m_FlickerModeProp;
SerializedProperty m_LightIntensityMinProp;
SerializedProperty m_LightIntensityMaxProp;
SerializedProperty m_FlickerDurationProp;
SerializedProperty m_IntensityCurveProp;
void OnEnable ()
{
m_ScriptProp = serializedObject.FindProperty ("m_Script");
m_FlickeringLightProp = serializedObject.FindProperty ("flickeringLight");
m_FlickeringRendererProp = serializedObject.FindProperty ("flickeringRenderer");
m_FlickerModeProp = serializedObject.FindProperty ("flickerMode");
m_LightIntensityMinProp = serializedObject.FindProperty ("lightIntensityMin");
m_LightIntensityMaxProp = serializedObject.FindProperty ("lightIntensityMax");
m_FlickerDurationProp = serializedObject.FindProperty ("flickerDuration");
m_IntensityCurveProp = serializedObject.FindProperty ("intensityCurve");
}
public override void OnInspectorGUI ()
{
serializedObject.Update ();
GUI.enabled = false;
EditorGUILayout.PropertyField (m_ScriptProp);
GUI.enabled = true;
EditorGUILayout.PropertyField (m_FlickeringLightProp);
EditorGUILayout.PropertyField (m_FlickeringRendererProp);
EditorGUILayout.PropertyField (m_FlickerModeProp);
if (m_FlickerModeProp.enumValueIndex == 0)
{
EditorGUILayout.PropertyField (m_LightIntensityMinProp);
EditorGUILayout.PropertyField (m_LightIntensityMaxProp);
EditorGUILayout.PropertyField (m_FlickerDurationProp);
}
else if (m_FlickerModeProp.enumValueIndex == 1)
{
EditorGUILayout.PropertyField (m_IntensityCurveProp);
}
serializedObject.ApplyModifiedProperties ();
}
/*public Light flickeringLight;
public Renderer flickeringRenderer;
public FlickerMode flickerMode;
public float lightIntensityMin = 1.25f;
public float lightIntensityMax = 2.25f;
public float flickerDuration = 0.075f;
public AnimationCurve intensityCurve;*/
}
#endif
潜行侦查
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Observer : MonoBehaviour
{
//游戏角色
public Transform Player;
//触发结束,使用GameEnding脚本
public GameEnding gameEnding;
//玩家是否被检测到
bool IsInRange = false;
private void OnTriggerEnter(Collider other)
{
//玩家被静态敌人发现
if (other.gameObject == Player.gameObject)
{
IsInRange = true;
}
}
//有ontriggerenter就有ontriggerexit
//扫描到玩家离开了
private void OnTriggerExit(Collider other)
{
if(other.gameObject==Player.gameObject)
IsInRange = false;
}
// Update is called once per frame
private void Update()
{
if (IsInRange == true)
{
//射线检测
Vector3 dir = Player.position - transform.position + Vector3.up;
Ray ray = new Ray(transform.position, dir);
RaycastHit raycastHit;
if(Physics.Raycast(ray,out raycastHit))
{
if (raycastHit.collider.transform==Player)
{
//游戏失败,玩家被抓住,调用GameEnding里的Caught方法
gameEnding.Caught();
}
}
}
}
}
玩家移动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
//定义一个旋转的速度
public float turnspeed = 20f;
//定义游戏人物上的组件,至少有状态机和刚体
Animator m_Animator;
Rigidbody m_Rigidbody;
//定义游戏人物移动的矢量
Vector3 m_Movemont;
//定义游戏人物旋转的角度
Quaternion m_Quaternion=Quaternion.identity;
// Start is called before the first frame update
void Start()
{
//这一步是为了获取人物上的刚体,动画状态机组件
m_Animator = GetComponent<Animator>();
m_Rigidbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
private void FixedUpdate()
{
//获取水平方向输入
float horizontal = Input.GetAxis("Horizontal");
//获取竖直方向输入
float vertical = Input.GetAxis("Vertical");
//设置游戏人物移动的方向
m_Movemont.Set(horizontal, 0f, vertical);
m_Movemont.Normalize();//这一步是为了让方向归一化
//定义人物是否移动的bool值
bool hasHorizontalInput = !Mathf.Approximately(horizontal, 0f);//0f是false,horizontal为true,加感叹号是为了负负得正
bool hasVerticalInput = !Mathf.Approximately(vertical, 0f);
//定义不管哪个方向有输入,都会播放动画,使用与方法
bool iswalking = hasHorizontalInput||hasVerticalInput;
m_Animator.SetBool("IsWalking", iswalking);//主要这里写的字符参数是和动画状态机里的命名一致的
//旋转的过渡 第一个参数是当前的位置,第二个是目标的位置,第三个是旋转的速度
//通过三元数转四元数的方法,获取游戏人物当前应有的角度
Vector3 desirForward = Vector3.RotateTowards(transform.forward,m_Movemont,turnspeed*Time.deltaTime,0f);
//定义一个新的变量,每帧到达的旋转位置
m_Quaternion = Quaternion.LookRotation(desirForward);
}
//做游戏人物的移动,这个OnAnimatorMove的方法前提是人物有动画状态机
private void OnAnimatorMove()
{//当前位置加上方向乘以动画根运动的大小
m_Rigidbody.MovePosition(m_Rigidbody.position + m_Movemont * m_Animator.deltaPosition.magnitude);
//现在把旋转的角度指定到人物上
m_Rigidbody.MoveRotation(m_Quaternion);//括号里写的是当前人物的角度
}
}
AI寻路
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class WayPointPatrol : MonoBehaviour
{
//导航组件
private NavMeshAgent navMeshAgent;
//导航点的数组
public Transform[] waypoints;
//当前巡逻的目标点
int m_currentpointIndex;
// Start is called before the first frame update
void Start()
{
//获取navmeshagent组件
navMeshAgent = GetComponent<NavMeshAgent>();
//从起点到达第一个巡逻点
navMeshAgent.SetDestination(waypoints[0].position);
}
// Update is called once per frame
void Update()
{
//到达目标点,前往下一个目标点
if (navMeshAgent.remainingDistance < navMeshAgent.stoppingDistance)
{
m_currentpointIndex = (m_currentpointIndex + 1) % waypoints.Length;
navMeshAgent.SetDestination(waypoints[m_currentpointIndex].position);
}
}
}