unity脚本基础
  • 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]

  1. 封装性
    • 在面向对象编程中,通常建议将字段设为私有(private)或受保护(protected),并通过属性或方法访问它们,以封装数据。
    • 使用 [SerializeField] 可以在保持封装性的同时,允许在 Unity 编辑器中编辑这些字段。
  2. 避免暴露不必要的公共字段
    • 如果将所有字段都设为公共字段,会导致 Inspector 窗口混乱,暴露不必要的字段。
    • 使用 [SerializeField] 可以只暴露需要编辑的字段,同时保持代码的整洁。
  3. 序列化需求
    • Unity 的序列化系统只会序列化公共字段或标记了 [SerializeField] 的私有/受保护字段。
    • 如果你希望某些私有字段的值能够保存到场景或预制体中,必须使用 [SerializeField]

使用场景

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 的属性。

注意事项

  1. 不要滥用 [SerializeField]
    • 只有在需要在 Inspector 中显示或需要序列化私有字段时,才使用 [SerializeField]
    • 如果字段不需要在 Inspector 中显示,也不需要序列化,直接使用私有字段即可。
  2. [SerializeField] 不会影响运行时访问
    • [SerializeField] 只影响 Unity 编辑器的行为,不会改变字段的访问权限。私有字段仍然是私有的,不能在外部直接访问。
  3. 与 [HideInInspector] 的区别
    • [SerializeField] 用于显示私有字段。
    • [HideInInspector] 用于隐藏公共字段。例如: csharp 复制 [HideInInspector]
      public int hiddenValue; // 公共字段,但不会在 Inspector 中显示
  4. 自定义 Inspector
    • 如果你需要更复杂的 Inspector 显示逻辑,可以编写自定义 Editor 脚本来替代 [SerializeField]

总结

[SerializeField] 是 Unity 中非常常用的特性,它允许你将私有字段或受保护字段暴露在 Inspector 中,并且支持序列化。它的主要用途包括:

  • 保持代码的封装性。
  • 避免暴露不必要的公共字段。
  • 支持私有字段的序列化。

正确使用 [SerializeField] 可以让你的代码更加整洁,同时提高 Unity 编辑器的可用性。

上一篇
下一篇