【Unity】[入门tips与通用性原则] 一些经验技巧和更好地写出简洁易懂的程序的原则方法

news2024/12/26 14:24:21

本文将持续间断更新

本文主要面向初级程序员,为了方便Unity开发,有些快捷键的方式和一些通用性的技巧tips等会在这篇博客内持续更新,欢迎点赞收藏

快捷键

  1. Ctrl + S ; 快捷保存!闲着没事就来两下!
  2. Ctrl+Shift+F ; 在hierarchy窗口中选中任意对象,在scene窗口中按此快捷键,可将对象坐标设置为此时scene窗口坐标。
  3. Alt+Mouse Left ; 在hierarchy窗口中折叠或展开对象时,按住alt再按左侧小三角,可使其所有子对象折叠或展开。

重要、有用,必学的函数

Lerp

使用变量插值:在编写脚本时,为了提高代码的可读性和灵活性,建议使用变量插值,例如 transform.position = Vector3.Lerp(transform.position, targetPosition, speed * Time.deltaTime); 这种方式,能够实现更平滑的过渡效果。
https://docs.unity3d.com/ScriptReference/30_search.html?q=Lerp.
在这里插入图片描述

开发时的原则

尽量减少GameObject的数量

尽可能地合并一些GameObject,可以大大减少Draw Call的次数,从而提高性能。

预制体重用

预制体是非常有用的,可以让我们在场景中快速地实例化对象,并且在多个场景中复用,更改一个prefab,即可应用更改到所有prefab的实例。
还可以有Prefab Variants(https://docs.unity3d.com/Manual/PrefabVariants.html),如同面向对象的继承一样,可以实现更改“树”prefab,应用更改到“苹果树”prefab和“梨子树”prefab这样的功能。

对象池

对象池是一种重复使用已经创建过的对象的技术,这样可以避免在创建对象时的开销,提高性能。比如子弹,如果每颗子弹单独Instantiate和destroy,开销会很大,转用对象池就能很省资源,下面是例子:

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

public class ObjectPool : MonoBehaviour {

    public GameObject bulletPrefab;
    public int poolSize = 20;

    private List<GameObject> bullets = new List<GameObject>();
	//创建了一个Bullet的对象池,通过实例化bulletPrefab来填充池子。
    void Start () {
        for (int i = 0; i < poolSize; i++) {
            GameObject bullet = Instantiate (bulletPrefab);
            bullet.SetActive (false);
            bullets.Add (bullet);
        }
    }
	//GetBullet函数用于获取一个Bullet对象,
	//它首先检查池子中是否有空闲的对象,
	//如果有,就返回其中一个;否则,就实例化一个新的Bullet对象。
    public GameObject GetBullet () {
        for (int i = 0; i < bullets.Count; i++) {
            if (!bullets[i].activeInHierarchy) {
                return bullets[i];
            }
        }

        GameObject bullet = Instantiate (bulletPrefab);
        bullet.SetActive (false);
        bullets.Add (bullet);

        return bullet;
    }
	//ReturnBullet函数用于将Bullet对象返回池子。
	//当我们使用完一个Bullet对象时,可以将其传递给ReturnBullet函数
	//,以便将其返回到池子中,而不是销毁它。
    public void ReturnBullet (GameObject bullet) {
        bullet.SetActive (false);
    }
}

在实际使用中,我们可以在需要创建Bullet对象时,使用ObjectPool的GetBullet函数获取一个对象,使用完毕后,使用ObjectPool的ReturnBullet函数将对象返回到池子中。这样,我们就可以避免频繁地创建和销毁Bullet对象,从而提高应用程序的性能。

使用Profiler进行性能分析

Profiler是Unity的一个内置工具,可以帮助开发者识别应用程序的性能瓶颈,以及优化应用程序的性能。可以通过Profiler判断当前程序的运行瓶颈在哪,我是通过unity官方教程了解到的这个工具,这个确实简单易懂,而且入门简单,能清楚的看到哪行到哪行用了多少ms。可以搜搜使用方法,我觉得非常有用。

使用协程来延迟执行代码

协程(Coroutine)是Unity中一种用于实现多任务并行处理的机制。协程可以在执行过程中暂停并等待某些条件满足后再继续执行,从而可以实现一些非常有用的功能,例如延时执行、动画效果、处理异步任务等。在谷歌搜Invoke和协程的区别的时候,大家都建议用协程,下面是一个使用协程实现延时执行的例子:

using UnityEngine;
using System.Collections;

public class CoroutineExample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(DelayedFunction(2.0f));
    }

    IEnumerator DelayedFunction(float delay)
    {
        Debug.Log("Delay start");
        yield return new WaitForSeconds(delay);
        Debug.Log("Delay end");
    }
}

另外开发中经常有生成器这种东西,我一般开个协程,start里调用一下,下面是我开发中的一个例子:

using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using UnityEngine;


public class ResidentManager : MonoBehaviour
{

    [SerializeField] GameObject[] residentsPrefabs;

    int totalResidentNumber;

    private void Awake()
    {
        //变量初始化
    }
    // Start is called before the first frame update
    void Start()
    {
        //一些在其他脚本内awake中初始化的变量需要在start中初始化,
        //因为Start方法会在所有对象的Awake方法调用完毕后执行
        totalResidentNumber = GameManager.INSTANCE.totalResidentNumber;

        
        StartCoroutine(SpawnResident());
    }

    // Update is called once per frame
    void Update()
    {
    }

    //协程,生成resident
    IEnumerator SpawnResident()
    {
        for (int i = 0; i < totalResidentNumber; i++)
        {
            int randomIndex = Random.Range(0, residentsPrefabs.Length);
            GameObject resident = Instantiate(residentsPrefabs[randomIndex], transform.position, Quaternion.identity);
            resident.transform.parent = transform;
            yield return new WaitForSeconds(0.1f);
        }
    }
}

条件编译

可以帮助我们根据不同的平台和编译条件编写不同的代码,从而提高应用程序的可移植性。用条件编译就不用换一次平台新建一次工程或者手动根据目标平台注释代码了,直接代码全写里面,区别就是编译的时候,根据build选项里的目标平台会自动确定需要编译哪部分代码,未被编译的代码没有任何性能损失。

一些只有在开发时才会用到的函数可以用UNITY_EDITOR来预编译,下面是我开发的一个VR游戏的例子,只有在调试的时候才会在editor和window下运行,所以可以这样写: 在这里插入图片描述

下面是一个使用条件编译实现平台相关功能的例子:

using UnityEngine;
using System.Collections;

public class PlatformExample : MonoBehaviour
{
#if UNITY_EDITOR
    void Start()
    {
        Debug.Log("Unity Editor");
    }
#elif UNITY_ANDROID
    void Start()
    {
        Debug.Log("Android");
    }
#elif UNITY_IOS
    void Start()
    {
        Debug.Log("iOS");
    }
#endif
}

一眼就能看明白,最重要的是方便,不用注释掉其他平台的代码,毕竟unity也是个适合跨平台的引擎。
其他的条件编译内容和平台列表参照官网:
https://docs.unity3d.com/Manual/PlatformDependentCompilation.html

重构你的代码!

脚本中函数位置规划

为了提高脚本的可读性,可以按照以下方式规划各个函数的位置:

  1. 首先,建议将所有的公共成员变量(例如,public变量和public属性)放在脚本的最上面,这样其他开发者就可以很方便地查看脚本中可见的成员变量。

  2. 接下来,建议将脚本的生命周期函数(例如,Start和Update)放在公共成员变量之后。这些函数通常用于初始化脚本和每帧更新脚本的状态,所以将它们放在公共成员变量之后更加合理。

  3. 紧接着,建议将其他重载函数、条件编译函数、公共函数和私有函数按照函数的逻辑顺序进行分组,并将各个函数组之间留出一定的空白行进行分割。这样做可以提高代码的可读性,使得开发者更容易理解代码的逻辑和结构。

  4. 如果有辅助计算函数,建议将它们放在调用它们的函数之后,并将它们命名为较短的名称,以突出它们的作用。

例如,一个可能的函数排列顺序如下:

public class ExampleScript : MonoBehaviour {
    // 公共成员变量
    public int someValue;

    // 生命周期函数
    void Start() {
        // ...
    }

    void Update() {
        // ...
    }

    // 其他重载函数和条件编译函数
    void OnTriggerEnter(Collider other) {
        // ...
    }

    #if UNITY_EDITOR
    void OnDrawGizmos() {
        // ...
    }
    #endif

    // 公共函数
    public void DoSomething() {
        // ...
    }

    // 私有函数
    private void DoAnotherThing() {
        // ...
    }

    // 辅助计算函数
    private void CalculateSomething() {
        // ...
    }

    // 调用辅助计算函数的函数
    private void PerformCalculations() {
        CalculateSomething();
        // ...
    }
}

上述代码中,按照公共成员变量、生命周期函数、其他重载函数和条件编译函数、公共函数、私有函数以及辅助计算函数的顺序进行排列,增加了代码的可读性,使得其他开发者更容易理解代码的逻辑和结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CMake基础使用和实战详解

CMake基础使用和实战详解一、CMake简介1.1、cmake 的特点1.2、注意1.3、使用建议二、安装 cmake三、CMake的简单使用3.1、准备工作3.2、开始构建3.3、解释CMakeLists.txt的内容3.4、基本语法规则四、更像样的CMake工程4.1、准备工作4.2、构建4.3、语法解释4.4、修改保存目标二进…

水文监测场景的数据通信规约解析和落地实践

[小 迪 导 读]&#xff1a;江苏云上需要通过云平台接入水文设备来实现水文数据的采集、存储、显示、控制、报警及传输等综合功能。企业介绍江苏云上智联物联科技有限公司是专业从事物联网相关产品与解决方案服务的高科技公司&#xff0c;总部位于美丽的江苏无锡。公司遵循“智联…

linux高级命令之死锁

死锁学习目标能够知道产生死锁的原因1. 死锁的概念死锁: 一直等待对方释放锁的情景就是死锁为了更好的理解死锁&#xff0c;来看一个现实生活的效果图:说明:现实社会中&#xff0c;男女双方一直等待对方先道歉的这种行为就好比是死锁。死锁的结果会造成应用程序的停止响应&…

面试官:你是怎样理解Fiber的

hello&#xff0c;这里是潇晨&#xff0c;今天我们来聊一聊Fiber。不知道大家面试的时候有没有遇到过和react Fiber相关的问题呢&#xff0c;这一类问题比较开放&#xff0c;但也是考察对react源码理解深度的问题&#xff0c;如果面试高级前端岗&#xff0c;恰巧你平时用的是re…

细谈JavaWeb中的Request和Response

文章目录1&#xff0c;Request和Response的概述2&#xff0c;Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式2.4 请求参数中文乱码问题2.4.1 POST请求解决方案2.4.2 GET请求…

MySQL使用C语言连接

文章目录MySQL使用C语言连接引入库下载库文件在项目中使用库使用库连接数据库下发SQL请求获取查询结果MySQL使用C语言连接 引入库 要使用C语言连接MySQL&#xff0c;需要使用MySQL官网提供的库。 下载库文件 下载库文件 首先&#xff0c;进入MySQL官网&#xff0c;选择DEVEL…

Java代码使用最小二乘法实现线性回归预测

最小二乘法简介最小二乘法是一种在误差估计、不确定度、系统辨识及预测、预报等数据处理诸多学科领域得到广泛应用的数学工具。它通过最小化误差&#xff08;真实目标对象与拟合目标对象的差&#xff09;的平方和寻找数据的最佳函数匹配。利用最小二乘法可以简便地求得未知的数…

如何写一个 things3 client

Things3[1] 是一款苹果生态内的任务管理软件&#xff0c;是一家德国公司做的&#xff0c;非常好用。我前后尝试了众多任务管理软件&#xff0c;最终选定 things3&#xff0c;以后有机会会写文章介绍我是如何用 things3 来管理我的日常任务。本文主要介绍欧神写的 tli[2] 工具来…

3D沉浸式体验开发技巧【Three.js】

在本文中&#xff0c;我们将看看如何使用 Three.js 创建一个充满后期效果和微交互的迷你城市。 推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链。 1、背景介绍 我是一个游戏爱好者。 我一直梦想创建一个交互式迷你城市&#xff0c;使用饱和的颜色&#xff0c;类似于…

Android自动化测试(UiAutomator)——UiObject

本文主要讲解使用UiAutomator的一些技巧&#xff0c;希望对于初学者有一定的帮助 UiObject 1、首先要声明对象 UiObject XXX new UiObject(new Selector) ; 2、其次对对象进行操作 操作类型包括&#xff1a; 执行类&#xff1a;文本输入与清除、点击/长按、拖动/滑动、 …

JAVA JDK 常用工具类和工具方法

目录 Pair与Triple Lists.partition-将一个大集合分成若干 List集合操作的轮子 对象工具Objects 与ObjectUtils 字符串工具 MapUtils Assert断言 switch语句 三目表达式 IOUtils MultiValueMap MultiMap JAVA各个时间类型的转换&#xff08;LocalDate与Date类型&a…

开源软件AirByte:入湖入仓,数据集成管道

从ETL到ELT就传统的 ETL而言&#xff0c;当我们开始构建数据仓库时&#xff0c;都要先去了解业务流程&#xff0c;明晰业务是如何运转的&#xff0c;数据是如何留痕的。通过收集用户的相关需求&#xff0c;从而去规划设计报表。企业需要进行数仓分域、分层、逻辑建模等一系列操…

Linux下程序调试的方法【GDB】GDB相关命令和基础操作(命令收藏)

目录 1、编译 2、启动gdb调试 2.1 直接运行 2.2 运行gdb后使用run命令 2.3 调试已运行的程序 3、图形界面提示 4、调试命令 1、查看源码 2、运⾏程序/查看运⾏信息 3、设置断点 5、单步/跳步执⾏ 6、分割窗口 7、其他命令 8、相关参数 1、编译 在编译时要加上-g选…

stm32f407探索者开发板(十七)——串口寄存器库函数配置方法

文章目录一、STM32串口常用寄存器和库函数1.1 常用的串口寄存器1.2 串口相关的库函数1.3 状态寄存器&#xff08;USART_ SR&#xff09;1.4 数据寄存器&#xff08;USART_ DR&#xff09;1.5 波特率寄存器&#xff08;USART_BRR&#xff09;二、串口配置一般步骤一、STM32串口常…

java static关键字 万字详解

目录 一、为什么需要static关键字&#xff1a; 二、static关键字概述 : 1.作用 : 2.使用 : 三、static修饰成员变量详解 : 1.特点 : 2.细节 : ①什么时候考虑使用static关键字? ②静态变量和非静态变量的区别&#xff1f; ③关于静态变量的初始化问题 : ④关于静态变…

基于springboot+vue的器官捐献系统

基于springbootvue的器官捐献系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&…

内网渗透(四十一)之横向移动篇-PsExec工具远程命令执行横向移动

系列文章第一章节之基础知识篇 内网渗透(一)之基础知识-内网渗透介绍和概述 内网渗透(二)之基础知识-工作组介绍 内网渗透(三)之基础知识-域环境的介绍和优点 内网渗透(四)之基础知识-搭建域环境 内网渗透(五)之基础知识-Active Directory活动目录介绍和使用 内网渗透(六)之基…

CCNP350-401学习笔记(201-250题)

201、An engineer attempts to configure a trunk between switch SW1 and switch SW2 using DTP, but the trunk does not form. Which command should the engineer apply to switch SW2 to resolve this issue? A. switchport mode dynamic desirable B. switchport mode a…

GNU make 中文手册 第一二章 概述与介绍

一、第一章&#xff1a;概述 准备知识 在开始我们关于 make 的讨论之前&#xff0c;首先需要明确一些基本概念&#xff1a; 编译&#xff1a;把高级语言书写的代码&#xff0c;转换为机器可识别的机器指令。编译高级语言后生成的指令虽然可被机器识别&#xff0c;但是还不能…

小程序 npm sill idealTree buildDeps 安装一直没反应

目录 一、问题 二、解决 1、删除.npmsrc 、清除缓存 2、更换镜像源 3、最终检测 一、问题 记录&#xff1a;今天npm 一直安装不成功 显示&#xff1a;sill idealTree buildDeps 我的版本&#xff1a; 我百度到换镜像源安装方法&#xff0c;但我尝试后&#xff0c;依然…