Unity3D代码开发编程技巧VIP

1、VS中调试代码

如果是Unity编辑器:

在问题处增加断点,同时选择“附加到Unity并播放”。

点击执行,此时Unity弹出如下提示:

点击“Enable debugging for all projects”

如果是Tuanjie编辑器(Unity的国内版本):

则需要确保使用 Visual Studio 2022 17.11 或更高版本,比如我使用的是:17.13.5。

其他使用方法同Unity编辑器。

如果VS版本过低,点击“附加到Unity并播放”,会提示选择Unity实例,但是没有一个Unity实例列表。

2、无返回委托使用Action类

当委托无返回,且参数个数不超过16个的情况下,可以直接使用Action来代替delegate声明。

3、枚举值最后一个为count自动计数

public enum InventoryLocation
{
    player, // 在角色手中
    chest, // 在箱子里
    count // 枚举位置的计数, 值为2,只要把count放在最后一位,自动进行了计数
}

只要把count放到枚举类的最后一个,那么count就是枚举值的个数。

4、按键使用

(1)聚焦物体按F键

聚焦物体:按住F键可以将场景视图聚焦到选中的物体上。

(2)使用Alt键移动界面

在Animator界面中,按住Alt键,然后鼠标左键移动,所有的的节点会跟着鼠标移动。

(3)查看粒子特效

(4)Anchor Presets面板参数

该面板用于设置锚点位置。

Shift:Also set Pivot。调整UI元素轴心点位置。

Alt:Also set position。保持元素的视觉位置不变。

Shift + Alt:结合两者优势,用于锚点、轴心点和位置需要同时精确控制的复杂场景。

锚点和轴心点的区别:

锚点是UI元素相对于父容器的定位参考点,决定了元素如何响应父容器的大小变化。

轴心点是UI元素自身的中心点,也是旋转和缩放的基准点。

视觉位置含义:UI元素在屏幕上实际显示的位置,是用户最终看到的UI元素所在的位置。当使用Alt键并选择 Anchor Presets 中的预设时,“Also set position” 表示通过调整Rect TransformPosition属性来保持元素的视觉位置不变,即无论锚点如何改变,元素在屏幕上的显示位置都不会发生变化,这在需要在更改锚点类型时避免元素位置跳动的场景中非常有用。

(5)Image的Image Type选项

  • Simple:直接将纹理显示在UI元素上。适用于不需要九宫格切图的简单图片,比如纯色背景、图标等。
  • Sliced:九宫格切片模式,将图片分为9个区域(4角、4边、中心),适用于需要保持边缘细节的可拉伸元素,如按钮、对话框背景等。
  • Tiled:平铺模式,图片会重复排列以填充整个UI区域。适用于需要重复图案的背景,如格子地板、纹理墙面等。
  • Filled:填充模式,用于实现进度条、血条、倒计时等效果。

(6)Horizontal Layout Group组件

Horizontal Layout Group组件用于自动排列子 UI 元素,使其在水平方向上依次布局。

  • Control Child Size(控制子对象大小):该属性决定是否强制子元素使用特定的宽高

  • Use Child Scale(使用子对象缩放):该属性决定是否考虑子元素自身的缩放因子(RectTransform中的 Scale 属性)

  • Child Force Expand(子对象强制扩展):该属性决定是否强制子元素填充额外空间

5、ExecuteAlways标识

使脚本的实例在播放模式期间以及编辑时始终执行。

(1)正常情况下MonoBehaviour的执行规则

在 Unity 里,MonoBehaviour 是脚本的基础类,正常来说,这些脚本只有在游戏处于播放模式,而且脚本所在的游戏对象在主场景里时才会执行。也就是说,当你在编辑场景(不播放游戏)的时候,或者你在编辑预制件(一种可重复使用的游戏对象模板)的时候,不管游戏是不是处于播放模式,这些脚本都不会运行。

(2)ExecuteAlways 改变了执行规则


当你给一个 MonoBehaviour 脚本加上 [ExecuteAlways] 属性后,这个脚本就会变得 “全天候” 执行了。不管是在游戏播放模式,还是在编辑模式(不播放游戏),又或者是在预制件编辑模式下,这个脚本的回调函数都会一直执行。


(3)使用场景举例


假设你正在开发一个编辑器工具。比如说,你想做一个脚本,它能在场景里实时显示游戏对象的一些信息,不管游戏有没有在播放。这种情况下,就可以给这个脚本加上 [ExecuteAlways] 属性。这样,你在编辑场景的时候也能看到这些信息,不用每次都进入播放模式去查看。

(4)注意的问题

避免错误修改对象:有些脚本里包含的逻辑是专门给游戏播放时用的,这些逻辑可能会修改游戏对象。如果在编辑模式或者预制件编辑模式下运行这些逻辑,就可能会错误地修改对象。为了避免这种情况,你可以使用 Application.IsPlaying 这个方法来检查当前是不是处于游戏播放模式,还可以传入脚本所在的游戏对象,看看它是不是在游戏世界里。例如:

using UnityEngine;

[ExecuteAlways]
public class MyScript : MonoBehaviour
{
    void Update()
    {
        if (Application.IsPlaying(gameObject))
        {
            // 这里放只有在游戏播放时才执行的逻辑
            Debug.Log("游戏正在播放,执行播放逻辑");
        }
        else
        {
            // 这里放编辑模式下执行的逻辑
            Debug.Log("处于编辑模式,执行编辑逻辑");
        }
    }
}

6、C#编程相关

(1)Remove的使用

在List<xx>类型数据中,通过Remove()方法可以删除数据

在Dictionary<xx, yy>类型数据中,通过Remove()方法会找到该key的数据然后删除掉。

(2)Dictionary

1)获取某个key对应的值

使用TryGetValue(key, out xx)的方法,返回内容在xx中。取到值返回true,否则返回false。

2)获取第i个元素的值

dictionary.ElementAt(i)

3)添加元素

使用Add(key, value)的方法

(3)显示类型转换 

public static explicit operator用于定义显示类型转换运算符。

编程时,将一种数据类型转换为另一种数据类型,分为隐式转换和显示转换。隐式转换是编译器自动完成的,而显示转换则需要程序员明确地进行转换操作。

显示类型转换运算符的基本语法:

public static explicit operator 目标类型(源类型 参数名)
{
    // 实现转换逻辑
    return 转换后的目标类型实例;
}

显示转换的例子:

using System;

// 定义一个自定义的英寸类型
public class Inches
{
    public double Value { get; set; }

    public Inches(double value)
    {
        Value = value;
    }
}

// 定义一个自定义的厘米类型
public class Centimeters
{
    public double Value { get; set; }

    public Centimeters(double value)
    {
        Value = value;
    }

    // 定义从 Inches 到 Centimeters 的显式转换运算符
    public static explicit operator Centimeters(Inches inches)
    {
        // 1 英寸 = 2.54 厘米
        return new Centimeters(inches.Value * 2.54);
    }
}

class Program
{
    static void Main()
    {
        // 创建一个 Inches 实例
        Inches inches = new Inches(10);

        // 显式地将 Inches 转换为 Centimeters
        Centimeters centimeters = (Centimeters)inches;

        Console.WriteLine($"10 英寸等于 {centimeters.Value} 厘米。");
    }
}

注意事项:

  • 转换运算符必须是静态的,因为他们是定义在类型上,而不是实例上
  • 转换运算符不能有返回类型,因为返回类型已经在运算符声明中指定了

(4)this的作用

在类中,this指的是当前实例

1)区分同名的局部变量和实例字段

public class Person
{
    private string name;

    public Person(string name)
    {
        // 使用 this 区分构造函数参数和类的字段
        this.name = name;
    }
}

this.name指的是类的实例字段

2)调用同一个类的其他构造函数

public class Rectangle
{
    private int width;
    private int height;

    public Rectangle() : this(0, 0)
    {
        // 默认构造函数调用带参数的构造函数
    }

    public Rectangle(int width, int height)
    {
        this.width = width;
        this.height = height;
    }
}

3)返回当前实例

public class Calculator
{
    private int result;

    public Calculator Add(int number)
    {
        result += number;
        return this; // 返回当前实例
    }

    public Calculator Subtract(int number)
    {
        result -= number;
        return this; // 返回当前实例
    }

    public int GetResult()
    {
        return result;
    }
}

// 使用示例
Calculator calc = new Calculator();
int finalResult = calc.Add(5).Subtract(3).GetResult();

在示例中,Add和Subtract方法都返回this,这样就可以进行方法链的调用。

4)作为参数传递当前实例

public class MyClass
{
    public void DoSomething()
    {
        AnotherClass another = new AnotherClass();
        another.Process(this); // 传递当前实例
    }
}

public class AnotherClass
{
    public void Process(MyClass obj)
    {
        // 处理传入的 MyClass 实例
    }
}

(5)default(T)方法

其用途是给出值类型或者引用类型的默认值。

针对不同类型的默认值:

T为值类型:会返回该值类型的初始值。例如int默认值为0,bool默认值为false

T为引用类型:会返回null,这表明该引用不指向任何对象。

(6)#pragma warning

#pragma warning 属于预处理执行,其用途是对编译器警告进行控制。

#pragma warning disable 414 的作用是把编译器警告 CS414 屏蔽掉。

比如有如下报警:

通过如下语句可以暂时取消warning报警:

#pragma warning disable 414
    private Direction playerDirection;
#pragma warning restore 414

(7)Enum.TryParse

用于将字符串转换为枚举值的静态方法,它属于 System.Enum 类。

带泛型参数的语法结构:

public static bool TryParse<TEnum>(string value, out TEnum result)
    where TEnum : struct, Enum;

默认情况下,TryParse区分大小写。如果需要不区分大小写,可以使用另一个重载方法:

Enum.TryParse("green", true, out Color result); // 第二个参数为 true 表示不区分大小写

7、VS中显示代码结构

显示代码结构的效果:

安装方法:

点击Extension(扩展) ->  Manage Extensions(管理扩展),搜索CodeNav进行安装。

8、获取当前场景下特定组件的所有游戏对象

使用FindObjectsOfType方法,它允许你找到当前场景中所有指定组件的游戏对象(GameObject)。这对于需要遍历特定组件的所有对象并执行某些操作时非常有用。

如果你想要找到所有包含特定组件的游戏对象,比如Health组件,你可以这样做:

Health[] healthComponents = FindObjectsOfType<Health>();
foreach (Health health in healthComponents)
{
    // 对每个包含Health组件的游戏对象执行操作
    health.TakeDamage(10);
}

9、Tilemap类的方法

Tilemap类是用于管理和操作瓦片地图的核心类。

(1)CompressBounds()方法

CompressBounds()方法用于压缩Tilemap的边界框(Bounds)。

Tilemap的边界框定义了Tilemap中所有包含瓦片(Tile)的单元格的最小外接矩形范围。

在某些情况下,由于瓦片的添加、移除或移动操作,Tilemap的边界框可能会包含一些没有瓦片的空白区域,使用CompressBounds()方法可以调整边界框,使其刚好包围所有实际存在瓦片的单元格,从而去除这些不必要的空白区域。

注意事项:

  • 调用CompressBounds()方法后,Tilemap的边界框会立即更新,但不会影响Tilemap中的瓦片布局。
  • 如果Tilemap中的瓦片布局发生了变化,需要再次调用CompressBounds()方法来更新边界框。

(2)GetTile()方法

用于获取瓦片地图中指定位置的瓦片。

方法:public TileBase GetTile(Vector3Int position);

一个 Vector3Int 类型的参数,表示瓦片在瓦片地图坐标系中的位置。Vector3Int 包含三个整数值 (x, y, z),分别代表瓦片在 X、Y、Z 轴上的位置。

(3)SetTile()方法

函数原型:

public void SetTile(Vector3Int position, TileBase tile);

参考示例:

using UnityEngine;
using UnityEngine.Tilemaps;

public class TilemapExample : MonoBehaviour
{
    public Tilemap tilemap;
    public TileBase customTile;

    void Start()
    {
        // 在坐标(0, 0, 0)处设置瓦片
        tilemap.SetTile(new Vector3Int(0, 0, 0), customTile);
        
        // 清除坐标(1, 0, 0)处的瓦片
        tilemap.SetTile(new Vector3Int(1, 0, 0), null);
    }
}

(4)Tilemap.cellBounds

Tilemap.cellBounds属于BoundsInt类型的属性,它代表了Tilemap里所有已放置瓦片的最小包围盒。此包围盒是在瓦片地图的单元格空间里定义的,单元格空间是基于瓦片地图的网格系统,以整数坐标来表示每个瓦片的位置。

tilemap.cellBounds.min是一个Vector3Int类型的值,它表示最小包围盒在单元格空间中的最小坐标。一般是Tilemap里所有已放置瓦片的最左下角(在二维平面上)或者最左前下角(在三维空间中)的单元格坐标。

tilemap.cellBounds.max同样是Vector3Int类型的值,它代表最小包围盒在单元格空间中的最大坐标。一般是Tilemap里所有已放置瓦片的最右上角(在二维平面上)或者最右后上角(在三维空间中)的单元格坐标。

示例代码:

Vector3Int startCell = tilemap.cellBounds.min;
Vector3Int endCell = tilemap.cellBounds.max;

(5)TileBase类

Tilemap是Unity中用于创建2D瓦片地图的工具,TileBase则是所有瓦片类型的基类。

TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));

tilemap是Tilemap类的一个实例,代表一个瓦片地图。

GetTile是Tilemap类的一个方法,其作用是获取指定单元格位置的瓦片。

TileBase类型的变量tile,用于存储从Tilemap中获取到的瓦片。如果指定位置存在瓦片,tile就会引用该瓦片;若指定位置没有瓦片,tile的值则为null。

Tilemap是瓦片地图,TileBase是瓦片。Tilemap提供了管理瓦片的方法,而Tilebase则定义了瓦片的属性和行为。

示例代码:

using UnityEngine;
using UnityEngine.Tilemaps;

public class TilemapExample : MonoBehaviour
{
    public Tilemap tilemap;
    public int x = 1;
    public int y = 2;

    void Start()
    {
        // 获取指定位置的瓦片
        TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));

        if (tile != null)
        {
            Debug.Log("在位置 (" + x + ", " + y + ") 找到了瓦片。");
        }
        else
        {
            Debug.Log("在位置 (" + x + ", " + y + ") 未找到瓦片。");
        }
    }
}

(6)ClearAllTiles()方法

清除tilemap里的所有tiles。

10、EditorUtility.SetDirty方法

它时一个非常实用的编辑器脚本函数。

用于标记某个UnityEngine.Object对象为已修改状态。

在Unity编辑器中,当你对场景中的对象或者项目中的资源进行修改时,Unity需要知道哪些对象被修改过,以便在保存项目时能够正确保存这些更改。EditorUtility.SetDirty()函数的作用就是告诉Unity编辑器,指定的对象已经被修改,需要在下次保存项目时将这些更改保存下来。

11、Grid类

(1)作用

Grid(网格)能在场景里构建一个规则的网格系统。

1)布局管理:通过Grid能够对游戏对象进行精准定位,让它们按照网格的规则排列,从而实现整齐有序的布局。

2)导航辅助:网格可以作为导航的基础,助力角色沿着网格移动,让移动逻辑变得更简单。

3)地图创建:Grid能用于创建2D或者3D地图,像瓦片地图可以借助网格来放置不同类型的瓦片。

像Tilemap地图就附带了Grid组件。

所以在使用时,可以使用Grid grid变量存放整个Tilemap地图:

通过Grid变量,可以实现worldPosition到gridPosition的转化。

转化的好处是??

当我们确定一个grid的属性时,Dictionary是根据(grid.x, grid.y, grid.z)的坐标设置Dictionary<位置,属性>信息的,所以确定了鼠标对应的grid信息,就可以拿到属性信息。

如果取鼠标的原始信息,不经过转换,我们就不知道grid信息。

每个worldPosition都写到Dictionary中就更没有必要了,world坐标很大(无穷大),Dictionary很大。

(2)Grid.WorldToCell的作用

它的主要功能是把世界空间中的坐标转换为网格空间中的单元格坐标。

在游戏开发过程中,当你需要明确某个世界位置对应网格中的哪个单元格时,就可以使用这个方法。

12、Unity注解

(1)RequireComponent特性

其用途是确保当一个脚本被添加到游戏对象时,指定的组件也会被添加到该游戏对象上。

要是指定的组件并不存在于游戏对象上,Unity会自动添加该组件。

示例:

[RequireComponent(typeof(GenerateGUID))]
public class GridPropertiesManager 

当GridPropertiesManager组件添加到某个游戏对象时,若该游戏对象上没有GenerateGUID组件,Unity就会自动添加GenerateGUID组件。要是游戏对象上已经有了GenerateGUID组件,那就不会再次添加。

所以,添加GridPropertiesManager组件时,若游戏对象原本没有GenerateGUID组件,就会自动创建该组件。

(2)HideInInspector

HideInInspector 是一个用于修饰公有变量的特性。它的主要作用是让这些变量在 Inspector 面板中不再显示,以此来防止它们被意外修改

13、获取组件的方式

(1)GetComponentInParent方法

原型:

public T GetComponentInParent<T>();

T表示你要查找的组件类型。

功能:它会在当前游戏对象上查找指定类型的组件,要是没找到,就会继续在其父对象上查找,然后是父对象的父对象,以此类推,直到找到指定类型的组件或者到达场景的根对象。一旦找到符合条件的组件,就会立刻返回该组件;若没找到,就返回null。

(2)GetComponent和FindObjectOfType的区别

GetComponent<>()作用域单个GameObject,只能获取当前游戏对象或其子对象上的组件。

效率高:因为只在单个对象或其层级内查找,开销较小,在Update中使用也安全。

GameObject.FindObjectOfType<>()会在整个场景中搜索并返回第一个匹配类型的组件实例。

效率低:需要遍历场景中的所有激活对象,性能开销大。避免在Update中使用。

14、碰撞检测的方法

(1)Physics2D.OverlapBoxAll

作用:检测一个2D矩形区域内的所有碰撞体。

方法的定义:

public static Collider2D[] OverlapBoxAll(Vector2 point, Vector2 size, float angle, int layerMask = Physics2D.DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
  • point:矩形区域的中心点,其类型为Vector2
  • size:矩形区域的大小,同样是Vector2类型
  • angle:矩形区域相对于Z轴的旋转角度,单位是度
  • layerMask:一个层掩码,用来筛选想要检测的碰撞体所在的层,默认值是Physics2D.DefaultRaycastLayers
  • minDepth:检测区域的最小深度(Z轴值),默认值是负无穷大
  • maxDepth:检测区域的最大深度(Z轴值),默认值是正无穷大

返回值:该方法会返回一个Collider2D数组,数组里包含了所有与指定矩形区域重叠的2D碰撞体。

示例代码:

using UnityEngine;

public class OverlapBoxExample : MonoBehaviour
{
    public Vector2 boxSize = new Vector2(1f, 1f);
    public float boxAngle = 0f;
    public LayerMask layerMask;

    void Update()
    {
        // 获取当前物体的位置
        Vector2 point = transform.position;

        // 检测矩形区域内的所有碰撞体
        Collider2D[] colliders = Physics2D.OverlapBoxAll(point, boxSize, boxAngle, layerMask);

        // 遍历所有检测到的碰撞体
        foreach (Collider2D collider in colliders)
        {
            Debug.Log("Detected collider: " + collider.name);
        }
    }
}

(2)Physics2D.OverlapBoxNonAlloc

Physics2D.OverlapBoxNonAlloc 方法的作用是检测一个 2D 矩形区域内的所有碰撞体。与 Physics2D.OverlapBox 不同,OverlapBoxNonAlloc 方法不会动态分配内存,而是使用预先分配好的数组来存储检测到的碰撞体,这样可以避免频繁的内存分配和回收,从而提升性能,尤其在需要频繁进行重叠检测的场景中优势明显。

方法定义:

public static int OverlapBoxNonAlloc(Vector2 point, Vector2 size, float angle, Collider2D[] results, int layerMask = Physics2D.AllLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
  • point:这是矩形区域的中心点,是一个二维向量(Vector2)。
  • size:它代表矩形区域的大小,同样是二维向量,分别表示矩形在 X 轴和 Y 轴上的尺寸。
  • angle:此参数为矩形的旋转角度,单位是度。
  • results:这是一个预先分配好的 Collider2D 数组,用于存储检测到的碰撞体。

15、协程相关类

(1)WaitForSeconds类

作用:用于创建一个等待指定秒数的对象,在协程中使用WaitForSeconds可以让程序暂停执行一段事件,然后再继续执行后续代码。

示例代码:

using UnityEngine;
using System.Collections;

public class ToolUseExample : MonoBehaviour
{
    // 声明一个私有的 WaitForSeconds 变量
    private WaitForSeconds afterUseToolAnimationPause;

    void Start()
    {
        // 初始化 WaitForSeconds 变量,设置等待时间为 2 秒
        afterUseToolAnimationPause = new WaitForSeconds(2f);
        // 启动协程
        StartCoroutine(UseTool());
    }

    IEnumerator UseTool()
    {
        // 模拟使用工具的动画
        Debug.Log("使用工具的动画开始");
        // 等待指定的时间
        yield return afterUseToolAnimationPause;
        // 等待结束后继续执行
        Debug.Log("使用工具的动画结束,等待时间已过");
    }
}

(2)协程与 yield return 的工作机制

在 Unity 中,协程是一种特殊的函数,它可以暂停执行并在之后的某个时间点继续执行。yield return 语句就是用来控制协程暂停和继续的关键。当协程执行到 yield return 语句时,协程会暂停执行,将控制权交还给调用者,直到满足特定的条件后才会恢复执行。

比如:

private IEnumerator CollectInPlayerDirectionRoutine(GridPropertyDetails gridPropertyDetails, ItemDetails equippedItemDetails, Vector3Int playerDirection)
{
    PlayerInputIsDisabled = true;
    playerToolUseDisabled = true;

    ProcessCropWithEquippedItemInPlayerDirection(playerDirection, equippedItemDetails, gridPropertyDetails);

    yield return pickAnimationPause;

    // After animation pause
    yield return afterPickAnimationPause;

    PlayerInputIsDisabled = false;
    playerToolUseDisabled = false;
}

执行“yield return pickAnimationPause”时,协程会暂停执行,暂停的时长由 pickAnimationPause 决定。

yield return null 会使协程暂停执行,直到下一帧。

16、System.Linq包

该命名空间提供了用于查询和操作数据集合的功能,这些功能基于LINQ(Language Integrated Query,语言集成查询)计数。常用的功能有:

(1)筛选数据

借助Where方法,可以从集合里面筛选出满足特定条件的元素。

using System.Collections.Generic;
using System.Linq;

// ...

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
// evenNumbers 现在包含 { 2, 4, 6 }

(2)排序数据

OrderBy和OrderByDescending方法可以对集合进行排序。

using System.Collections.Generic;
using System.Linq;

// ...

List<int> numbers = new List<int> { 3, 1, 4, 1, 5, 9 };
var sortedNumbers = numbers.OrderBy(n => n);
// sortedNumbers 现在包含 { 1, 1, 3, 4, 5, 9 }

(3)投影数据

Select方法可以将集合元素投影成新的形式。

using System.Collections.Generic;
using System.Linq;

// ...

List<int> numbers = new List<int> { 1, 2, 3 };
var squaredNumbers = numbers.Select(n => n * n);
// squaredNumbers 现在包含 { 1, 4, 9 }

(4)聚合操作

可使用Sum、Average、Min、Max等方法进行聚合操作。

using System.Collections.Generic;
using System.Linq;

// ...

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int sum = numbers.Sum();
// sum 现在等于 15

(5)查找元素

First、FirstOrDefault、Last、LastOrDefault等方法能用于查找集合中的元素。

using System.Collections.Generic;
using System.Linq;

// ...

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int firstEven = numbers.First(n => n % 2 == 0);
// firstEven 现在等于 2

(6)在Unity中的应用场景

System.Linq命名空间的功能在处理游戏对象集合、组件集合等方面十分有用。例如,可以筛选出具有特定组件的游戏对象,或者对一组游戏对象按某种属性排序。

using UnityEngine;
using System.Linq;

public class LinqExample : MonoBehaviour
{
    void Start()
    {
        // 获取场景中所有带有 Rigidbody 组件的游戏对象
        var rigidbodies = FindObjectsOfType<GameObject>()
            .Where(g => g.GetComponent<Rigidbody>() != null)
            .ToList();

        // 按 Rigidbody 的质量排序
        var sortedRigidbodies = rigidbodies.OrderBy(g => g.GetComponent<Rigidbody>().mass);

        foreach (var rb in sortedRigidbodies)
        {
            Debug.Log(rb.name + " has mass: " + rb.GetComponent<Rigidbody>().mass);
        }
    }
}

17、Camera类

(1)将ScreenPoint转为WorldPosition

Vector3 screenPosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0f);

Vector3 worldPosition = mainCamera.ScreenToWorldPoint(screenPosition);

return worldPosition; 

18、RectTransform类

(1)概念

RectTransform主要用于处理UI元素的布局和变换。作用:

1)定义UI元素的位置和大小

通过锚点(Anchors)和偏移(Offsets)来确定UI元素在画布(Canvas)上的位置。

通过设置RectTransform的宽度和高度属性来定义UI元素的大小。

2)实现自适应布局

通过锚点实现UI元素根据其父元素或画布的大小进行自适应调整。

通过设置偏移量,可以精确控制UI元素相对于锚点的位置。

通过设置伸缩属性,可以让UI元素在某个方向上自动伸缩。

3)支持层级结构

子元素的位置和大小可以相对于父元素进行调整。

子元素会按照层级顺序进行渲染,即后添加的子元素会覆盖在先添加的子元素之上。可以通过调整元素在层级中的顺序来控制渲染顺序。

4)与其他组件协同工作

与其他UI组件(如Image、Text等)一起使用,为这些组件提供布局和变换信息。例如,Image组件用于显示图像,而RectTransform则决定了图像在屏幕上的位置和大小。

与动画系统(Animator)结合使用,实现UI元素的动画效果。可以通过动画来改变RectTransform的位置、大小、旋转等属性,从而创建出丰富的UI动画。

(2)RectTransformUtility.PixelAdjustPoint方法

功能:获取坐标在特定RectTransform中的位置。这个位置是经过像素调整的,目的是保证在不同分辨率的屏幕上,位置都能正确映射到RectTransform上。

Vector2 screenPosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);

return RectTransformUtility.PixelAdjustPoint(screenPosition, cursorRectTransform, canvas);

19、Vector类

(1)Vector3Int和Vector3的区别

Vector3:其各分量(x、y、z)的数据类型是float(单精度浮点数)。

Vector3Int:各分量的数据类型是int(整数)。

(2)Vector3.Normalize功能

将一个三维向量转换为单位向量(长度为1的向量),其主要作用是仅仅保留向量的方向,而忽略其长度。

其作用是获取方向,例如角色朝向、光线方向。

20、例子系统

(1)Duration和Start Lifetime的区别

Duration(持续时间):例子系统从启动到停止发射新粒子的总时长(秒)。

Start Lifetime(初始生命周期):每个例子从诞生到消失的存活时间(秒)。

(2)Simulation Space中Local和World的区别

1)Local

含义:粒子的运动基于粒子系统自身的局部坐标系。粒子会跟随粒子系统所在游戏对象的移动、旋转和缩放而相应改变位置、方向和大小 。就好比粒子 “附着” 在粒子系统上,与粒子系统保持相对固定的位置关系。  

应用场景:适用于需要粒子与特定游戏对象保持关联的效果。例如飞机飞行时尾部喷射的烟雾,烟雾粒子会随着飞机的移动、转向而移动、转向 ;汽车行驶时车轮扬起的尘土,尘土粒子会跟随汽车的运动而运动。

2)World

含义:粒子的运动基于场景的世界坐标系。粒子一旦发射出来,其位置就固定在世界空间中,不会受到粒子系统所在游戏对象后续移动、旋转和缩放的影响 。粒子系统只是决定粒子发射的初始位置和状态,之后粒子在世界空间中独立运动。

应用场景:适合制作粒子与发射器后续运动无关的效果。比如从泡泡枪中吹出的泡泡,泡泡生成后会在世界空间中受重力、风力等影响自由飘动,而不受泡泡枪移动的干扰;又如在固定位置爆炸产生的火花,火花向四周飞散后,不会因为爆炸源(如炸弹模型)的移动而改变自身运动轨迹 。

3)纹理(Texture)和材质(Material)的区别
纹理 是粒子的 “皮肤”,定义外观细节。
材质 是粒子的 “渲染规则”,决定如何显示纹理。

21、动画系统

(1)animator.GetCurrentAnimatorStateInfo

animator.GetCurrentAnimatorStateInfo(int layerIndex) 是一个用于获取动画状态信息的重要方法,这些信息包括动画名称、持续时间、当前进度、是否循环等。

参数 layerIndex:
Unity 的 Animator 支持多层动画叠加(例如,角色可以同时播放走路动画和挥手动画)。
0 表示基础层(Base Layer),这是 Animator 中默认的动画层。大多数简单的动画系统只使用基础层

使用例子:

while (!animator.GetCurrentAnimatorStateInfo(0).IsName("Harvested"))
{
    yield return null;
}

(2)Has Exit Time属性

Has Exit Time是动画状态机中两个状态之间过渡(Transition)的属性。 启用该选项后,当前动画必须播放到一定比例或时间后才能触发过渡,确保动画完整执行。 禁用该选项后,过渡可以立即发生(例如基于条件或事件),无需等待当前动画完成。

(3)Animator组件的Apply Root Motion

动画中的位移和旋转数据直接应用到角色上。比如角色的 "向前跑" 动画会自动让角色在场景中向前移动。

如果没有勾选这个选项,动画只会“向前跑”但是角色在场景中不会向前移动。

(4)Animator.StringToHash

主要功能是把动画状态机里的字符串转换为对应的整数哈希值。

在进行动画状态切换或者参数设置时,使用哈希值(整数)要比直接使用字符串高效得多。这是因为字符串比较需要逐个字符进行检查,而整数比较仅仅是数值的比对。

示例:

using UnityEngine;

public class AnimationController : MonoBehaviour
{
    private Animator animator;
    private int jumpTriggerHash;

    void Start()
    {
        animator = GetComponent<Animator>();
        
        // 预先计算哈希值
        jumpTriggerHash = Animator.StringToHash("Jump");
    }

    void Update()
    {
        // 使用哈希值设置动画参数
        if (Input.GetButtonDown("Jump"))
        {
            animator.SetTrigger(jumpTriggerHash);
        }

    }
}

(5)AnimationClip类

动画片段类。

该类用于存储动画数据,这些数据能够控制角色运动、物体变换以及各种动画效果。

示例:

using UnityEngine;
using UnityEditor; // 仅在编辑器环境使用

public class AnimationClipExample : MonoBehaviour
{
    [ContextMenu("Create Animation")]
    public void CreateSimpleAnimation()
    {
        // 创建新的动画剪辑
        AnimationClip clip = new AnimationClip();
        clip.name = "SimpleFloat";
        clip.frameRate = 60;

        // 设置循环模式
        AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clip);
        settings.loopTime = true;
        AnimationUtility.SetAnimationClipSettings(clip, settings);

        // 创建位置Y轴的动画曲线
        AnimationCurve curve = new AnimationCurve();
        curve.AddKey(0f, 0f);     // 时间0,Y=0
        curve.AddKey(1f, 1f);     // 时间1,Y=1
        curve.AddKey(2f, 0f);     // 时间2,Y=0
        curve.postWrapMode = WrapMode.Loop; // 设置循环模式

        // 将曲线应用到Y轴位置
        clip.SetCurve("", typeof(Transform), "localPosition.y", curve);

        // 保存动画文件到项目
        AssetDatabase.CreateAsset(clip, "Assets/SimpleFloat.anim");
        AssetDatabase.SaveAssets();

        Debug.Log("动画创建成功!路径:Assets/SimpleFloat.anim");
    }
}

使用步骤:

  1. 将此脚本挂载到任意GameObject上
  2. 在Inspector面板右键点击脚本组件
  3. 选择“Create Animation”菜单项
  4. 项目中会自动生成"SimpleFloat.anim"动画文件

查看效果:

  1. 创建Animator Controller
  2. 将生成的动画文件拖入控制器
  3. 给物体添加Animator组件并关联控制器
  4. 运行游戏即可看到物体上下浮动效果

(6)animatorOverrideController类

动画覆盖控制器类。

作用:在运行时动态替换动画片段,同时保留原控制器的参数与状态机逻辑。

创建并应用覆盖控制器的方法:

// 第一行:创建一个AnimatorOverrideController实例,以现有的RuntimeAnimatorController为基础
animatorOverrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);

// 第二行:把新创建的覆盖控制器应用到Animator组件上
animator.runtimeAnimatorController = animatorOverrideController;

第2行代码将新创建的覆盖控制器应用到Animator组件上。从这以后,Animator就会使用覆盖控制器中的动画设置,不过状态机的转换逻辑遵循原控制器。

实际应用的例子:

// 假设已经获取到了Animator组件
Animator animator = GetComponent<Animator>();

// 创建并应用覆盖控制器
AnimatorOverrideController overrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
animator.runtimeAnimatorController = overrideController;

// 加载新的跑步动画
AnimationClip specialRunClip = Resources.Load<AnimationClip>("SpecialRun");

// 替换基础跑步动画
overrideController["BaseRun"] = specialRunClip;

22、预制体prefab

(1)Prefab Variant预制体变体

Prefab Variant是基于现有预制体创建的一种特殊预制体,它允许你在保留原始预制体结构和功能的同时,对特定实例进行个性化修改。

Prefab Variant(预制体变体)和复制预制体虽然都能创建相似的对象,但它们的实现机制和适用场景有本质区别。

Prefab Variant

  • 继承关系:变体与基础预制体保持动态链接,自动继承其结构、组件和属性。
  • 差异化存储:仅存储与基础预制体不同的属性(称为 “覆盖值”),节省项目空间。
  • 双向更新
    • 基础→变体:修改基础预制体时,未被覆盖的属性会自动更新到所有变体。
    • 变体→基础:可通过 “Apply Overrides” 将变体的修改合并回基础预制体。

复制预制体

  • 独立副本:复制后的预制体与原始预制体完全分离,没有任何链接关系。
  • 完整数据:包含原始预制体的所有属性的独立副本,即使未修改也会保存。
  • 单向更新
    • 原始→复制:修改原始预制体不会影响复制体。
    • 复制→原始:两者无关联,无法同步修改。

建议:优先使用 Prefab Variant,特别是在需要长期维护和更新的项目中。复制预制体仅适用于简单、一次性的场景。

23、界面UI控制组件

(1)Aspect Ratio Filter组件

用于控制UI元素的高宽比(Aspect Ratio),确保其在不同屏幕尺寸下保持特定的比例关系。

Aspect Mode的选项:

  • Fit In Parent:元素会在父容器内尽可能大,但保证完全可见(不超出父容器边界)。
  • Envelope Parent:元素会完全覆盖父容器,但可能部分内容超出父容器而被裁剪。
  • Width Controls Height:元素宽度由父容器决定,高度根据预设宽高比自动计算。
  • Height Controls Width:元素高度由父容器决定,宽度根据预设宽高比自动计算。

24、GameObject类及其属性

(1)activeSelf属性

其主要作用是判断游戏对象自身的激活状态

25、与用户交互操作

(1)拖拽Drag操作

需要实现IBeginDragHandler、IDragHandler、IEndDragHandler接口,分别实现OnBeginDrag、OnDrag、OnEndDrag方法。

方法示例:

// 拖拽开始
    public void OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("开始拖拽");
        canvasGroup.alpha = 0.6f;
        canvasGroup.blocksRaycasts = false; // 允许射线检测穿透当前UI
    }

(2)指针操作

处理指针进入/离开 UI元素的事件。

需要实现IPointerEnterHandler、IPointerExitHandler接口,分别实现OnPointerEnter、OnPointerExit方法。

方法示例:

    // 指针进入
    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("指针进入");
        image.color = Color.green;
    }

(3)拖拽操作与指针操作的区别

1)概念

【拖拽】依赖指针的按下状态,且需要移动指针才能触发,纯按下不移动不会触发拖拽事件。

示例:按住鼠标左键移动UI元素。

【指针】仅依赖指针的位置,与是否按下无关。即使不按下鼠标/触摸,只要光标移动到UI元素上 ,就会触发OnPointerEnter事件。

示例:鼠标悬停在按钮上时改变颜色(无需点击)。

2)应用场景

拖拽:

  • 拖动 UI 元素(如背包物品、窗口)
  • 排序列表项(如将物品拖入指定位置)
  • 交互游戏机制(如拼图、钓鱼拉拽)

指针:

  • 悬停提示(如鼠标移到按钮上显示文字说明)
  • 视觉反馈(如指针进入时元素高亮 / 放大)
  • 触摸区域检测(如移动端 UI 的手指悬停逻辑)

26、生命周期函数

(1)Awake/OnEnable/Start的区别

如果3个都会执行,那么执行顺序是:Awake -> OnEnable -> Start。

3个函数的特点如下

【Awake】

  • 最早执行:适合用于脚本初始化,例如获取组件引用(GetComponent)、设置默认值等
  • 不受激活状态影响:即使物体当前未激活(SetActive(false)),Awake仍会执行
  • 仅执行一次:物体在场景中存在期间,无论激活/禁用多少次,Awake只在创建时调用一次。

【OnEnable】

  • 依赖激活状态:仅在物体启用时触发,可用于响应激活事件(如从禁用恢复到启用)
  • 可多次调用:每次物体从禁用变为启用时,都会重新调用OnEnable
  • 执行顺序在Awake之后:如果物体首次激活,OnEnable会在Awake之后、Start之前执行

【Start】

  • 依赖激活状态:若物体未激活(SetActive(false)),Start不会执行
  • 延迟执行:在Awake和OnEnable之后调用,适合需要依赖其他脚本初始化完成的场景(例如等待其他物体的Awake执行完毕)
  • 仅执行一次:物体首次激活时调用一次,后续禁用/启用不会再次触发。

几种场景下的调用情况

1)物体创建(未激活)

调用Awake(无论是否激活)

不调用OnEnable和Start(因为 未激活)

2)物体首次激活(SetActive(true))

调用OnEnable(首次激活时触发)

调用Start(仅首次激活时触发)

3)物体禁用后再次激活

调用OnEnable(每次激活时触发)

不调用Start(仅首次激活时执行一次)

27、Event事件

(1)OnDisable未取消订阅事件的影响

首先,会导致内存泄漏。若订阅事件的对象被销毁,而事件仍保留对该对象方法的引用,这个对象就无法被垃圾回收。长此以往,内存占用会不断增加。

其次,会造成空引用异常。当订阅事件的对象已被销毁,但事件触发时,仍会尝试调用该对象的方法,这就会引发NullReferenceException,导致游戏崩溃。

然后,会造成方法重复调用。若对象反复启用和禁用,每次启用时都会进行事件订阅。要是没有在禁用时取消订阅,同一方法就会被多次订阅,事件触发时该方法会被多次调用。

28、纹理Texture2D类

(1)作用

在运行时对2D纹理进行创建、修改以及读取操作。

(2)GetPixels()方法

获取纹理中的像素信息。

private Texture2D farmerBaseTexture;
Color[] farmerBasePixels = farmerBaseTexture.GetPixels();

GetPixels()方法返回一个Color数组,包含纹理中每个像素的颜色信息。数组长度等于纹理宽度*高度,像素按行优先顺序排列(从左到右,从上到下)。

(3)SetPixels()方法

farmerBaseCustomised.SetPixels(farmerBasePixels);
farmerBaseCustomised.Apply();
  • farmerBaseCustomised 是目标纹理(例如可自定义的角色皮肤)。
  • SetPixels() 方法将源纹理的像素数据复制到目标纹理中。
  • Apply() 方法提交修改,使更改在 GPU 渲染时生效。

(4)filterMode属性

使用方法:对纹理在缩放时的采样方式进行控制。

  • Point(最近邻):纹理在缩放过程中,会保留清晰的像素边缘,比较适合像素艺术风格的游戏
  • Bilinear(双线性):通过对相邻像素进行平均处理,使纹理边缘看起来更加平滑
  • Trilinear(三线性):和双线性类似,但在处理Mipmap层级之间的过渡时效果更好

29、RigidBody组件 

作用:模拟重力、碰撞、力的作用等物理效果。

(1)Body Type选项

Dynamic:完全受物理引擎控制,受重力、力和碰撞影响。可与其他物体发生物理交互。一般用于角色、敌人、可移动的道具。

Kinematic:不受重力和力影响,位置由Transform或脚本控制(rigidbody2D.MovePosition执行移动)。仍可参与碰撞检测,但不会被物理力推动。一般用于平台(如移动的传送带)、NPC路径移动

Static:完全静止,不受物理影响。仅作为碰撞体存在,不会移动或旋转。一般用于地形、墙壁、固定障碍物。

30、Mathf方法

(1)Approximately()方法

Mathf.Approximately(float a, float b)

作用: 用于比较两个浮点数是否“近似相等”。

由于浮点数在计算机中的精度问题(如 0.1f + 0.2f != 0.3f),直接使用 == 比较两个浮点数可能会导致误差。Mathf.Approximately 提供了一种更安全的方式来判断两个浮点数是否足够接近,可以认为是“相等”。

(2)MoveTowards()方法

Mathf.MoveTowards(float current, float target, float maxDelta)

作用: 将一个值从当前值向目标值移动,但不会超过指定的最大步长。 这个方法常用于平滑地改变某个值(比如位置、角度、速度等),避免一次性跳变到目标值。

参数:

current: 当前值。

target: 目标值。

maxDelta: 每次调用允许移动的最大步长(正值)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值