跟着书学Unity-《Unity3D游戏开发》宣雨松编著-(一)Unity的基础知识、编辑器结构、及拓展编辑器(拓展Project视图)_unity3d游戏开发第三版pdf-CSDN博客

跟着书学Unity-《Unity3D游戏开发》宣雨松编著-(二)拓展编辑器(拓展Hierarchy视图、拓展Inspector视图)_unity prefabutility 雨松-CSDN博客

跟着书学Unity-《Unity3D游戏开发》宣雨松编著-(三)拓展编辑器(拓展Sence视图、拓展Game视图、MeneItem菜单)_unity3d游戏开发第3版电子版-CSDN博客

跟着书学Unity-《Unity3D游戏开发》宣雨松编著-(四)拓展编辑器(面板拓展、Unity编辑器的源码)-CSDN博客

跟着书学Unity-《Unity3D游戏开发》宣雨松编著-(五)游戏脚本-CSDN博客

目录

4游戏脚本

4.1创建脚本

4.1.1脚本模板

4.1.2拓展脚本模板

4.2脚本的生命周期

4.2.1脚本绑定事件

4.2.2脚本初始化和销毁

4.2.3脚本更新与协程任务

4.2.4停止协程任务

4.2.5使用OnGUI显示FPS

4.3多脚本管理

4.3.1脚本的执行顺序

4.3.2多脚本优化

4.4脚本序列化

4.4.1查看数据

4.4.2私有序列化数据

4.4.3 serializedObject

4.4.4监听部分元素修改事件

4.4.5ScriptableObject

4.4.6单例脚本

4.4.7定时器

4.5脚本编译

4.5.1日志


4游戏脚本

4.1创建脚本

4.1.1脚本模板

有两种设置脚本模板的方式

以下是AI总结出来的一些特点

特性 直接修改系统模板 创建项目特定模板 (推荐)
修改位置 Unity安装目录下的ScriptTemplates文件夹 项目内的Editor文件夹
灵活性 相对较低,影响所有项目 ,可针对不同项目定制
维护性 低,Unity升级后可能需要重新修改 ,随项目代码一起管理,不易丢失
团队共享 繁琐,需每位成员单独修改 方便,通过版本控制系统(如Git)共享Editor文件夹
安全性 低,误操作可能影响Unity正常运行 ,仅在项目范围内生效
推荐场景 个人开发者,对所有项目有统一固定要求 团队协作,不同项目有不同代码规范需求

直接修改的方法

  1. 找到模板文件:模板文件位于Unity编辑器的安装目录下:

    • WindowsC:\Program Files\Unity\Editor\Data\Resources\ScriptTemplates

    • Mac (旧版本)/Applications/Unity/Editor/Data/Resources/ScriptTemplates

    • Mac (新版本, 如5.2.1f1之后)/Applications/Unity/Unity.app/Contents/Resources/ScriptTemplates

  2. 识别模板文件:你会看到多个以数字开头的.txt文件,例如:

    • 81-C# Script-NewBehaviourScript.cs.txt (这是最常用的C#脚本模板)

    • 其他模板文件(如JavaScript、Shader等)也在此目录。

  3. 编辑模板:用文本编辑器(如VSCode、记事本)打开你想要修改的模板文件,例如 81-C# Script-NewBehaviourScript.cs.txt。你可以根据需求修改内容,但务必保留关键的占位符

    • #SCRIPTNAME#:在创建脚本时会被替换为你在Unity中输入的文件名(作为类名)。

    • #NOTRIM#:一个指令,用于确保其所在行的缩进和格式不会被自动修剪。

  4. 保存并重启:保存修改后的模板文件,并重启Unity编辑器以使更改生效。

4.1.2拓展脚本模板

using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Text;
using UnityEditor.ProjectWindowCallback;
using System.Text.RegularExpressions;

public class Script_04_01
{
    //自创脚本模板所在的目录
    private const string MY_SCRIPT_DEFAULT = "Assets/Script/Editor/ScriptTemplates/C# Script-MyNewBehaviourScript.cs.txt";
    [MenuItem("Assets/Create/C# MyScript", false, 80)]
    public static void CreatMyScript()
    {
        string locationPath = GetSelectedPathOrFallback();//获取文件位置
        ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
            ScriptableObject.CreateInstance<MyDoCreateScriptAsset>(),
            locationPath + "/MyNewBehaviourScript.cs",
            null, MY_SCRIPT_DEFAULT);
    }

    //获取创建文件的位置
    public static string GetSelectedPathOrFallback()
    {
        string path = "Assets";
        foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object),
            SelectionMode.Assets))
        {
            path = AssetDatabase.GetAssetPath(obj);//获取资源的路径
            if (!string.IsNullOrEmpty(path) && File.Exists(path))//检查获取路径是否争取二
            {
                path = Path.GetDirectoryName(path);//返回指定路径的目录信息。
                break;
            }
        }
        return path;
    }
}
class MyDoCreateScriptAsset : EndNameEditAction
{
    public override void Action(int instanceId, string pathName, string resourceFile)
    {
        UnityEngine.Object o = CreateScriptAssetFromTemplate(pathName, resourceFile);
        ProjectWindowUtil.ShowCreatedAsset(o);//在 Project 窗口中高亮显示刚刚创建的资产,并尝试将其选中。
    }

    internal static UnityEngine.Object CreateScriptAssetFromTemplate(string pathName, string resourceFile)
    {
        string fullPath = Path.GetFullPath(pathName);//返回指定路径字符串的绝对路径。
        //读取模板内容
        StreamReader streamReader = new StreamReader(resourceFile);
        string text = streamReader.ReadToEnd();//模板内容
        streamReader.Close();
        string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(pathName);//返回不具有扩展名的指定路径字符串的文件名。

        text = Regex.Replace(text, "#Name#", fileNameWithoutExtension); //在指定的输入字符串中,将匹配指定正则表达式的所有字符串替换为指定的替换字符串。
        //设置编码格式
        bool encoderShouldEmitUTF8Identifier = true;
        bool throwOnInvalidBytes = false;
        UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier,
            throwOnInvalidBytes);
        bool append = false;
        //将模板内容写入新文件
        StreamWriter streamWriter = new StreamWriter(fullPath, append, encoding);
        streamWriter.Write(text);
        streamWriter.Close();
        AssetDatabase.ImportAsset(pathName);
        return AssetDatabase.LoadAssetAtPath(pathName, typeof(UnityEngine.Object));
    }
}

以上代码会使我们的编辑器出现一个创建C# MyScript的按钮,会根据我们设置的模板以及设置的名字生成对应的文件,注释尽量写了,如果有补充欢迎评论

官方文档

StreamReader 类 (System.IO) | Microsoft Learn

StreamWriter 类 (System.IO) | Microsoft Learn

File 类 (System.IO) | Microsoft Learn

Selection - Unity 脚本 API

AssetDatabase-GetAssetPath - Unity 脚本 API

Path.GetDirectoryName 方法 (System.IO) | Microsoft Learn

ProjectWindowUtil的相关内容比较少所以结合AI给的内容进行了一些理解

ProjectWindowUtil 提供了一系列方法来帮助我们在 Project 窗口中创建资源、响应操作以及添加自定义的菜单项。

4.2脚本的生命周期

事件函数的执行顺序 - Unity 手册

4.2.1脚本绑定事件

在编辑模式下给脚本添加Reset()方法就可以监听脚本绑定时的事件了

4.2.2脚本初始化和销毁

主要与生命周期的几个方法相关可以直接查看上方的链接

4.2.3脚本更新与协程任务

Update():每一帧执行时都会立即调用此方法

LateUpdate():Update()执行后执行

FixedUpdate():(相对)固定更新,比较适合以秒为单位的计时。默认情况下是0.02秒调用一次,可以设置路径为Editor-》ProjectSetting-》Time。

协程的写法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Script_04_04 : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(CreateCube());//开始协程
    }

    IEnumerator CreateCube()
    {
        for (int i = 0; i < 100; i++)
        {
            GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = Vector3.one*i;
        }
        yield return new WaitForSeconds(1f);//等待一秒
    }

}

4.2.4停止协程任务

可以创建一个类型为Coroutine的变量用来存储StartCoroutine()方法返回的协程对象在需要的时候使用StopCoroutine()方法传入对象就可以了。

或者使用StopAllCoroutine()这个方法停止当前脚本已启动的所有协程

4.2.5使用OnGUI显示FPS

代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Script_04_06 : MonoBehaviour
{
    public float updateInterval = 0.5f;
    private float accum = 0;
    private int frames = 0;
    private float timeleft;
    private string stringFps;

    void Start()
    {
        timeleft = updateInterval;
    }

    void Update()
    {
        timeleft -=Time.deltaTime;
        accum += Time.timeScale / Time.deltaTime;
        ++frames;
        if(timeleft <= 0.0)
        {
            float fps = accum / frames;
            string format = System.String.Format("{0:F2}FPS", fps);
            stringFps = format;
            timeleft = updateInterval;
            accum = 0.0f;
            frames = 0;
        }
    }
    void OnGUI()
    {
        GUIStyle guiStyle = GUIStyle.none;
        guiStyle.fontSize = 30;
        guiStyle.normal.textColor = Color.red;
        guiStyle.alignment = TextAnchor.UpperLeft;
        Rect rt = new Rect(40,0,100,100);//位置
        GUI.Label(rt,stringFps,guiStyle);//显示
    }
}

效果

相关文档

Rect - Unity 脚本 API

GUI-Label - Unity 脚本 API

4.3多脚本管理

4.3.1脚本的执行顺序

在Script Execution Order中可以调整脚本的执行顺序

路径Edit-》Project Setting-》Script Execution Order

或者使用Awake进行初始化

4.3.2多脚本优化

尽可能减少挂载的脚本数量,以及尽可能减少脚本当中的空方法

4.4脚本序列化

脚本可以通过序列化和反序列化保存游戏数据,脚本本身没有保存数据的功能而是将数据保存在文件当中,使用时直接通过语法访问数据。

我的理解中序列化就是把开发过程当中的各类数据转变成某种特定数据格式并存储在场景中或者文件当中也就是存档

反序列化就是将某种特定格式的内容读入场景中并使用也就是读档

以下只保留了书中的部分内容

4.4.1查看数据

场景、预制体这一类的数据可以直接拖入编辑器打开进行查看。

所有参与序列化的对象都需要在脚本当中声明对应的数据结构

4.4.2私有序列化数据

脚本中的所有的公有对象都是支持序列化格式,但是不是所有的数据我们都会使用公有的,也有部分数据我们会使用private声明 所以如果想要使用编辑器来编辑脚本的数据的话可以用[SerializeField]标记数据

如果公有数据不想在编辑器中显示可以使用[HideInInspector]标记

示例代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.IO.LowLevel.Unsafe;

#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("name");
        property.stringValue = EditorGUILayout.TextField("姓名", property.stringValue);

        property = serializedObject.FindProperty("prefab");
        property.objectReferenceValue =EditorGUILayout.ObjectField("游戏对象",
            property.objectReferenceValue,typeof(GameObject),true);

        //全部保存数据
        serializedObject.ApplyModifiedProperties();

    }
}


#endif

效果

SerializeField - Unity 脚本 API

HideInInspector - Unity 脚本 API

4.4.3 serializedObject

只能在编辑器模式下使用使用方法可以参考上面的代码

SerializedObject - Unity 脚本 API

4.4.4监听部分元素修改事件

MonoBehaviour中的OnValidate方法会在编辑面板中的信息发生变化时被回调

或者

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class Script_04_10 : MonoBehaviour
{
    [SerializeField]
    private GameObject[] targets;
}

#if UNITY_EDITOR
[CustomEditor(typeof(Script_04_10))]
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

当targets中的内容发生变化时就会有相关信息

SerializedObject - Unity 脚本 API

4.4.5ScriptableObject

ScriptableObject - Unity 脚本 API

4.4.6单例脚本

示例

using UnityEngine;

public class SingletonExample : MonoBehaviour
{
    public static SingletonExample Instance { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
    }
}

4.4.7定时器

示例代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class WaitTimeManager 
{
    private static TaskBehaviour m_Task;
    static WaitTimeManager()
    {
        GameObject go = new GameObject("#WaitTimeManager#");
        GameObject.DontDestroyOnLoad(go);
        m_Task = go.AddComponent<TaskBehaviour>();
    }

    //等待
    static public Coroutine WaitTime(float time,UnityAction callback)
    {
        return m_Task.StartCoroutine(Coroutine(time, callback));
    }

    //取消等待
    static public void CancelWait(ref Coroutine coroutine)
    {
        if (coroutine != null)
        {
            m_Task.StopCoroutine(coroutine);
            coroutine = null;
        }
    }

    static IEnumerator Coroutine(float time,UnityAction callback)
    {
        yield return new WaitForSeconds(time);
        if(callback != null)
        {
            callback();
        }
    }

    class TaskBehaviour : MonoBehaviour { }
}

调用

Coroutine coroutine = WaitTimeManager.WaitTime(5f,delegate{
    Debug.Log("等待5秒后回调");
});

//等待的过程中取消它
WaitTimeManager.CancelWait(ref coroutine);

这是用协程实现的

其实定时器的实现还有很多方法

比如用UnityActionUnityAction的用法_unity 方法中 action 怎么调用-CSDN博客

4.5脚本编译

省略了绝大部分内容,只看了用的比较多的

4.5.1日志

Debug - Unity 脚本 API

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐