unity和c#问题记录(2)

unity的帧

Unity的渲染是一个持续的过程,每一帧都会经历多个阶段,包括输入处理、游戏逻辑更新、物理模拟、动画计算、渲染等。Update函数是在游戏逻辑更新阶段被调用的,它允许你修改游戏对象的状态(如位置、旋转、缩放等)和进行其他逻辑处理。

1. 输入处理

描述:Unity会检测用户输入,包括键盘、鼠标、触摸屏等设备的输入,并将其转化为游戏的输入数据。这是游戏与用户交互的第一步,决定了游戏如何响应用户的操作。

2. 游戏逻辑更新

描述:在这一阶段,Unity会更新游戏中的逻辑状态,包括游戏对象的位置、旋转、状态等。这些更新通常通过在游戏脚本中编写的代码来实现,如C#或UnityScript。游戏逻辑是游戏的核心,决定了游戏的玩法和规则。

3. 物理模拟

描述:如果游戏中涉及到物理模拟(如碰撞、重力、摩擦力等),Unity会在此阶段进行计算。物理引擎会处理这些物理效果,确保游戏世界中的物体按照物理规律运动。

4. 动画计算

描述:对于需要动画的物体,Unity会在此阶段计算动画的关键帧和插值,以生成平滑的动画效果。动画可以是骨骼动画、帧动画或其他类型的动画,它们共同构成了游戏世界的动态表现。

5. 渲染准备

描述:在渲染之前,Unity会进行一系列的准备工作,包括设置渲染状态(如材质、纹理、光照等)、确定渲染顺序(通过渲染队列和渲染层级)等。这些准备工作为后续的渲染阶段提供了必要的数据和参数。

6. 渲染阶段

描述:渲染阶段是Unity帧渲染流程的核心部分,它可以进一步细分为几何阶段和光栅化阶段。

  • 几何阶段:在GPU中进行,负责对渲染图元进行逐顶点、图元处理。包括顶点变换、顶点着色器计算、裁剪等。这一阶段将顶点数据转换为适合光栅化的格式,并剔除不在视野范围内的图元。
  • 光栅化阶段:同样在GPU中进行,将几何阶段的输出(如三角形)转换为屏幕上的像素。包括三角形光栅化、片元着色器计算、深度测试和颜色混合等步骤。最终生成图像并显示在屏幕上。

7. 后期处理

描述(可选):在某些情况下,Unity还会对渲染后的图像进行后期处理,如添加滤镜、调整色彩平衡等。这些后期处理效果可以增强游戏的视觉效果,使其更加吸引人。

当你在Update函数中修改游戏对象的数据时,这些修改会立即生效,但它们的效果是逐步在渲染过程中展现的。Unity会根据当前的渲染状态和帧率来更新屏幕上的显示,因此你可能会看到游戏对象在连续帧之间平滑地移动或变化。

具体来说,当你在Update函数中修改游戏对象的位置时,这个新的位置会在下一帧渲染时被考虑,并据此更新屏幕上的显示。

Unity中的线程和协程

线程

使用情况

  • 线程在Unity中主要用于处理那些需要长时间运行且不会直接影响游戏主循环的任务,比如大量的数据处理、复杂的物理模拟、网络请求、文件I/O操作等。
  • 由于Unity的许多API都不是线程安全的,因此直接在多线程中调用这些API可能会导致问题。但是,你可以通过一些方法(如使用委托和队列)来在Unity的主线程中安全地处理这些从其他线程返回的数据或结果。

注意事项

  • Unity的渲染和大部分游戏逻辑都在主线程上执行,因此过度使用多线程可能会增加系统的复杂性和维护难度。
  • 需要确保线程之间的同步和数据一致性,避免出现竞态条件和数据不一致的问题。

协程

使用情况

  • 协程是Unity中处理异步操作和延时任务的一种非常方便和高效的方式。
  • 它允许你暂停和恢复代码的执行,而不会阻塞主线程。这使得协程成为处理动画、延时操作、网络请求等任务的理想选择。

优点

  • 协程的使用非常简单,只需要在函数前添加IEnumerator返回类型,并在函数体内使用yield return语句来指定暂停点。
  • 协程的执行由Unity的帧渲染流程控制,因此它们可以在游戏的每一帧中自动恢复执行,而无需手动管理线程或同步问题。

注意事项

  • 协程并不是真正的多线程,它们仍然在主线程上执行。因此,如果协程中的任务过于耗时,仍然可能会导致游戏卡顿。
  • 需要谨慎使用yield return new WaitForSeconds()等延时语句,因为它们会直接影响游戏的帧率。

总结

线程和协程在Unity中都有各自的使用场景和优缺点。对于需要长时间运行且不会直接影响游戏主循环的任务,可以考虑使用线程;而对于需要处理异步操作和延时任务的情况,协程则是一个更加方便和高效的选择。然而,在实际开发中,应根据具体需求和场景来选择合适的工具,以确保游戏的性能和用户体验。

unity函数的执行

  • Awake:在脚本实例被创建时立即调用,用于初始化代码。它在任何Start方法之前调用。
  • Start:在对象首次激活时调用,用于执行仅在对象激活时需要执行的代码。
  • Update:每帧都会调用一次,用于处理游戏逻辑,如移动、碰撞检测等。
  • FixedUpdate:在固定时间间隔内调用,用于处理物理相关的逻辑,确保物理模拟的准确性和稳定性。
  • LateUpdate:在Update之后调用,用于处理需要在所有其他更新之后进行的操作,如相机跟随等。

Unity的运行机制支持一定程度的并发和并行处理,但需要注意的是,Unity的脚本执行是在主线程上进行的,这意味着它们并不是真正的并行执行(即在多个CPU核心上同时执行)。然而,Unity内部可能会使用多线程来优化渲染和物理模拟等任务,但这些任务与脚本执行是分开处理的。

unity中的单例

在Unity中,单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。这对于管理游戏状态、资源管理器、音效管理器等全局对象非常有用。

单例模式的实现方式可以有多种,但基本思想都是确保一个类只有一个实例,并提供一个全局访问点。

public class Singleton  
{  
    // 私有静态变量,用于存储类的唯一实例  
    private static Singleton _instance;  
  
    // 私有构造函数,防止外部通过new创建实例  
    private Singleton()  
    {  
        // 可以在这里进行初始化操作  
    }  
  
    // 公共静态属性,返回类的唯一实例  
    // 如果实例不存在,则创建实例;如果已存在,则直接返回现有实例  
    public static Singleton Instance  
    {  
        get  
        {  
            if (_instance == null)  
            {  
                _instance = new Singleton();  
            }  
            return _instance;  
        }  
    }  
  
    // 类的其他成员...  
}

 上面的代码在多线程环境下可能会遇到问题,因为两个线程可能同时进入

if (_instance == null)的判断,并都认为实例不存在,从而都尝试创建实例。

为了解决这个问题,可以使用lock关键字来确保在同一时刻只有一个线程能够进入创建实例的代码块。

但出于性能和简洁性的考虑,通常推荐使用更高级别的同步机制,如.NET框架提供的Lazy<T>类。

using System;  
  
public class Singleton  
{  
    // 使用Lazy<T>来确保线程安全地延迟初始化  
    private static readonly Lazy<Singleton> _lazyInstance = new Lazy<Singleton>(() => new Singleton());  
  
    // 私有构造函数  
    private Singleton()  
    {  
        // 初始化代码  
    }  
  
    // 公共静态属性,返回类的唯一实例  
    public static Singleton Instance => _lazyInstance.Value;  
  
    // 类的其他成员...  
}

 错误例子

public class Singleton  
{  
    private static Singleton instance;  
    public static Singleton Instance =>instance;
}

 只是简单地返回了 instance 字段,但没有处理字段为 null 的情况,也没有在第一次访问时创建实例。

如果 instance 字段从未被初始化(即始终为 null),那么尝试访问 Instance 属性将总是返回 null,这与单例模式的预期不符。

Lazy<T> 来确保实例的延迟初始化和线程安全

using System;  
  
public class Singleton  
{  
    // 使用Lazy<T>来确保线程安全地延迟初始化实例  
    private static readonly Lazy<Singleton> lazyInstance = new Lazy<Singleton>(() => new Singleton());  
  
    // 私有构造函数,防止外部代码通过new创建实例  
    private Singleton()  
    {  
        // 可以在这里进行初始化操作  
    }  
  
    // 公共静态属性,返回类的唯一实例  
    public static Singleton Instance => lazyInstance.Value;  
  
    // 类的其他成员...  
}

不想使用 Lazy<T>,但想要一个线程安全的单例实现,可以使用双重检查锁定(Double-Checked Locking)模式 

public class Singleton  
{  
    // 使用volatile关键字确保instance的读取是原子的  
    private static volatile Singleton instance;  
    private static object syncRoot = new Object();  
  
    private Singleton()  
    {  
        // 可以在这里进行初始化操作  
    }  
  
    public static Singleton Instance  
    {  
        get  
        {  
            if (instance == null)  
            {  
                lock (syncRoot)  
                {  
                    if (instance == null)  
                    {  
                        instance = new Singleton();  
                    }  
                }  
            }  
            return instance;  
        }  
    }  
  
    // 类的其他成员...  
}

unity中类之间的问题

首先,

在C#中,类中的静态成员(包括静态字段、属性、方法、构造函数等)属于类本身,而不是类的任何特定实例。这意味着无论创建了多少个类的实例,静态成员都只有一份拷贝,被所有实例共享。

转向unity,

在Unity中,类里的静态变量之所以可以被任意不同的脚本调用,主要原因在于静态变量是属于类的,而不是属于类的某个特定实例。这种特性使得静态变量在整个程序中是全局可访问的,只要该类的访问权限允许(比如不是private),那么任何能够访问到这个类的代码都可以访问到这个类的静态变量。

同一个程序集的问题,

这取决于这些脚本(类定义)在Unity项目中的物理和组织结构,但通常情况下,它们都属于同一个程序集。

只要它们都在同一个Unity项目中,并且没有被明确分割到不同的程序集中,那么它们就属于同一个命名空间(除非显式指定了不同的命名空间)和同一个程序集

因此,它们之间可以自由地互相访问静态变量和其他公共成员,只要这些成员的访问权限允许。

需要注意的是,虽然静态变量提供了跨实例共享数据的便利,但它们也带来了潜在的问题,如内存泄漏(如果静态变量引用了不应该持续存在的对象)、难以追踪的依赖关系以及可能的线程安全问题(特别是在多线程环境中)。因此,在使用静态变量时需要谨慎考虑。 

c#密封类

sealed class MySealedClass  
{  
    // 类的成员  
    public void MyMethod()  
    {  
        // 方法实现  
    }  
}  
  
// 尝试继承MySealedClass将会导致编译错误  
// class MyClass : MySealedClass  
// {  
// }

拓展方法

Unity中的MonoBehaviour(通常用作游戏脚本的基类)和普通的C#类一样,并不直接支持“静态类”作为组件附加到游戏物体上。静态类不能实例化,也不能作为MonoBehaviour附加到游戏物体上。然而,静态类中的静态方法和扩展方法仍然可以在Unity脚本中被调用。

在C#中,扩展方法是一种特殊的静态方法,它允许你为已存在的类型“添加”新的方法,而无需修改该类型的源代码。这些扩展方法通过定义在静态类中的静态方法来实现,并且遵循特定的命名规则来标识它们是为哪个类型扩展的。

拓展方法的基本语法

扩展方法的定义包含以下几个关键点:

  1. 静态类:扩展方法必须定义在静态类中。
  2. 静态方法:扩展方法是静态的。
  3. 第一个参数:扩展方法的第一个参数指定了该方法扩展的类型,并且该参数前面必须加上this关键字。
  4. 访问级别:扩展方法的访问级别(如publicinternal等)由包含它的静态类决定。

示例

假设我们有一个名为MyStringExtensions的静态类,我们想要为string类型添加一个扩展方法,用于反转字符串。

using System;  
  
// 静态类  
public static class MyStringExtensions  
{  
    // 扩展方法  
    // 注意:第一个参数是string类型,并且前面有this关键字  
    // 这表明这个方法是为string类型扩展的  
    public static string Reverse(this string s)  
    {  
        char[] charArray = s.ToCharArray();  
        Array.Reverse(charArray);  
        return new string(charArray);  
    }  
}  
  
// 使用扩展方法  
class Program  
{  
    static void Main(string[] args)  
    {  
        string original = "hello";  
        string reversed = original.Reverse(); // 注意这里,就像Reverse是string的一部分一样  
        Console.WriteLine(reversed); // 输出:olleh  
    }  
}

在上面的例子中,MyStringExtensions类包含了一个名为Reverse的静态方法,它接受一个string类型的参数(前面带有this关键字),并返回该字符串的反转版本。尽管Reverse方法是定义在MyStringExtensions类中的,但你可以像它是string类本身的一部分一样来调用它,只需确保在调用它的代码文件顶部添加了包含MyStringExtensions类的命名空间的using指令(如果它位于不同的命名空间中)。

注意事项

  • 扩展方法不会修改它们扩展的类型的实例。在上面的例子中,Reverse方法返回了一个新的字符串,而不是修改了传入的字符串。
  • 扩展方法是在编译时解析的,而不是在运行时。这意味着编译器会检查你调用的扩展方法是否在你的项目中可用,并且它的签名是否与你的调用匹配。
  • 如果一个类型具有与扩展方法同名的实例方法,则实例方法将优先于扩展方法被调用。这是因为编译器在解析方法调用时会首先查找实例方法。

杂记

在项目中为预设体(Prefab)拖入一个新脚本时,不会直接影响场景中已存在的该预设体的实例。

预设体(Prefab)是Unity中一种特殊的游戏对象,它可以在场景中被多次实例化而不影响原始预设体本身

NGUI的Button组件,用代码的形式增加要执行的函数,

btn.onClick.Add(new EventDelegate(函数名));

EventDelegate是NGUI中的类,UGUI中 为myButton.onClick.AddListener(HandleButtonClick);  

NGUI的Resources.Load<T>("地址信息");//地址不能有后缀,前缀也不要

至少unity2021.3.16配上NGUI Next-Gen UI 2023.08.01是这样

Assets/Resources/Atlas/create character.asset

屏幕尺寸:屏幕对角线的长度

屏幕比例:长:宽的像素点的数量比

显式实现接口,接口中的as,同名需点出...

挂到物体上的类不能是static脚本里的

public才可实例

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值