• Unity读书系列《Unity3D游戏开发》——脚本(一)



    前言

    脚本在Unity的重要程度不用多说,她是大部分软件的核心组件。
    我们将在此篇文章学习脚本模版及其拓展、脚本的生命周期、脚本的执行顺序、脚本序列化,下一篇为脚本编译与调试。


    一、脚本模版及其拓展

    1、脚本模版

    如下图我们可以在Project视图右键进行脚本创建,除了C#脚本,还有两类脚本;Testing用来做单元测试,Playables是TimeLine引入的新概念。
    在这里插入图片描述
    通常来讲我们会根据项目修改脚本模版,位置在Unity安装位置的Resource文件夹内,例如:“C:\Program Files\Unity 2021.3.16f1\Editor\Data\Resources\ScriptTemplates”。除了按照项目修改模版,通常会将脚本编码改成UTF-8并在脚本模版中加入中文或中文字符。
    在这里插入图片描述

    2、拓展脚本模版

    如果按上述方法修改脚本模版会十分麻烦,不说每个版本的unity都需要修改,项目里每个成员都需要修改,当模版变动难道每个人都要重新修改吗。我们想做到版本管理可以拓展脚本的方式添加模版。

    二、脚本的生命周期

    Unity脚本有一套完整的生命周期,无需手动调用。其中包含初始化、更新回调、渲染、销毁,我们经常使用的协程等也在其内。
    官方解释:
    在这里插入图片描述
    书中解释:
    在这里插入图片描述

    三、脚本的执行顺序

    在上一章中当有两个脚本标记了"[CustomEditor(typeof(Camera))]",只会执行第一个创建的脚本逻辑。
    为什么呢?Unity的脚本可以预先挂载场景中,也可以在运行时动态添加,所以用Script Execution Order使用来管理脚本的执行顺序,我们可以在此添加脚本并设置顺序。
    在这里插入图片描述
    我们考虑一件事,现在有多个脚本,A脚本在Awake获取B脚本的数据,A如果比B先执行那就会报错。实际上这种时序的问题还会发生在项目越发复杂,初始化过多的情况。不仅如此,多个脚本还会产生多余消耗,假设每个脚本都有初始化(Awake、Start等)和更新(Update)的方法,几百个脚本同时运行,性能差的主机可能分分钟卡死。
    解决方法:将系统分类,每个系统的初始化等功能都设置入口,功能统一调用。初学者推荐尝试单例的写法来管理小项目,再根据项目尝试工厂模式、适配器模式等设计模式,最后学习框架的思想。

    四、脚本序列化

    想了解脚本序列化,首先我们需要明白脚本本身并没有保存数据,而是将数据保存于文件中,也就是脚本可以通过序列化和反序列化来保存游戏。
    书中举例,给场景其中一个物体加上脚本Test.cs,再找一个预制体Prefab.prefab加上脚本Test.cs。接下来我们修改场景中的脚本数据,也就是Id,那么数据将保存在Scene.unity;同理,在Project下修改预制体上的脚本Id,那么数据会保存在预制体文件夹内。

    如果把预制体赋值到场景的脚本中(脚本中的public GameObject prefab;),那么数据会存在Scene.unity。大家仔细想想,平常雀氏是这样操作的,只是不明所以罢了。

    public class Test : MonoBehaviour
    {
    	public int Id;
    	public GameObject prefab;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、序列化数据

    打开上面的预制体和场景,就会发现里面存着序列化的数据,这里不做展示。
    通常,公开的对象会支持序列化格式,如果不想显示可以加上[HideInInspector],私有序列化需要再代码上加上[SerializeField]。

    public class Test : MonoBehaviour
    {
    	[SerializeField]
    	private int Id;
    	[SerializeField]
    	private GameObject prefab;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    假如我此时还想访问呢,直接请出serializedObject.FindProperty()访问私有属性。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    public class Script_04_08 : MonoBehaviour
    {
        [SerializeField]
        private int id;
        [SerializeField]
        private string name;
        [SerializeField]
        private GameObject prefab;
    }
    
    #if UNITY_EDITOR
    [CustomEditor(typeof(Script_04_08))]
    public class ScriptInsector : Editor
    {
        public override void OnInspectorGUI()
        {
            //更新最新数据
            serializedObject.Update();
            //获取数据信息
            SerializedProperty property = serializedObject.FindProperty("id");
            //赋值数据
            property.intValue = EditorGUILayout.IntField("主键", property.intValue);
    
            property = serializedObject.FindProperty("prefab");
            property.objectReferenceValue = EditorGUILayout.ObjectField("游戏对象",
                property.objectReferenceValue,typeof(GameObject),true);
    
            //全部保存数据
            serializedObject.ApplyModifiedProperties();
        }
    }
    #endif
    
    • 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

    2、serializedObject

    serializedObject只能在Editor中使用,它专门用于获取设置的序列化信息。通常,要开发复杂的编辑组件,都需要重写OnInspectorGUI()方法,但是如果希望有些用源生的绘制结构,同时兼容有些自定义渲染的话,那该怎么办呢?

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    public class SerializedObjectTest : MonoBehaviour
    {
        [SerializeField]
        private int id;
        [SerializeField]
        private GameObject[] targets;
    }
    
    #if UNITY_EDITOR
    [CustomEditor(typeof(SerializedObjectTest))]
    public class ScriptInsector : Editor
    {
        public override void OnInspectorGUI()
        {
            //更新最新数据
            serializedObject.Update();
            //获取数据信息
            SerializedProperty property = serializedObject.FindProperty("id");
            //赋值数据
            property.intValue = EditorGUILayout.IntField("主键", property.intValue);
            //以默认样式绘制数组数据
            EditorGUILayout.PropertyField(serializedObject.FindProperty("targets"), true);
            //全部保存数据
            serializedObject.ApplyModifiedProperties();
        }
    }
    #endif
    
    • 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

    效果如下:
    在这里插入图片描述

    3、监听部分元素修改事件

    脚本面板中,参与绘制的元素都是在OninspectorGUI中绘制的。我们想监听其中的一个元素有两种方法,第一种适用于简单的脚本,在脚本中写入Onvalidate(),当面板信息发生改变就会回调该方法;

    void Onvalidate(){
    Debug.log("信息改变");
    }
    
    • 1
    • 2
    • 3

    第二种需要监听GUI的元素写在EditorGUI.BeginChangeCheck()。我们监听变化则需要在if(EditorGUI.EndChangeCheck()) 中处理。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    public class Test : MonoBehaviour
    {
        [SerializeField]
        private GameObject[] targets;
    }
    
    #if UNITY_EDITOR
    [CustomEditor(typeof(Test))]
    public class ScriptInsector : Editor
    {
        public override void OnInspectorGUI()
        {
            //更新最新数据
            serializedObject.Update();
            //标记检查
            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(serializedObject.FindProperty("targets"), true);
            //标记检查发生变化
            if(EditorGUI.EndChangeCheck()) {
                Debug.Log("元素发生变化");
    }
            //判断面板元素是否任意发生改变
            if(GUI.changed) {
    
            }
            //全部保存数据
            serializedObject.ApplyModifiedProperties();
    
        }
    }
    #endif
    
    • 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

    五、定时器与间隔定时器

    通常我们可以用协程做定时器,缺陷是依赖于脚本,所以封装一个不依赖脚本实现的定时器是很有必要的。
    详细使用请看定时器与间隔定时器

    六、工作线程(多线程)

    工作线程(多线程)是指在一个程序中同时执行多个任务的能力。多线程编程可以提高程序的性能和响应性,特别是在需要同时执行多个任务的情况下。工作线程通常用于执行耗时的任务,以避免阻塞主线程,从而保持程序的流畅性。
    目前很多主流游戏或软件在启动或下载等待时会使用多线程技术,应用十分广泛。
    详细使用请看Unity多线程


    总结

    这篇文章将介绍Unity中脚本的核心概念和重要性。我们探讨如何创建和定制脚本以满足特定需求,以及脚本的生命周期、执行顺序和序列化等内容。后面两个示例(定时器和工作线程的运用)我分别用单独的章节说明,如果需要再去学习运用;这种安排旨在强调项目驱动的学习方式,同时鼓励读者在实践中运用这些示例来更快地提升自己的技能。逐步学习并根据需求应用所学知识,将使读者能够更有效地掌握相关技能。
    最后,感谢大家观看,欢迎讨论指正错误,别忘了点赞👍。

  • 相关阅读:
    vue使用原生video标签基本功能(不含样式)
    基于Java毕业设计学生在线评教系统源码+系统+mysql+lw文档+部署软件
    元宇宙中的情绪与情感探索在军事上的应用
    机器学习 --- PCA
    python篇----进程+线程
    Vue3之属性传值的四种情况
    BEIT-3杂谈
    已发表的paper
    09_CSS3多媒体查询
    springboot server.address 配置问题
  • 原文地址:https://blog.csdn.net/qq_41912124/article/details/135833884