- MonoBehaviour类:所有Unity脚本的基类,提供大部分Unity功能。如果脚本不是继承自MonoBehaviour,则无法将这个脚本作为组件运行。
- Transform类:每个Game Object都包含的默认组件,功能:平移、旋转、缩放(这三个在 Inspector 面板可以设置),以及层级面板(Hierarchy)中的父/子关系【子对象随父对象移动】。
- Rigid Body(2D)类:一种组件,提供所有物理功能。
void Awake(); // 实例化后最先执行的函数,只执行一次
void OnEnable(); // 脚本可用后,执行一次;因为可以关闭脚本,所以可以==反复执行==,在Awake之后执行
void Start(); // 进入Update函数之前执行,只执行一次,在OnEnable之后执行
void Update(); // 程序主循环,每帧执行一次 帧相关
void LateUpdate(); // 在Update之后执行 帧相关
void FixedUpdate(); // 这个函数的执行频率可以自定义,通过 Unity【Project】->【Time】自定义 Fixed Timestep。
void OnDestroy() //销毁时调用
Debug.Log(“awake”);
1.Vector
void Start()
{
//向量,坐标,旋转,缩放
Vector3 v = new Vector3();
v = Vector3.zero;
v = Vector3.right;
Vector3 v2 = Vector3.forward;
//计算两个向量夹角
Debug.Log(Vector3.Angle(v,v2));
//计算两点之间距离
Debug.Log(Vector3.Distance(v, v2));
//点乘
Debug.Log(Vector3.Dot(v, v2));
//叉乘
Debug.Log(Vector3.Cross(v, v2));
//插值
Debug.Log(Vector3.Lerp(Vector3.zero, Vector3.one, 0.8f));
//向量的模
Debug.Log(v.magnitude);
//规范化向量
Debug.Log(v.normalized);
}
2.欧拉角和四元数
void Start()
{
//旋转:欧拉角,四元数
Vector3 rotate = new Vector3(0, 30, 0);
Quaternion quaternion = Quaternion.identity;//四元数
//欧拉角转为四元数
quaternion = Quaternion.Euler(rotate);
//四元数转换为欧拉角
rotate = quaternion.eulerAngles;
//看向一个物体
quaternion = Quaternion.LookRotation(new Vector3(0, 0, 0));
}
3.debug
Debug.Log();
Debug.LogWarning();
Debug.LogError();
4.代码修改物体属性
void Start()
{
//拿到当前脚本所挂载的物体
GameObject gameObject = this.gameObject;
//游戏物体的名称
Debug.Log(gameObject.name);
//游戏物体的tag
Debug.Log(gameObject.tag);
//立方体名称
Debug.Log(cube.name);
//当前自身真正激活状态
Debug.Log(cube.activeInHierarchy);
//当前自身激活状态
Debug.Log(cube.activeSelf);
//获取transform组件
Transform transform = this.transform;
//获取其他组件
BoxCollider collider = GetComponent<BoxCollider>();
//获取当前物体的子物体身上的某个组件
GetComponentInChildren<CapsuleCollider>();
//获取当前物体的父物体身上的某个组件
GetComponentInParent<CapsuleCollider>();
//添加一个组件
gameObject.AddComponent<AudioSource>();
//通过游戏物体的名称来获取物体
GameObject test = GameObject.Find("test");
//通过游戏标签来获取物体
test = GameObject.FindWithTag("Enemy");
test.SetActive(false);
Debug.Log(test.name);
}
5.预设体实例化成游戏物体
//获取预设体
public GameObject prefab;
//通过预设体来实例化一个游戏物体
Instantiate(prefab);
//游戏物体的子物体
Instantiate(prefab,transform);
//世界中心,不旋转
GameObject go = Instantiate(prefab,Vector3.zero,Quaternion.identity);
//销毁
Destroy(go);
6.脚本时间
//游戏开始到现在的时间
Debug.Log(Time.time);
//时间缩放值
Debug.Log(Time.timeScale);
//固定时间间隔
Debug.Log(Time.fixedDeltaTime);
//上一帧到这一帧所用的时间;
Debug.Log(Time.deltaTime);
6-1 计时器
timer += Time.deltaTime;
//上一帧到这一帧所用的时间;
Debug.Log(Time.deltaTime);
if (timer > 3)
{
Debug.Log("大于3秒了");
}
7 application路径
//游戏数据文件夹路径
Debug.Log(Application.dataPath + "/新建文件.txt");
//持久化文件夹路径 --存档
Debug.Log(Application.persistentDataPath);
//streamingAssetsPath路径(只读,配置文件)
Debug.Log(Application.streamingAssetsPath);
//临时文件夹
Debug.Log(Application.temporaryCachePath);
//控制是否在后台运行
Debug.Log(Application.runInBackground);
//打开url
Application.OpenURL("https://bilibili.com");
//退出游戏
Application .Quit();
8 场景管理类
//场景类、场景管理类
SceneManager.LoadScene("base");
//获取当前场景
UnityEngine.SceneManagement.Scene scene = SceneManager.GetActiveScene();
//场景名称
Debug.Log(scene.name);
//场景是否已经加载
Debug.Log(scene.isLoaded);
//场景路径
Debug.Log(scene.path);
//场景索引
Debug.Log(scene.buildIndex);
//获取当前场景下全部的游戏物体
GameObject[] gos = scene.GetRootGameObjects();
foreach (GameObject go in gos)
{
Debug.Log(go.name);
}
//场景管理类
UnityEngine.SceneManagement.Scene scene2 =SceneManager.CreateScene("newscene");
//已加载场景个数
Debug.Log(SceneManager.sceneCount);
//销毁场景
SceneManager.UnloadSceneAsync(scene2);
//加载场景Single(加载单场景)、Additive(叠加场景)
SceneManager.LoadScene("base",LoadSceneMode.Additive);
9 异步场景加载
AsyncOperation operation;
void Start()
{
StartCoroutine(loadScene());
}
//协程方法用来异步加载场景
IEnumerator loadScene()
{
operation = SceneManager.LoadSceneAsync(1);
//加载完场景不要自动跳转
operation.allowSceneActivation = false;
yield return operation;
}
float timer = 0;
// Update is called once per frame
void Update()
{
Debug.Log(operation.progress);
timer += Time.deltaTime;
if (timer > 5)
{
operation.allowSceneActivation = true;
}
}
10 位置旋转缩放
父子集通过transform
public class transformtest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
//获取位置
Debug.Log(transform.position);//世界位置
Debug.Log(transform.localPosition);//相对位置
//获取旋转
Debug.Log(transform.rotation);
Debug.Log(transform.localRotation);
Debug.Log(transform.localEulerAngles);
//获取缩放
Debug.Log(transform.localScale);//只有相对父物体的缩放
//向量
Debug.Log(transform.forward);
Debug.Log(transform.right);
Debug.Log(transform.up);
//父子关系
_ = transform.parent.gameObject;
//获取子物体个数
Debug.Log(transform.childCount);
//解除和子物体的关系
//transform.DetachChildren();
//获取子物体
Transform a = transform.Find("child");
a = transform.GetChild(0);
Debug.Log(a);
//判断一个物体是不是另外一个物体的子物体
bool res = a.IsChildOf(transform);
Debug.Log(res);
//设置父物体
a.SetParent(transform);
}
// Update is called once per frame
void Update()
{
//一直看向000
//transform.LookAt(Vector3.zero);
//旋转
//transform.Rotate(Vector3.up,1);
//绕某个物体旋转 参数: 物体、 轴 每帧的速度
//transform.RotateAround(Vector3.zero,Vector3.up,5);
//移动
//transform.Translate(Vector3.forward*0.1f);
}
}
11.键盘鼠标点击
//鼠标点击
//按下鼠标
if (Input.GetMouseButtonDown(0))
{
Debug.Log("按下了鼠标左键");
}
//持续按下鼠标
if (Input.GetMouseButton(0))
{
Debug.Log("持续按下鼠标");
}
//抬起鼠标
if (Input.GetMouseButtonUp(0))
{
Debug.Log("抬起鼠标");
}
//按下键盘
if (Input.GetKeyDown(KeyCode.A))
{
Debug.Log("按下A");
}
//持续按下
if (Input.GetKey(KeyCode.A);
{
Debug.Log("持续按下A");
}
//抬起键盘
if (Input.GetKeyUp(KeyCode.A))
{
Debug.Log("松开A");
}
12 单多点触摸
void Update()
{
//判断单点触摸
if (Input.touchCount == 1)
{
//触摸对象
Touch touch0 = Input.touches[0];
//触摸位置
Debug.Log(touch0.position);
//触摸阶段
switch (touch0.phase)
{
case TouchPhase.Began:
break;
case TouchPhase.Moved:
break;
case TouchPhase.Stationary:
break;
case TouchPhase.Ended:
break;
case TouchPhase.Canceled:
break;
}
}
if (Input.touchCount == 2)
{
Touch touch1 = Input.touches[0];
Touch touch2 = Input.touches[1];
}
}
13 播放声音
public AudioClip music;
public AudioClip se;
//播放器组件
private AudioSource player;
// Start is called before the first frame update
void Start()
{
player = GetComponent<AudioSource>();
//设定播放
player.clip = music;
//循环
player.loop = true;
//音量
player.volume = 0.5f;
}
// Update is called once per frame
void Update()
{
//用空格切换声音播放和暂停
if (Input.GetKeyDown(KeyCode.Space))
{
if (player.isPlaying)
{
player.Pause();
}
else
{
player.Play();
}
}
//按鼠标左键播放声音
if (Input.GetMouseButtonDown(0))
{
player.PlayOneShot(se);
}
}
14 移动
private CharacterController player;
// Start is called before the first frame update
void Start()
{
player = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
//水平轴
float horizontal = Input.GetAxis("Horizontal");
//垂直轴
float vertical = Input.GetAxis("Vertical");
//创建一个方向向量
Vector3 dir = new Vector3(horizontal, 0, vertical);
Debug.DrawRay(transform.position,dir,Color.red);
player.SimpleMove(dir*3);
}
加上重力
public class movetest : MonoBehaviour
{
public float movespeed = 8f;//移动速度
public float jumpspeed = 5f;//跳跃速度
public float gravity = 20f;//重力
private CharacterController player;//角色控制器
private Vector3 moveDirection = Vector3.forward;//移动方向
// Start is called before the first frame update
void Start()
{
player = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
movement();
}
void movement()
{
//水平轴
float horizontal = Input.GetAxis("Horizontal");
//垂直轴
float vertical = Input.GetAxis("Vertical");
//创建一个方向向量
moveDirection= new Vector3(horizontal, 0, vertical);
//跳跃
if (Input.GetKey(KeyCode.Space))
{
moveDirection.y = jumpspeed;
}
//应用重力
moveDirection.y -=gravity * Time.deltaTime;
Debug.DrawRay(transform.position, moveDirection, Color.red);
player.Move(moveDirection * movespeed * Time.deltaTime);
}
}
15 虚拟轴
想象的一条范围是 -1~1之间的数轴。
其实就是一种简便方法。比如我们想使用A、D键和左、右键来控制游戏物体的左右移动时,按照我们的之前的思路是用一个if条件来判断四个键是否按下,
而有了虚拟轴之后,我们只需要获取水平方向的输入就可以。
public class Axis : MonoBehaviour {
void Start () {
}
void Update () {
//Horizontal是控制水平方向的值,也就是x轴的值
//GetAxisRaw方法获取虚拟轴,当某键按下时会直接返回1或-1,没有变化过程
float x1 = Input.GetAxisRaw("Horizontal");
//GetAxis方法获取虚拟轴会有一个变化过程
float x2 = Input.GetAxis("Horizontal");
//Vertical是控制垂直方向的值,也就是z轴的值
float y = Input.GetAxis("Vertical");
//利用Translate方法来控制gameobject移动
transform.Translate(new Vector3(x1,0,y)*Time.deltaTime);
}
}
16 碰撞的产生与监听
//创建一个爆炸预设体
public GameObject prefab;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
//监听发生碰撞
private void OnCollisionEnter(Collision collision)
{
//创建一个爆炸物体
Instantiate(prefab,transform.position,Quaternion.identity);
Destroy(gameObject);
Debug.Log(collision.gameObject.name);
}
//持续碰撞中
private void OnCollisionStay(Collision collision)
{
}
//结束碰撞
private void OnCollisionExit(Collision collision)
{
}
17 触发与碰撞
//创建一扇门
public GameObject door;
// Start is called before the first frame update
void Start()
{
show(door);
}
// Update is called once per frame
void Update()
{
}
public void show(GameObject prompt)
{
prompt.SetActive(true);
}
public void hide(GameObject prompt)
{
prompt.SetActive(false);
}
private void OnTriggerEnter(Collider other)
{
Debug.Log(Time.time + ":进入该触发器的对象是:" + other.gameObject.name);
hide(door);
}
private void OnTriggerStay(Collider other)
{
Debug.Log(Time.time + "留在触发器的对象是:" + other.gameObject.name);
}
private void OnTriggerExit(Collider other)
{
Debug.Log(Time.time + "离开触发器的对象是:" + other.gameObject.name);
show(door);
}
走进就会触发消失,走出触发显示
18 射线检测
使用一条射线来检测是击中了某个物体/多个物体
- 射击游戏:
玩家的瞄准和射击:检测玩家视线是否与敌人或其他目标相交。
子弹轨迹和效果:模拟子弹的飞行路径和击中效果。 - 交互和UI:
鼠标点击检测:检测玩家的鼠标点击是否与游戏对象或UI元素相交。
触摸屏交互:在移动设备上检测玩家的触摸是否与特定的游戏元素相交。 - 角色控制器和AI:
视野检测:NPC或敌人在一定范围内检测玩家或其他角色。
碰撞避免:AI角色在移动时使用射线检测来避免碰撞。 - 虚拟现实(VR)和增强现实(AR):
眼睛或手部追踪:在VR中检测玩家的视线或手部位置。
对象交互:在AR中检测玩家是否与虚拟对象相交。
Ray ray = new Ray(Vector3.zero, Vector3.forward); // 射线的起点 + 射线的方向
void Start()
{
//方式1
//Ray ray = new Ray(Vector3.zero,Vector3.up);
}
// Update is called once per frame
void Update()
{
//方式2
//按下鼠标左键发射射线
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//声明碰撞信息类
RaycastHit hit;
//碰撞显示
bool res = Physics.Raycast(ray, out hit);
//如果碰撞到的情况下,hit有内容
if (res)
{
transform.position = hit.point;
Debug.Log(hit.point);
}
//多检测 射线、射线距离、射线层级
RaycastHit[] hits = Physics.RaycastAll(ray, 100,1<<10);
}
}
19 ai导航
组件 Nav mesh agent ai代理
//代理组件
private NavMeshAgent agent;
// Start is called before the first frame update
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
// Update is called once per frame
void Update()
{
//如果按下鼠标左键
if (Input.GetMouseButtonDown(0))
{
//获取点击位置
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
Vector3 point = hit.point;
//设置该位置为导航目标点
agent.SetDestination(point);
}
}
}
动态障碍物
nav mesh obstacle
网格链接
generate offmeshlinks
导航区域
用花费值来判断此区域是否可以行走
20 SerializeField
[SerializeField] 是 Unity 中的一个特性(Attribute),用于控制脚本中的私有字段或受保护字段在 Unity 编辑器中的可见性和可编辑性。默认情况下,Unity 只会序列化并显示 公共字段,但通过 [SerializeField],你可以让 私有字段 或 受保护字段 也显示在 Inspector 窗口中,并且可以被序列化(即保存到场景或预制体中)。
基本用法
csharp
复制
using UnityEngine;
public class Example : MonoBehaviour
{
[SerializeField]
private int health; // 私有字段,但会在 Inspector 中显示
[SerializeField]
protected float speed; // 受保护字段,但会在 Inspector 中显示
public int damage; // 公共字段,默认会在 Inspector 中显示
}
在上面的代码中:
health是一个私有字段,通常不会在 Inspector 中显示,但通过[SerializeField],它会在 Inspector 中显示,并且可以被编辑。speed是一个受保护字段,同样通过[SerializeField]在 Inspector 中显示。damage是一个公共字段,默认会在 Inspector 中显示,不需要[SerializeField]。
为什么要用 [SerializeField]?
- 封装性
- 在面向对象编程中,通常建议将字段设为私有(
private)或受保护(protected),并通过属性或方法访问它们,以封装数据。 - 使用
[SerializeField]可以在保持封装性的同时,允许在 Unity 编辑器中编辑这些字段。
- 在面向对象编程中,通常建议将字段设为私有(
- 避免暴露不必要的公共字段
- 如果将所有字段都设为公共字段,会导致 Inspector 窗口混乱,暴露不必要的字段。
- 使用
[SerializeField]可以只暴露需要编辑的字段,同时保持代码的整洁。
- 如果将所有字段都设为公共字段,会导致 Inspector 窗口混乱,暴露不必要的字段。
- 序列化需求
- Unity 的序列化系统只会序列化公共字段或标记了
[SerializeField]的私有/受保护字段。 - 如果你希望某些私有字段的值能够保存到场景或预制体中,必须使用
[SerializeField]。
- Unity 的序列化系统只会序列化公共字段或标记了
使用场景
1. 私有字段的序列化
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
private int maxHealth = 100; // 私有字段,但会在 Inspector 中显示并可编辑
private int currentHealth;
private void Start()
{
currentHealth = maxHealth; // 在游戏开始时初始化当前生命值
}
}
- 在这个例子中,
maxHealth是一个私有字段,但通过[SerializeField],它会在 Inspector 中显示,并且可以在编辑器中设置初始值。 currentHealth是一个运行时变量,不需要在 Inspector 中显示,因此不需要[SerializeField]。
2. 引用其他组件或对象
复制
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField]
private Player target; // 私有字段,引用 Player 对象
[SerializeField]
private Transform spawnPoint; // 私有字段,引用一个 Transform
}
- 在这个例子中,
target和spawnPoint是私有字段,但通过[SerializeField],你可以在 Inspector 中拖拽赋值。
3. 自定义类的序列化
using UnityEngine;
[System.Serializable]
public class Weapon
{
public string name;
public int damage;
}
public class Player : MonoBehaviour
{
[SerializeField]
private Weapon primaryWeapon; // 自定义类,通过 [SerializeField] 序列化
[SerializeField]
private Weapon secondaryWeapon;
}
- 在这个例子中,
Weapon是一个自定义类,通过[SerializeField],你可以在 Inspector 中编辑primaryWeapon和secondaryWeapon的属性。
注意事项
- 不要滥用
[SerializeField]- 只有在需要在 Inspector 中显示或需要序列化私有字段时,才使用
[SerializeField]。 - 如果字段不需要在 Inspector 中显示,也不需要序列化,直接使用私有字段即可。
- 只有在需要在 Inspector 中显示或需要序列化私有字段时,才使用
[SerializeField]不会影响运行时访问[SerializeField]只影响 Unity 编辑器的行为,不会改变字段的访问权限。私有字段仍然是私有的,不能在外部直接访问。
- 与
[HideInInspector]的区别[SerializeField]用于显示私有字段。[HideInInspector]用于隐藏公共字段。例如: csharp 复制 [HideInInspector]
public int hiddenValue; // 公共字段,但不会在 Inspector 中显示
- 自定义 Inspector
- 如果你需要更复杂的 Inspector 显示逻辑,可以编写自定义 Editor 脚本来替代
[SerializeField]。
- 如果你需要更复杂的 Inspector 显示逻辑,可以编写自定义 Editor 脚本来替代
总结
[SerializeField] 是 Unity 中非常常用的特性,它允许你将私有字段或受保护字段暴露在 Inspector 中,并且支持序列化。它的主要用途包括:
- 保持代码的封装性。
- 避免暴露不必要的公共字段。
- 支持私有字段的序列化。
正确使用 [SerializeField] 可以让你的代码更加整洁,同时提高 Unity 编辑器的可用性。