<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>384400&apos;s Blog - 分享网络技术、UE开发、Unity 开发、AI 技术应用</title><description>分享网络技术、UE开发、Unity 开发、AI 技术应用</description><link>https://gzhblog.cn/</link><language>zh_CN</language><item><title>UE5 Gameplay Ability System (GAS) 完全指南</title><link>https://gzhblog.cn/posts/2026-03/2026-03-30-ue5-gas-system/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/2026-03-30-ue5-gas-system/</guid><description>深入解析Unreal Engines 5的Gameplay Ability System，从核心概念到实战应用，帮你快速掌握技能系统开发</description><pubDate>Mon, 30 Mar 2026 17:59:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Gameplay Ability System（GAS）是Epic官方推出的强大技能系统框架，被《堡垒之夜》等顶级游戏采用。它提供了一套完整的技能、buff、冷却管理的解决方案。&lt;/p&gt;
&lt;p&gt;本文带你从零理解GAS的设计思想，并手把手实现一个简单的技能系统。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;什么是GAS？&lt;/h2&gt;
&lt;p&gt;GAS是Unreal Engine的&lt;strong&gt;核心技能/属性系统框架&lt;/strong&gt;，核心特点是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ASC架构&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ability System Component驱动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;属性委托&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GameplayAttribute系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;技能标签&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tag驱动的权限控制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;预测系统&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;客户端预测支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;技能委托&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;完整的生命周期事件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;简单来说：GAS让你能优雅地实现&lt;strong&gt;技能、buff、属性、冷却&lt;/strong&gt;一套完整的战斗系统。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;GAS核心概念&lt;/h2&gt;
&lt;h3&gt;1. ASC（Ability System Component）&lt;/h3&gt;
&lt;p&gt;挂在Actor上的核心组件，负责管理所有技能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPROPERTY()
UAbilitySystemComponent* AbilitySystemComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. AttributeSet（属性集）&lt;/h3&gt;
&lt;p&gt;存储角色的数值属性（生命值、魔法值、攻击力等）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UMyAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:
    UPROPERTY()
    float Health = 100.f;

    UPROPERTY()
    float MaxHealth = 100.f;

    UPROPERTY()
    float Mana = 50.f;

    UPROPERTY()
    float MaxMana = 50.f;

    UPROPERTY()
    float AttackPower = 10.f;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. GameplayAbility（技能）&lt;/h3&gt;
&lt;p&gt;具体的技能逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UMyGameplayAbility : public UGameplayAbility
{
    GENERATED_BODY()

public:
    // 技能激活时调用
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, 
                                FGameplayAbilityActorInfo* ActorInfo,
                                const FGameplayAbilityActivationInfo&amp;amp; ActivationInfo,
                                FOnGameplayAbilityEnded::FDelegate* OnEnded) override;

    // 技能结束时调用
    virtual void EndAbility(const FGameplayAbilitySpecHandle Handle,
                           FGameplayAbilityActorInfo* ActorInfo,
                           FGameplayAbilityActivationInfo ActivationInfo,
                           bool bReplicateEndAbility,
                           bool bWasCancelled) override;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. GameplayEffect（效果）&lt;/h3&gt;
&lt;p&gt;用于修改属性（buff/debuff）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 瞬间生效的效果（回血、掉血）
TSubclassOf&amp;lt;UGameplayEffect&amp;gt; InstantEffect;

// 持续效果（持续回血、dot）
TSubclassOf&amp;lt;UGameplayEffect&amp;gt; DurationEffect;

// 无限持续效果（buff）
TSubclassOf&amp;lt;UGameplayEffect&amp;gt; InfiniteEffect;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. GameplayTags（标签）&lt;/h3&gt;
&lt;p&gt;用于权限控制和技能识别：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在ProjectSettings中定义Tag
// Ability.Book.Fire
// Ability.Book.Ice
// Buff.Shield
// Debuff.Slow
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;项目配置&lt;/h2&gt;
&lt;h3&gt;启用GAS插件&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;Edit &amp;gt; Plugins&lt;/code&gt; 中启用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GameplayAbilities&lt;/strong&gt; — 核心GAS插件&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;创建AttributeSet&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// MyAttributeSet.h
#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;AttributeSet.h&quot;
#include &quot;MyAttributeSet.generated.h&quot;

UCLASS()
class UMyAttributeSet : public UAttributeSet
{
    GENERATED_BODY()

public:
    // 生命值
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    FGameplayAttributeData Health;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    FGameplayAttributeData MaxHealth;

    // 魔法值
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    FGameplayAttributeData Mana;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    FGameplayAttributeData MaxMana;

    // 攻击力
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    FGameplayAttributeData AttackPower;

    // 回调：当属性被修改时触发
    virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData&amp;amp; Data) override;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// MyAttributeSet.cpp
#include &quot;MyAttributeSet.h&quot;
#include &quot;GameplayEffect.h&quot;
#include &quot;GameplayEffectExtension.h&quot;

void UMyAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData&amp;amp; Data)
{
    if (Data.EvaluatedData.Attribute == GetHealthAttribute())
    {
        SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
    }
    else if (Data.EvaluatedData.Attribute == GetManaAttribute())
    {
        SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绑定ASC到Character&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// MyCharacter.h
UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;Abilities&quot;)
    TObjectPtr&amp;lt;UAbilitySystemComponent&amp;gt; AbilitySystemComponent;

    UPROPERTY()
    TObjectPtr&amp;lt;UMyAttributeSet&amp;gt; AttributeSet;

    virtual void PossessedBy(AController* NewController) override;
    virtual void OnRep_PlayerState() override;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// MyCharacter.cpp
#include &quot;MyCharacter.h&quot;
#include &quot;MyAttributeSet.h&quot;

void AMyCharacter::PossessedBy(AController* NewController)
{
    Super::PossessedBy(NewController);

    if (AbilitySystemComponent)
    {
        AbilitySystemComponent-&amp;gt;InitAbilityActorInfo(this, this);
        
        // 给ASC注册AttributeSet
        AbilitySystemComponent-&amp;gt;AddAttributeSetSubobject(AttributeSet);
        
        // 赋予初始技能
        GiveDefaultAbilities();
    }
}

void AMyCharacter::OnRep_PlayerState()
{
    Super::OnRep_PlayerState();

    if (AbilitySystemComponent)
    {
        AbilitySystemComponent-&amp;gt;InitAbilityActorInfo(this, this);
    }
}

void AMyCharacter::GiveDefaultAbilities()
{
    // 在这里赋予角色的默认技能
    // 使用GrantAbility或通过DataAsset配置
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;实战：创建Fireball技能&lt;/h2&gt;
&lt;h3&gt;步骤1：创建技能蓝图&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建&lt;code&gt;GameplayAbility&lt;/code&gt;子类蓝图 &lt;code&gt;BP_FireballAbility&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在蓝图编辑器中设置：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ability Tags&lt;/strong&gt;：添加 &lt;code&gt;Damage.Fire&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cooldown&lt;/strong&gt;：设置冷却时间&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;：设置消耗（魔法值）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;步骤2：实现技能逻辑&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// FireballAbility.h
UCLASS()
class UFireballAbility : public UGameplayAbility
{
    GENERATED_BODY()

public:
    // 发射火球
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                FGameplayAbilityActorInfo* ActorInfo,
                                const FGameplayAbilityActivationInfo&amp;amp; ActivationInfo,
                                FOnGameplayAbilityEnded::FDelegate* OnEnded) override;

    // 激活技能的输入事件
    UFUNCTION(BlueprintCallable, Category = &quot;Ability&quot;)
    void InputPressed(const FGameplayAbilitySpecHandle Handle,
                     const FGameplayAbilityActorInfo* ActorInfo,
                     const FGameplayAbilityActivationInfo&amp;amp; ActivationInfo);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// FireballAbility.cpp
#include &quot;FireballAbility.h&quot;
#include &quot;Abilities/GameplayAbilityTargetActor_SingleLineTrace.h&quot;
#include &quot;Abilities/GameplayAbilityTypes.h&quot;

void UFireballAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
                                       FGameplayAbilityActorInfo* ActorInfo,
                                       const FGameplayAbilityActivationInfo&amp;amp; ActivationInfo,
                                       FOnGameplayAbilityEnded::FDelegate* OnEnded)
{
    // 检查技能是否有效
    if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
    {
        EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
        return;
    }

    // 获取技能所有者
    AActor* Owner = ActorInfo-&amp;gt;OwnerActor.Get();

    // 生成火球逻辑...
    // 这里可以Spawn一个Actor或播放特效

    UE_LOG(LogTemp, Warning, TEXT(&quot;Fireball Activated!&quot;));

    // 结束技能
    EndAbility(Handle, ActorInfo, ActivationInfo, false, false);
}

void UFireballAbility::InputPressed(const FGameplayAbilitySpecHandle Handle,
                                    const FGameplayAbilityActorInfo* ActorInfo,
                                    const FGameplayAbilityActivationInfo&amp;amp; ActivationInfo)
{
    TryActivateAbility(Handle);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤3：赋予技能&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在Character的GiveDefaultAbilities中
void AMyCharacter::GiveDefaultAbilities()
{
    if (!AbilitySystemComponent) return;

    // 创建技能Spec
    FGameplayAbilitySpec Spec;
    Spec.Ability = NewObject&amp;lt;UFireballAbility&amp;gt;();
    Spec.Level = 1;

    // 赋予技能
    AbilitySystemComponent-&amp;gt;GiveAbility(Spec);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;冷却系统&lt;/h2&gt;
&lt;h3&gt;设置Cooldown GameplayEffect&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 定义冷却GE
TSubclassOf&amp;lt;UGameplayEffect&amp;gt; CooldownGE = StaticLoadClass(
    UGameplayEffect::StaticClass(),
    nullptr,
    TEXT(&quot;GameplayEffect&apos;/Game/Effects/GE_Cooldown.GE_Cooldown_C&apos;&quot;)
);

// 检查是否在冷却中
bool AMyCharacter::IsAbilityOnCooldown(FGameplayTag AbilityTag)
{
    FGameplayTagContainer OwnedTags;
    AbilitySystemComponent-&amp;gt;GetOwnedGameplayTags(OwnedTags);
    return OwnedTags.HasTag(AbilityTag);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: GAS和普通函数调用的区别？&lt;/h3&gt;
&lt;p&gt;A: GAS提供了完整的生命周期管理、预测系统、网络同步、冷却/消耗验证。普通函数调用没有这些。&lt;/p&gt;
&lt;h3&gt;Q: AttributeSet为什么要用FGameplayAttributeData而不是float？&lt;/h3&gt;
&lt;p&gt;A: FGameplayAttributeData支持元属性（MaxHealth）和Clamp设置，能正确处理属性比例修改。&lt;/p&gt;
&lt;h3&gt;Q: 如何调试GAS？&lt;/h3&gt;
&lt;p&gt;A: 使用&lt;code&gt;ASC-&amp;gt;Debug&lt;/code&gt;命令可以在屏幕上显示所有技能和属性的详细信息。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;GAS是UE5最强大的技能系统框架，核心组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ASC&lt;/strong&gt; — 技能系统核心组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AttributeSet&lt;/strong&gt; — 管理角色属性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameplayAbility&lt;/strong&gt; — 定义技能逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameplayEffect&lt;/strong&gt; — 实现buff/debuff&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GameplayTags&lt;/strong&gt; — 权限控制和识别&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;建议从官方示例项目入手，熟悉后再自定义实现复杂系统。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/gameplay-ability-system&quot;&gt;Epic官方GAS文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/tree/release/Engine/Plugins/Runtime/GameplayAbilities&quot;&gt;GAS插件源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tranek/GASDocumentation&quot;&gt;GAS Community Wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>UE5 Mass集群系统详解 — 大规模人群模拟解决方案</title><link>https://gzhblog.cn/posts/2026-03/2026-03-30-ue5-mass-cluster-system/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/2026-03-30-ue5-mass-cluster-system/</guid><description>深入解析Unreal Engine 5的Mass集群系统，从架构设计到实战应用，帮你快速掌握大规模人群模拟技术</description><pubDate>Mon, 30 Mar 2026 12:31:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在游戏开发中，渲染大量独立个体（如城市人群、动物群体、昆虫群落）一直是性能优化的难题。传统方式下，每个AI角色都是独立的Actor，大规模场景下Draw Call和物理计算会成为灾难。&lt;/p&gt;
&lt;p&gt;UE5给出的答案是——&lt;strong&gt;Mass&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Mass是Epic官方推出的&lt;strong&gt;Entity Component System (ECS)&lt;/strong&gt; 架构的人群模拟框架，专为大规模集群设计。本文带你从零理解Mass的设计思想，并手把手实现一个简单的集群效果。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;什么是Mass？&lt;/h2&gt;
&lt;p&gt;Mass是UE5内置的&lt;strong&gt;大规模实体模拟框架&lt;/strong&gt;，核心特点是：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECS架构&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;数据与逻辑分离，缓存友好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPU Driven&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;支持GPU实例化渲染&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;可扩展&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自定义Fragment处理复杂行为&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;高性能&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;单帧可处理数万个实体&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;简单来说：Mass让你能渲染&lt;strong&gt;数万个同时运行的AI单位&lt;/strong&gt;，而且帧率稳定。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Mass核心概念&lt;/h2&gt;
&lt;h3&gt;1. Entity（实体）&lt;/h3&gt;
&lt;p&gt;一个唯一的ID，标识一个集群单位。不存储任何数据，只是一个Handle。&lt;/p&gt;
&lt;h3&gt;2. Fragment（片段）&lt;/h3&gt;
&lt;p&gt;存储实体的数据，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FTransformFragment&lt;/code&gt; — 位置、旋转、缩放&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FStaticMeshHashMeshVISet&lt;/code&gt; — 渲染数据&lt;/li&gt;
&lt;li&gt;自定义Fragment存储AI状态&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Trait（特征）&lt;/h3&gt;
&lt;p&gt;给Entity附加的行为逻辑，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移动行为&lt;/li&gt;
&lt;li&gt;状态机&lt;/li&gt;
&lt;li&gt;渲染表现&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Processing（处理）&lt;/h3&gt;
&lt;p&gt;每个系统（System）处理特定的Fragment数据，按阶段顺序执行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;项目配置&lt;/h2&gt;
&lt;h3&gt;启用Mass插件&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;Edit &amp;gt; Plugins&lt;/code&gt; 中启用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MassEntity&lt;/strong&gt; — 核心框架&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MassSmartObject&lt;/strong&gt; — 智能物体交互&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MassCrowd&lt;/strong&gt; — 人群可视化（可选）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重启编辑器后生效。&lt;/p&gt;
&lt;h3&gt;创建Mass处理器的模块&lt;/h3&gt;
&lt;p&gt;Mass处理器需要在C++模块中注册。如果你的项目没有Gameplay框架，需要先创建：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// .uproject 添加依赖
&quot;MassEntity&quot;: { &quot;Enabled&quot;: true },
&quot;MassCrowd&quot;: { &quot;Enabled&quot;: true }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;实战：创建一个简单的集群效果&lt;/h2&gt;
&lt;h3&gt;步骤1：定义Fragment&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// MyMassFragments.h
#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;MassEntityTypes.h&quot;
#include &quot;MyMassFragments.generated.h&quot;

// 位置和移动数据
USTRUCT()
struct FMyMovementFragment : public FMassFragment
{
    GENERATED_BODY()

    FVector Velocity = FVector::ZeroVector;
    float Speed = 100.f;
};

// 外观数据
USTRUCT()
struct FMyVisualFragment : public FMassFragment
{
    GENERATED_BODY()

    UStaticMesh* Mesh = nullptr;
    FLinearColor Color = FLinearColor::White;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤2：创建System&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// MyMassSystem.h
#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;MassEntityTypes.h&quot;
#include &quot;MassProcessor.h&quot;
#include &quot;MyMassSystem.generated.h&quot;

// 移动处理系统
UCLASS()
class UMyMovementSystem : public UMassProcessor
{
    GENERATED_BODY()

public:
    UMyMovementSystem();

protected:
    virtual void ConfigureQueries() override;
    virtual void Execute(UMassEntitySubsystem&amp;amp; EntitySubsystem, FMassExecutionContext&amp;amp; Context) override;

    FMassEntityQuery EntityQuery;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// MyMassSystem.cpp
#include &quot;MyMassSystem.h&quot;
#include &quot;MyMassFragments.h&quot;

UMyMovementSystem::UMyMovementSystem()
{
    ExecutionOrder.ExecuteInGroup = EMassFragmentAccessAlgorithm::Linear;
    EntityQuery.AddRequirement&amp;lt;FMyMovementFragment&amp;gt;(EMassFragmentAccess::ReadWrite);
}

void UMyMovementSystem::ConfigureQueries()
{
    EntityQuery.AddTagRequirement&amp;lt;FMyMovementFragment&amp;gt;(EMassFragmentPresence::All);
}

void UMyMovementSystem::Execute(UMassEntitySubsystem&amp;amp; EntitySubsystem, FMassExecutionContext&amp;amp; Context)
{
    EntityQuery.ForEachEntityChunk(EntitySubsystem, Context, [this](FMassExecutionContext&amp;amp; Context)
    {
        TArrayView&amp;lt;FMyMovementFragment&amp;gt; MovementList = Context.GetMutableFragments&amp;lt;FMyMovementFragment&amp;gt;();

        for (int32 i = 0; i &amp;lt; MovementList.Num(); ++i)
        {
            FMyMovementFragment&amp;amp; Movement = MovementList[i];
            
            // 简单示例：让实体向前移动
            FVector Direction = FVector(1.f, 0.f, 0.f);
            Movement.Velocity = Direction * Movement.Speed;
        }
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;步骤3：Spawn实体&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在GameInstance或Actor中
void USpawnerComponent::SpawnCluster()
{
    UWorld* World = GetWorld();
    UMassEntitySubsystem* MassSystem = UWorld::GetSubsystem&amp;lt;UMassEntitySubsystem&amp;gt;(World);
    
    const int32 ClusterCount = 1000;
    FMassEntityManager&amp;amp; EntityManager = MassSystem-&amp;gt;GetEntityManager();
    
    for (int32 i = 0; i &amp;lt; ClusterCount; ++i)
    {
        FMassEntityHandle Entity = EntityManager.CreateEntity();
        
        FMyMovementFragment Movement;
        Movement.Speed = 100.f + FMath::Rand() % 50.f;
        
        EntityManager.SetFragmentData(Entity, Movement);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Mass与渲染&lt;/h2&gt;
&lt;h3&gt;使用InstancedStaticMesh&lt;/h3&gt;
&lt;p&gt;最常见的渲染方式是配合 &lt;code&gt;InstancedStaticMeshComponent&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 渲染系统示例
void UMyRenderSystem::Execute(UMassEntitySubsystem&amp;amp; EntitySubsystem, FMassExecutionContext&amp;amp; Context)
{
    TArray&amp;lt;FMatrix&amp;gt; InstanceMatrices;
    // ... 填充矩阵数据
    
    if (InstancedMesh)
    {
        InstancedMesh-&amp;gt;SetStaticMesh(YourMesh);
        InstancedMesh-&amp;gt;BuildRuntimeMeshIfNeeded();
        
        for (int32 i = 0; i &amp;lt; InstanceMatrices.Num(); ++i)
        {
            InstancedMesh-&amp;gt;SetMatrixAtIndex(i, InstanceMatrices[i]);
        }
        InstancedMesh-&amp;gt;MarkRenderStateDirty();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MassCrowd插件&lt;/h3&gt;
&lt;p&gt;如果你只需要简单的人群渲染，可以启用 &lt;code&gt;MassCrowd&lt;/code&gt; 插件，它提供了开箱即用的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基础移动AI&lt;/li&gt;
&lt;li&gt;状态机（行走、站立、奔跑）&lt;/li&gt;
&lt;li&gt;动画绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;集群算法：Reynolds&apos; Boids&lt;/h2&gt;
&lt;p&gt;要让集群行为自然，通常使用&lt;strong&gt;Boids算法&lt;/strong&gt;，三条核心规则：&lt;/p&gt;
&lt;h3&gt;1. 分离（Separation）&lt;/h3&gt;
&lt;p&gt;避免碰撞，保持个体间距：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FVector Separation(const FMyMovementFragment&amp;amp; Self, const TArray&amp;lt;FMyMovementFragment&amp;gt;&amp;amp; Neighbors)
{
    FVector Force = FVector::ZeroVector;
    for (const auto&amp;amp; Other : Neighbors)
    {
        float Distance = FVector::Dist(Self.Position, Other.Position);
        if (Distance &amp;lt; MinSeparationDistance &amp;amp;&amp;amp; Distance &amp;gt; 0)
        {
            FVector Away = (Self.Position - Other.Position).GetSafeNormal();
            Force += Away / Distance;
        }
    }
    return Force;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 对齐（Alignment）&lt;/h3&gt;
&lt;p&gt;与邻居速度方向一致：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FVector Alignment(const FMyMovementFragment&amp;amp; Self, const TArray&amp;lt;FMyMovementFragment&amp;gt;&amp;amp; Neighbors)
{
    FVector AverageVelocity = FVector::ZeroVector;
    int32 Count = 0;
    for (const auto&amp;amp; Other : Neighbors)
    {
        if (FVector::Dist(Self.Position, Other.Position) &amp;lt; NeightborRadius)
        {
            AverageVelocity += Other.Velocity;
            ++Count;
        }
    }
    return Count &amp;gt; 0 ? (AverageVelocity / Count).GetSafeNormal() : FVector::ZeroVector;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 内聚（Cohesion）&lt;/h3&gt;
&lt;p&gt;向邻居中心移动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FVector Cohesion(const FMyMovementFragment&amp;amp; Self, const TArray&amp;lt;FMyMovementFragment&amp;gt;&amp;amp; Neighbors)
{
    FVector Center = FVector::ZeroVector;
    int32 Count = 0;
    for (const auto&amp;amp; Other : Neighbors)
    {
        if (FVector::Dist(Self.Position, Other.Position) &amp;lt; NeightborRadius)
        {
            Center += Other.Position;
            ++Count;
        }
    }
    return Count &amp;gt; 0 ? ((Center / Count) - Self.Position).GetSafeNormal() : FVector::ZeroVector;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;性能优化建议&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优化项&lt;/th&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;空间分区&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;使用Grid或Octree减少邻居搜索范围&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LOD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;远处实体用简化模型或2D sprite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;批处理&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fragment数据布局连续，利于缓存命中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Job System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mass内部已用TaskGraph，可并行处理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPU计算&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;对于简单逻辑，考虑使用GPU Buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: Mass和Niagara有什么区别？&lt;/h3&gt;
&lt;p&gt;A: Niagara是粒子特效系统，适合数千个简单粒子。Mass是通用ECS框架，适合需要独立AI逻辑的实体。&lt;/p&gt;
&lt;h3&gt;Q: 支持动画吗？&lt;/h3&gt;
&lt;p&gt;A: 支持！可以用 &lt;code&gt;MassAnimationInstance&lt;/code&gt; 配合 Animation Blueprint，或者使用 &lt;code&gt;MassCrowd&lt;/code&gt; 的内置动画。&lt;/p&gt;
&lt;h3&gt;Q: 如何调试？&lt;/h3&gt;
&lt;p&gt;A: 启用 &lt;code&gt;MassVisualizer&lt;/code&gt; 插件，可以在Editor中可视化实体位置、速度等信息。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Mass是UE5解决大规模集群问题的利器。核心思想：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ECS架构&lt;/strong&gt; — 数据与逻辑分离，利于并行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragment自定义&lt;/strong&gt; — 灵活扩展，满足各种需求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量处理&lt;/strong&gt; — 利用TaskGraph最大化性能&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;建议从官方 &lt;code&gt;MassCrowd&lt;/code&gt; 插件开始体验，熟悉后再深入自定义System和Fragment。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/mass-overview&quot;&gt;Epic官方Mass文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unrealengine.com/en-US/API/Plugins/MassEntity/&quot;&gt;MassEntity API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/tree/release/Samples/Feature/Mass&quot;&gt;UE5 Mass示例项目&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>GPT-SoVITS 本地语音合成完整教程</title><link>https://gzhblog.cn/posts/2026-03/2026-03-24-gpt-sovits-tts/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/2026-03-24-gpt-sovits-tts/</guid><description>详细记录GPT-SoVITS本地语音合成模型的部署与使用，涵盖环境配置、模型训练、API调用，以及在项目中的集成方法</description><pubDate>Tue, 24 Mar 2026 20:40:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;GPT-SoVITS是一款强大的本地语音合成项目，支持零样本声音克隆和少样本训练。本文记录完整的本地部署流程，帮你快速上手AI语音合成技术。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;环境要求&lt;/h2&gt;
&lt;h3&gt;硬件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;显卡：NVIDIA GPU（建议6GB以上显存）&lt;/li&gt;
&lt;li&gt;内存：16GB RAM&lt;/li&gt;
&lt;li&gt;硬盘：30GB可用空间&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;软件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;CUDA 11.8 或 12.1&lt;/li&gt;
&lt;li&gt;cuDNN&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;安装依赖&lt;/h2&gt;
&lt;h3&gt;创建虚拟环境&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;conda create -n gptsovits python=3.10
conda activate gptsovits
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安装PyTorch&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;克隆项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/RVC-Boss/GPT-SoVITS.git
cd GPT-SoVITS
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安装依赖&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;预训练模型下载&lt;/h2&gt;
&lt;h3&gt;需要下载的模型&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;大小&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-SoVITS模型&lt;/td&gt;
&lt;td&gt;主模型&lt;/td&gt;
&lt;td&gt;~1.5GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HuggingFace模型&lt;/td&gt;
&lt;td&gt;底模&lt;/td&gt;
&lt;td&gt;~4GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;训练语料（可选）&lt;/td&gt;
&lt;td&gt;参考音色&lt;/td&gt;
&lt;td&gt;几百MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;下载地址&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://modelscope.cn/models&quot;&gt;Modelscope下载&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/&quot;&gt;HuggingFace下载&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;模型存放&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;GPT-SoVITS/
├── GPT_SoVITS/
│   └── pretrained_models/
│       ├── GPT-SoVITS/
│       └── huqubert/
└── SoVITS/
    └── pretrained_models/
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;推理使用&lt;/h2&gt;
&lt;h3&gt;启动WebUI&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python webui.py --port 9876 --colab
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;浏览器访问&lt;/h3&gt;
&lt;p&gt;打开 &lt;code&gt;http://localhost:9876&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;推理参数&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;建议值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;参考音频&lt;/td&gt;
&lt;td&gt;克隆音色的源&lt;/td&gt;
&lt;td&gt;5-30秒&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;文本内容&lt;/td&gt;
&lt;td&gt;要合成的文字&lt;/td&gt;
&lt;td&gt;避免超长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;语速&lt;/td&gt;
&lt;td&gt;合成语速&lt;/td&gt;
&lt;td&gt;1.0正常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;音调&lt;/td&gt;
&lt;td&gt;音高调整&lt;/td&gt;
&lt;td&gt;0正常&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;API服务&lt;/h2&gt;
&lt;h3&gt;启动API服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python API.py --port 9880
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTTP调用示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import requests

# 合成语音
url = &quot;http://127.0.0.1:9880/tts&quot;

data = {
    &quot;text&quot;: &quot;你好，欢迎使用GPT-SoVITS语音合成&quot;,
    &quot;text_lang&quot;: &quot;zh&quot;,
    &quot;ref_audio_path&quot;: &quot;path/to/reference.wav&quot;,
    &quot;prompt_lang&quot;: &quot;zh&quot;,
    &quot;prompt_text&quot;: &quot;这是一段参考音频的文字内容&quot;
}

response = requests.post(url, json=data)

# 保存音频
with open(&quot;output.wav&quot;, &quot;wb&quot;) as f:
    f.write(response.content)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;返回格式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;code&quot;: 200,
  &quot;message&quot;: &quot;success&quot;,
  &quot;data&quot;: {
    &quot;audio&quot;: &quot;base64编码的音频数据&quot;,
    &quot;duration&quot;: 3.5
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;训练自定义音色&lt;/h2&gt;
&lt;h3&gt;数据准备&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;收集目标音色的音频（5-30分钟）&lt;/li&gt;
&lt;li&gt;音频格式：16bit WAV，16kHz，单声道&lt;/li&gt;
&lt;li&gt;命名规范：&lt;code&gt;speaker_name_001.wav&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;音频预处理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python 3_applyforcevocab.py \
  --input_path ./raw/ \
  --output_path ./processed/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;启动训练&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 训练SoVITS
python train_sovits.py \
  --data_path ./processed/ \
  --model_path ./output/sovits/

# 训练GPT
python train_gpt.py \
  --data_path ./processed/ \
  --model_path ./output/gpt/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;训练参数&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;建议值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;batch_size&lt;/td&gt;
&lt;td&gt;批量大小&lt;/td&gt;
&lt;td&gt;显存允许尽量大&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;learning_rate&lt;/td&gt;
&lt;td&gt;学习率&lt;/td&gt;
&lt;td&gt;1e-4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;epochs&lt;/td&gt;
&lt;td&gt;训练轮数&lt;/td&gt;
&lt;td&gt;10-20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;与项目集成&lt;/h2&gt;
&lt;h3&gt;Python集成&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import subprocess
import base64
import os

class GPTSoVITSClient:
    def __init__(self, api_url=&quot;http://127.0.0.1:9880&quot;):
        self.api_url = api_url

    def generate(self, text, ref_audio, ref_text):
        import requests
        with open(ref_audio, &quot;rb&quot;) as f:
            ref_audio_b64 = base64.b64encode(f.read()).decode()

        data = {
            &quot;text&quot;: text,
            &quot;text_lang&quot;: &quot;zh&quot;,
            &quot;ref_audio_path&quot;: ref_audio,
            &quot;prompt_lang&quot;: &quot;zh&quot;,
            &quot;prompt_text&quot;: ref_text
        }

        response = requests.post(self.api_url + &quot;/tts&quot;, json=data)
        result = response.json()

        if result[&quot;code&quot;] == 200:
            audio_b64 = result[&quot;data&quot;][&quot;audio&quot;]
            audio_data = base64.b64decode(audio_b64)

            output_path = &quot;temp_output.wav&quot;
            with open(output_path, &quot;wb&quot;) as f:
                f.write(audio_data)
            return output_path
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;游戏引擎集成&lt;/h3&gt;
&lt;p&gt;在UE5中使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在OpenClaw或其他项目中调用API
void USoVITSManager::GenerateVoice(const FString&amp;amp; Text, const FString&amp;amp; RefAudio)
{
    TSharedRef&amp;lt;IHttpRequest&amp;gt; Request = FHttpModule::Get().CreateRequest();
    Request-&amp;gt;SetURL(&quot;http://127.0.0.1:9880/tts&quot;);
    Request-&amp;gt;SetVerb(&quot;POST&quot;);
    Request-&amp;gt;SetHeader(&quot;Content-Type&quot;, &quot;application/json&quot;);

    TArray&amp;lt;TSharedPtr&amp;lt;FJsonValue&amp;gt;&amp;gt; JsonArray;
    // ... 构建JSON
    Request-&amp;gt;SetContentAsString(JsonString);

    Request-&amp;gt;OnProcessRequestComplete().BindUObject(this, &amp;amp;USoVITSManager::OnResponse);
    Request-&amp;gt;ProcessRequest();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: 显存不足？&lt;/h3&gt;
&lt;p&gt;A: 降低batch_size，或使用量化模型。&lt;/p&gt;
&lt;h3&gt;Q: 合成声音不自然？&lt;/h3&gt;
&lt;p&gt;A: 提供更清晰、时长适中的参考音频（10-20秒为佳）。&lt;/p&gt;
&lt;h3&gt;Q: 中文发音错误？&lt;/h3&gt;
&lt;p&gt;A: 使用拼音或带声调的拼音输入。&lt;/p&gt;
&lt;h3&gt;Q: 如何提升推理速度？&lt;/h3&gt;
&lt;p&gt;A: 使用TensorRT加速，或降低推理精度。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;GPT-SoVITS核心要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;本地部署&lt;/strong&gt; — 无需云服务，保护隐私&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;零样本克隆&lt;/strong&gt; — 5秒音频即可克隆&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;少样本训练&lt;/strong&gt; — 5-30分钟数据定制音色&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API服务&lt;/strong&gt; — 方便项目集成&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;游戏应用&lt;/strong&gt; — NPC对话、语音播报&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本地语音合成让AI应用更灵活可控。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/RVC-Boss/GPT-SoVITS&quot;&gt;GPT-SoVITS项目地址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://modelscope.cn/models&quot;&gt;Modelscope模型下载&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/&quot;&gt;官方训练教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你掌握了GPT-SoVITS本地部署与使用！&lt;/p&gt;
</content:encoded></item><item><title>OpenClaw 部署完整指南</title><link>https://gzhblog.cn/posts/2026-03/2026-03-23-openclaw-deployment/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/2026-03-23-openclaw-deployment/</guid><description>详细记录OpenClaw AI助手的部署流程，涵盖环境配置、安装步骤、插件安装、Telegram机器人配置等核心内容</description><pubDate>Mon, 23 Mar 2026 20:34:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;OpenClaw是一个强大的AI助手框架，支持多种聊天平台（Telegram、Discord、Signal等）。本文记录完整的OpenClaw部署流程，帮助你快速搭建自己的AI助手。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;环境要求&lt;/h2&gt;
&lt;h3&gt;硬件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;内存：4GB RAM以上&lt;/li&gt;
&lt;li&gt;硬盘：10GB可用空间&lt;/li&gt;
&lt;li&gt;网络：稳定的互联网连接&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;软件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 18+&lt;/li&gt;
&lt;li&gt;npm 或 pnpm&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;安装Node.js&lt;/h2&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;下载LTS版本（18.x或更高）&lt;/li&gt;
&lt;li&gt;运行安装程序&lt;/li&gt;
&lt;li&gt;验证安装：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;node --version
npm --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Linux (Ubuntu/Debian)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;安装OpenClaw&lt;/h2&gt;
&lt;h3&gt;全局安装CLI&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm install -g openclaw
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建新项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openclaw create my-bot
cd my-bot
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;目录结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;my-bot/
├── config.yaml          # 主配置文件
├── skills/             # 技能文件夹
├── memory/             # 记忆存储
├── workspace/          # 工作区
└── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;配置文件详解&lt;/h2&gt;
&lt;h3&gt;config.yaml&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 机器人名称
name: &quot;My Bot&quot;

# 运行端口
port: 18789

# Telegram配置
telegram:
  enabled: true
  botToken: &quot;你的BotToken&quot;
  allowedUsers:
    - 用户ID1
    - 用户ID2

# AI模型配置
models:
  default: &quot;minimax/MiniMax-M2.7&quot;
  providers:
    minimax:
      apiKey: &quot;你的API密钥&quot;
      baseUrl: &quot;https://api.minimax.chat&quot;

# 日志配置
logging:
  level: &quot;info&quot;
  file: &quot;openclaw.log&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取Telegram Bot Token&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在Telegram搜索 @BotFather&lt;/li&gt;
&lt;li&gt;发送 &lt;code&gt;/newbot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;输入机器人名称&lt;/li&gt;
&lt;li&gt;获取Token&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;获取用户ID&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在Telegram搜索 @userinfobot&lt;/li&gt;
&lt;li&gt;发送任意消息&lt;/li&gt;
&lt;li&gt;获取数字ID&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;启动机器人&lt;/h2&gt;
&lt;h3&gt;开发模式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;生产模式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm run build
npm start
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;系统服务（Linux）&lt;/h3&gt;
&lt;p&gt;创建systemd服务文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=OpenClaw Bot
After=network.target

[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/my-bot
ExecStart=/usr/bin/npm start
Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启用服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable openclaw
sudo systemctl start openclaw
sudo systemctl status openclaw
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;技能系统&lt;/h2&gt;
&lt;h3&gt;安装技能&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;openclaw skill install &amp;lt;skill-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常用技能&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;技能&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;clawhub&lt;/td&gt;
&lt;td&gt;技能市场&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;github&lt;/td&gt;
&lt;td&gt;GitHub集成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weather&lt;/td&gt;
&lt;td&gt;天气查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;summarize&lt;/td&gt;
&lt;td&gt;内容总结&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;常用命令&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/help&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;显示帮助信息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;显示状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/skills&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;列出已安装技能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/reload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;重载配置&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Docker部署&lt;/h2&gt;
&lt;h3&gt;Dockerfile&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 18789

CMD [&quot;npm&quot;, &quot;start&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;docker-compose.yml&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.8&apos;
services:
  openclaw:
    build: .
    container_name: openclaw-bot
    restart: unless-stopped
    ports:
      - &quot;18789:18789&quot;
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - ./config.yaml:/app/config.yaml:ro
      - ./data:/app/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Nginx反向代理&lt;/h2&gt;
&lt;h3&gt;配置SSL证书&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:18789;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &apos;upgrade&apos;;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启Nginx：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: 启动失败，提示端口被占用？&lt;/h3&gt;
&lt;p&gt;A: 检查端口是否被其他程序占用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lsof -i :18789
netstat -tlnp | grep 18789
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q: Telegram无法连接？&lt;/h3&gt;
&lt;p&gt;A: 检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bot Token是否正确&lt;/li&gt;
&lt;li&gt;网络能否访问Telegram API&lt;/li&gt;
&lt;li&gt;allowedUsers是否包含你的ID&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Q: AI模型调用失败？&lt;/h3&gt;
&lt;p&gt;A: 检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;API Key是否有效&lt;/li&gt;
&lt;li&gt;API额度是否充足&lt;/li&gt;
&lt;li&gt;网络是否能访问模型API&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Q: 如何更新OpenClaw？&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm update -g openclaw
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;OpenClaw部署要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境准备&lt;/strong&gt; — Node.js 18+&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安装启动&lt;/strong&gt; — npm install -g openclaw&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置&lt;/strong&gt; — config.yaml填写正确信息&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启动&lt;/strong&gt; — npm start 或 Docker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代理&lt;/strong&gt; — Nginx配置SSL&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;祝你部署成功！&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://openclaw.ai&quot;&gt;OpenClaw官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.openclaw.ai&quot;&gt;OpenClaw文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://clawhub.ai&quot;&gt;ClawHub技能市场&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 你的OpenClaw机器人已准备就绪！&lt;/p&gt;
</content:encoded></item><item><title>UE5 StateTree状态树完全指南</title><link>https://gzhblog.cn/posts/2026-03/2026-03-15-ue5-state-tree/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/2026-03-15-ue5-state-tree/</guid><description>深入解析Unreal Engine 5的StateTree状态树系统，涵盖状态机基础、节点类型、实战用法，帮你快速掌握AI行为逻辑开发</description><pubDate>Sun, 15 Mar 2026 20:24:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;StateTree是UE5引入的全新AI行为逻辑框架，旨在替代传统的Behavior Tree。它结合了有限状态机（FSM）和行为树的优点，提供更直观、更模块化的AI逻辑设计方式。&lt;/p&gt;
&lt;p&gt;本文系统讲解StateTree的用法，帮你快速上手这一强大的AI工具。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;为什么用StateTree？&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;Behavior Tree&lt;/th&gt;
&lt;th&gt;StateTree&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;状态切换&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;需要自定义逻辑&lt;/td&gt;
&lt;td&gt;内置状态转换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;模块化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;有限&lt;/td&gt;
&lt;td&gt;高度模块化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;并行任务&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;支持但复杂&lt;/td&gt;
&lt;td&gt;原生支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;学习曲线&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;较陡&lt;/td&gt;
&lt;td&gt;平缓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;h3&gt;State（状态）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// State是StateTree的基本单元
// 每个State包含：Enter、Tick、Exit三个阶段
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Transition（转换）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 状态之间的转换条件
// 支持事件触发和条件判断
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Event（事件）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 事件驱动状态切换
// 可在代码或蓝图中发送事件
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;节点类型&lt;/h2&gt;
&lt;h3&gt;1. State Nodes（状态节点）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;节点&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;普通状态，执行子节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linked State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;链接到另一个StateTree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operator State&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;并行执行多个任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;2. Task Nodes（任务节点）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 执行具体行为
// 如：移动、攻击、等待
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Condition Nodes（条件节点）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 判断是否满足某个条件
// 用于Transition中
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Event Nodes（事件节点）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 等待特定事件触发
// 收到事件后切换状态
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;创建StateTree&lt;/h2&gt;
&lt;h3&gt;1. 创建Asset&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;内容浏览器右键&lt;/li&gt;
&lt;li&gt;AI → StateTree&lt;/li&gt;
&lt;li&gt;命名如 &lt;code&gt;STT_EnemyAI&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2. 基本结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Root
├── Idle State
│   └── Tasks: Wait, LookAround
├── Chase State
│   └── Tasks: MoveToTarget, FollowPath
└── Attack State
    └── Tasks: FaceTarget, ExecuteAttack
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 配置Transition&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Idle → Chase
├── Condition: TargetDetected == true
└── Event: OnTargetFound

Chase → Attack
├── Condition: DistanceToTarget &amp;lt; MeleeRange
└── Event: OnInRange

Attack → Chase
├── Condition: DistanceToTarget &amp;gt; MeleeRange
└── Event: OnOutOfRange
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;代码集成&lt;/h2&gt;
&lt;h3&gt;发送事件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 从代码发送StateTree事件
UStateTreeComponent* StateTree = Player-&amp;gt;FindComponentByClass&amp;lt;UStateTreeComponent&amp;gt;();
if (StateTree)
{
    FStateTreeEvent Event;
    Event.Tag = FGameplayTag::RequestGameplayTag(&quot;Event.EnemyAlert&quot;);
    Event.Payload.SetValue&amp;lt;FVector&amp;gt;(TargetLocation);
    StateTree-&amp;gt;SendEvent(FStateTreeEvent(&quot;Alert&quot;, Event));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绑定数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// StateTree需要的数据定义在Schema中
USTRUCT()
struct FEnemyStateTreeContext : public FStateTreeOwnerContext
{
    GENERATED_BODY()

    UPROPERTY()
    TObjectPtr&amp;lt;AAIController&amp;gt; Controller;

    UPROPERTY()
    TObjectPtr&amp;lt;UAIPerceptionComponent&amp;gt; Perception;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;实战：敌人AI&lt;/h2&gt;
&lt;h3&gt;需求分析&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Idle（待机）
  ↓ 发现目标
Chase（追击）
  ↓ 进入攻击范围
Attack（攻击）
  ↓ 目标逃离
Chase（追击）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建Tasks&lt;/h3&gt;
&lt;h4&gt;MoveToTarget Task&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;USTRUCT()
struct FMoveToTargetTask : public FStateTreeTask
{
    GENERATED_BODY()

    virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext&amp;amp; Context, const FStateTreeTransition&amp;amp; Transition) const override
    {
        // 获取Context中的数据
        FEnemyStateTreeContext EnemyContext;
        if (!Context.GetContext(EnemyContext)) return EStateTreeRunStatus::Failed;

        AAIController* Controller = EnemyContext.Controller;
        if (!Controller) return EStateTreeRunStatus::Failed;

        // 执行移动
        Controller-&amp;gt;MoveToActor(EnemyContext.TargetActor);
        return EStateTreeRunStatus::Running;
    }

    virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext&amp;amp; Context, const float DeltaTime) const override
    {
        // 检查是否到达
        AAIController* Controller = EnemyContext.Controller;
        if (Controller-&amp;gt;GetMoveStatus() == EPathFollowingStatus::Type::AtGoal)
        {
            return EStateTreeRunStatus::Succeeded;
        }
        return EStateTreeRunStatus::Running;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Attack Task&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;USTRUCT()
struct FAttackTask : public FStateTreeTask
{
    GENERATED_BODY()

    float AttackCooldown = 1.0f;
    float LastAttackTime = 0.f;

    virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext&amp;amp; Context, const FStateTreeTransition&amp;amp; Transition) const override
    {
        FEnemyStateTreeContext EnemyContext;
        if (!Context.GetContext(EnemyContext)) return EStateTreeRunStatus::Failed;

        // 执行攻击
        if (GetWorld()-&amp;gt;TimeSeconds - LastAttackTime &amp;gt; AttackCooldown)
        {
            Attack(EnemyContext.Controller);
            LastAttackTime = GetWorld()-&amp;gt;TimeSeconds;
        }

        return EStateTreeRunStatus::Succeeded;
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;调试StateTree&lt;/h2&gt;
&lt;h3&gt;编辑器内调试&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在StateTree编辑器中打开Debug面板&lt;/li&gt;
&lt;li&gt;Play In Editor&lt;/li&gt;
&lt;li&gt;观察状态高亮和变量值&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;日志输出&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UE_VLOG_UELOG(StateTreeComponent-&amp;gt;GetOwner(), LogStateTree, Log, TEXT(&quot;Entering State: %s&quot;), *StateName);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;可视化工具&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StateTree Debugger&lt;/strong&gt; — 查看当前状态和转换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI Perception Visualizer&lt;/strong&gt; — 查看AI感知范围&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior Tree Visualizer&lt;/strong&gt; — 配合使用&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: StateTree和Behavior Tree哪个好？&lt;/h3&gt;
&lt;p&gt;A: 新项目推荐StateTree，更易维护。Behavior Tree适合复杂行为树已有项目。&lt;/p&gt;
&lt;h3&gt;Q: 如何处理并发状态？&lt;/h3&gt;
&lt;p&gt;A: 使用 &lt;strong&gt;Operator State&lt;/strong&gt; 节点，可以同时执行多个任务。&lt;/p&gt;
&lt;h3&gt;Q: 可以和Behavior Tree混用吗？&lt;/h3&gt;
&lt;p&gt;A: 可以。通过 &lt;strong&gt;Linked State&lt;/strong&gt; 节点引用Behavior Tree。&lt;/p&gt;
&lt;h3&gt;Q: 性能如何？&lt;/h3&gt;
&lt;p&gt;A: 比传统Behavior Tree略优，尤其在高频状态切换场景。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;StateTree核心要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;模块化&lt;/strong&gt; — 状态和任务独立，可复用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件驱动&lt;/strong&gt; — 灵活的状态切换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并行执行&lt;/strong&gt; — Operator State支持&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;易调试&lt;/strong&gt; — 内置调试工具&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码集成&lt;/strong&gt; — 可扩展Task和Condition&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;StateTree是UE5 AI开发的首选框架。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/state-tree&quot;&gt;UE5 StateTree官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/tree/release/Engine/Plugins/Experimental/StateTree&quot;&gt;StateTree插件源码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.epicgames.com/studio/lyra&quot;&gt;Lyra示例项目&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你掌握了UE5 StateTree的用法！&lt;/p&gt;
</content:encoded></item><item><title>游戏开发常用设计模式完全指南</title><link>https://gzhblog.cn/posts/2026-03/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E5%B8%B8%E7%94%A8%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/%E6%B8%B8%E6%88%8F%E5%BC%80%E5%8F%91%E5%B8%B8%E7%94%A8%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</guid><description>系统讲解游戏开发中最常用的设计模式，包括单例模式、观察者模式、状态机模式、对象池模式、命令模式等，并结合Unity和游戏开发实战进行深入分析。</description><pubDate>Mon, 02 Mar 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;设计模式是解决特定问题的最佳实践，是经过大量项目验证的代码组织方式。在游戏开发中，合理运用设计模式能让代码更清晰、更易维护、更易扩展。本文系统讲解游戏开发中最常用的设计模式。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 单例模式（Singleton）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;确保一个类只有一个实例，并提供全局访问点。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GameManager&lt;/td&gt;
&lt;td&gt;游戏状态管理，全局唯一&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AudioManager&lt;/td&gt;
&lt;td&gt;音频管理，统一控制背景音乐、音效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UIManager&lt;/td&gt;
&lt;td&gt;UI 管理，协调所有界面&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataManager&lt;/td&gt;
&lt;td&gt;数据管理，保存/加载游戏数据&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Unity 实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance != null &amp;amp;&amp;amp; Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;避免滥用单例，导致耦合度过高&lt;/li&gt;
&lt;li&gt;考虑使用 ScriptableObject 替代部分单例&lt;/li&gt;
&lt;li&gt;线程安全问题在 Unity 中较少出现，但需注意&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 观察者模式（Observer）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;定义对象间的一种一对多依赖关系，当一个对象状态改变时，所有依赖它的对象都会收到通知。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;事件系统&lt;/td&gt;
&lt;td&gt;玩家受伤通知、得分变化、任务完成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI 更新&lt;/td&gt;
&lt;td&gt;血量变化时自动更新血条&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;成就系统&lt;/td&gt;
&lt;td&gt;达成条件时触发成就奖励&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现方式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 事件参数
public class PlayerHealthChangedEvent
{
    public int CurrentHealth;
    public int MaxHealth;
}

// 事件管理器
public class EventManager
{
    public static EventManager Instance { get; private set; }

    private Dictionary&amp;lt;string, List&amp;lt;System.Action&amp;lt;object&amp;gt;&amp;gt;&amp;gt; _listeners = new();

    public void Subscribe(string eventName, System.Action&amp;lt;object&amp;gt; callback)
    {
        if (!_listeners.ContainsKey(eventName))
            _listeners[eventName] = new List&amp;lt;System.Action&amp;lt;object&amp;gt;&amp;gt;();
        _listeners[eventName].Add(callback);
    }

    public void Publish(string eventName, object data = null)
    {
        if (_listeners.ContainsKey(eventName))
        {
            foreach (var callback in _listeners[eventName])
            {
                callback?.Invoke(data);
            }
        }
    }
}

// 使用
public class HealthBar : MonoBehaviour
{
    private void Start()
    {
        EventManager.Instance.Subscribe(&quot;PlayerHealthChanged&quot;, OnHealthChanged);
    }

    private void OnHealthChanged(object data)
    {
        if (data is PlayerHealthChangedEvent e)
        {
            UpdateHealthBar(e.CurrentHealth, e.MaxHealth);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 状态机模式（State Machine）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;允许对象在内部状态改变时改变其行为，看起来好像修改了它的类。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;角色状态&lt;/td&gt;
&lt;td&gt;待机、行走、跑步、跳跃、攻击&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 行为&lt;/td&gt;
&lt;td&gt;巡逻、追击、攻击、逃跑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;游戏流程&lt;/td&gt;
&lt;td&gt;开始菜单、游戏中、暂停、游戏结束&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;简单实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public interface IState
{
    void Enter();
    void Update();
    void Exit();
}

public class StateMachine
{
    private IState _currentState;

    public void TransitionTo(IState newState)
    {
        _currentState?.Exit();
        _currentState = newState;
        _currentState.Enter();
    }

    public void Update()
    {
        _currentState?.Update();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Unity 角色状态机示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 待机状态
public class IdleState : IState
{
    private PlayerController _player;

    public IdleState(PlayerController player)
    {
        _player = player;
    }

    public void Enter()
    {
        _player.Animator.SetBool(&quot;IsWalking&quot;, false);
    }

    public void Update()
    {
        float h = Input.GetAxisRaw(&quot;Horizontal&quot;);
        if (Mathf.Abs(h) &amp;gt; 0.1f)
        {
            _player.StateMachine.TransitionTo(new WalkState(_player));
        }
    }

    public void Exit() { }
}

// 行走状态
public class WalkState : IState
{
    private PlayerController _player;

    public WalkState(PlayerController player)
    {
        _player = player;
    }

    public void Enter()
    {
        _player.Animator.SetBool(&quot;IsWalking&quot;, true);
    }

    public void Update()
    {
        float h = Input.GetAxisRaw(&quot;Horizontal&quot;);
        _player.transform.Translate(Vector3.right * h * _player.Speed * Time.deltaTime);

        if (Mathf.Abs(h) &amp;lt; 0.1f)
        {
            _player.StateMachine.TransitionTo(new IdleState(_player));
        }
    }

    public void Exit() { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 对象池模式（Object Pool）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;避免频繁的对象创建和销毁，复用已创建的对象。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;子弹/炮弹&lt;/td&gt;
&lt;td&gt;射击游戏中的大量子弹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;粒子特效&lt;/td&gt;
&lt;td&gt;爆炸、火花等频繁创建的特效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;敌人&lt;/td&gt;
&lt;td&gt;波次生成的敌人&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI 元素&lt;/td&gt;
&lt;td&gt;伤害数字、道具图标&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class ObjectPool&amp;lt;T&amp;gt; where T : class, new()
{
    private Stack&amp;lt;T&amp;gt; _pool = new();
    private System.Func&amp;lt;T&amp;gt; _createFunc;

    public ObjectPool(System.Func&amp;lt;T&amp;gt; createFunc)
    {
        _createFunc = createFunc;
    }

    public T Get()
    {
        return _pool.Count &amp;gt; 0 ? _pool.Pop() : _createFunc();
    }

    public void Return(T obj)
    {
        _pool.Push(obj);
    }
}

// Unity 中的对象池
public class BulletPool : MonoBehaviour
{
    public GameObject bulletPrefab;
    private ObjectPool&amp;lt;GameObject&amp;gt; _pool;

    private void Start()
    {
        _pool = new ObjectPool&amp;lt;GameObject&amp;gt;(() =&amp;gt;
        {
            return Instantiate(bulletPrefab);
        });
    }

    public GameObject GetBullet()
    {
        var bullet = _pool.Get();
        bullet.SetActive(true);
        return bullet;
    }

    public void ReturnBullet(GameObject bullet)
    {
        bullet.SetActive(false);
        _pool.Return(bullet);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;设置合理的池子容量上限&lt;/li&gt;
&lt;li&gt;对象回收时注意重置状态&lt;/li&gt;
&lt;li&gt;考虑线程安全问题&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 命令模式（Command）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;将请求封装成对象，从而允许参数化和排队执行请求。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;输入处理&lt;/td&gt;
&lt;td&gt;键盘按键映射为具体命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;撤销/重做&lt;/td&gt;
&lt;td&gt;记录操作历史，支持撤销重做&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 命令&lt;/td&gt;
&lt;td&gt;给 AI 单位下达移动、攻击等命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;回放系统&lt;/td&gt;
&lt;td&gt;录制操作命令并回放&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 命令接口
public interface ICommand
{
    void Execute();
    void Undo();
}

// 移动命令
public class MoveCommand : ICommand
{
    private Transform _target;
    private Vector3 _direction;
    private Vector3 _previousPosition;

    public MoveCommand(Transform target, Vector3 direction)
    {
        _target = target;
        _direction = direction;
    }

    public void Execute()
    {
        _previousPosition = _target.position;
        _target.position += _direction;
    }

    public void Undo()
    {
        _target.position = _previousPosition;
    }
}

// 命令管理器
public class CommandManager
{
    private Stack&amp;lt;ICommand&amp;gt; _history = new();
    private Stack&amp;lt;ICommand&amp;gt; _redoStack = new();

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _history.Push(command);
        _redoStack.Clear();
    }

    public void Undo()
    {
        if (_history.Count &amp;gt; 0)
        {
            var cmd = _history.Pop();
            cmd.Undo();
            _redoStack.Push(cmd);
        }
    }

    public void Redo()
    {
        if (_redoStack.Count &amp;gt; 0)
        {
            var cmd = _redoStack.Pop();
            cmd.Execute();
            _history.Push(cmd);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 工厂模式（Factory）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;定义创建对象的接口，让子类决定实例化哪个类。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;敌人生成&lt;/td&gt;
&lt;td&gt;根据波次生成不同类型敌人&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;道具生成&lt;/td&gt;
&lt;td&gt;随机掉落不同道具&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;技能释放&lt;/td&gt;
&lt;td&gt;创建不同的技能实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI 生成&lt;/td&gt;
&lt;td&gt;动态创建不同类型的界面&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 抽象工厂
public abstract class EnemyFactory
{
    public abstract Enemy CreateEnemy(EnemyType type);
}

// 具体工厂
public class UnityEnemyFactory : EnemyFactory
{
    public override Enemy CreateEnemy(EnemyType type)
    {
        Enemy enemy = type switch
        {
            EnemyType.Slime =&amp;gt; Resources.Load&amp;lt;Slime&amp;gt;(&quot;Prefabs/Enemies/Slime&quot;),
            EnemyType.Goblin =&amp;gt; Resources.Load&amp;lt;Goblin&amp;gt;(&quot;Prefabs/Enemies/Goblin&quot;),
            EnemyType.Dragon =&amp;gt; Resources.Load&amp;lt;Dragon&amp;gt;(&quot;Prefabs/Enemies/Dragon&quot;),
            _ =&amp;gt; throw new ArgumentException(&quot;Unknown enemy type&quot;)
        };

        return Instantiate(enemy);
    }
}

// 使用
public class WaveManager : MonoBehaviour
{
    private EnemyFactory _factory = new UnityEnemyFactory();

    public void SpawnWave(WaveConfig config)
    {
        foreach (var enemyType in config.Enemies)
        {
            var enemy = _factory.CreateEnemy(enemyType);
            enemy.transform.position = GetSpawnPoint();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 外观模式（Facade）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;为复杂的子系统提供一个统一的高层接口，简化子系统使用。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;音频系统&lt;/td&gt;
&lt;td&gt;统一管理背景乐、音效、语音&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存档系统&lt;/td&gt;
&lt;td&gt;统一管理存档、读档、自动存档&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;战斗系统&lt;/td&gt;
&lt;td&gt;整合伤害计算、Buff 管理、战斗结算&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 子系统
public class AudioSubsystem { }
public class SaveSubsystem { }
public class NetworkSubsystem { }

// 外观类
public class GameFacade
{
    public AudioSubsystem Audio { get; }
    public SaveSubsystem Save { get; }
    public NetworkSubsystem Network { get; }

    public GameFacade()
    {
        Audio = new AudioSubsystem();
        Save = new SaveSubsystem();
        Network = new NetworkSubsystem();
    }

    public void Initialize()
    {
        Audio.Init();
        Save.Init();
        Network.Connect();
    }
}

// 使用方不需要了解子系统细节
public class GameManager : MonoBehaviour
{
    private GameFacade _facade;

    private void Start()
    {
        _facade = new GameFacade();
        _facade.Initialize();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 策略模式（Strategy）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;定义一系列算法，把它们一个个封装起来，使它们可以相互替换。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;移动方式&lt;/td&gt;
&lt;td&gt;走路、跑步、蹲行、翻滚&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;攻击方式&lt;/td&gt;
&lt;td&gt;近战、远程、法术&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI 寻路&lt;/td&gt;
&lt;td&gt;A*、Dijkstra、导航网格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;难度设置&lt;/td&gt;
&lt;td&gt;简单、普通、困难&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 策略接口
public interface IMoveStrategy
{
    void Move(Transform transform, float speed);
}

// 行走策略
public class WalkStrategy : IMoveStrategy
{
    public void Move(Transform transform, float speed)
    {
        float h = Input.GetAxis(&quot;Horizontal&quot;);
        float v = Input.GetAxis(&quot;Vertical&quot;);
        Vector3 dir = new Vector3(h, 0, v).normalized;
        transform.position += dir * speed * Time.deltaTime;
    }
}

// 翻滚策略
public class RollStrategy : IMoveStrategy
{
    public void Move(Transform transform, float speed)
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 执行翻滚
        }
    }
}

// 使用
public class PlayerController : MonoBehaviour
{
    public IMoveStrategy MoveStrategy { get; set; }

    private void Update()
    {
        MoveStrategy?.Move(transform, Speed);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 装饰器模式（Decorator）&lt;/h2&gt;
&lt;h3&gt;概念&lt;/h3&gt;
&lt;p&gt;动态地给对象添加额外的职责，比继承更灵活。&lt;/p&gt;
&lt;h3&gt;游戏开发中的应用&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Buff 系统&lt;/td&gt;
&lt;td&gt;给角色添加各种增益/减益效果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;装备强化&lt;/td&gt;
&lt;td&gt;武器附加元素伤害、属性强化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;技能强化&lt;/td&gt;
&lt;td&gt;基础技能加强化效果&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 基础攻击
public class AttackComponent
{
    public virtual int CalculateDamage()
    {
        return 10;
    }
}

// 装饰器基类
public abstract class AttackDecorator : AttackComponent
{
    protected AttackComponent _baseAttack;

    public AttackDecorator(AttackComponent baseAttack)
    {
        _baseAttack = baseAttack;
    }

    public override int CalculateDamage()
    {
        return _baseAttack.CalculateDamage();
    }
}

// 火焰附魔
public class FireEnchantment : AttackDecorator
{
    public FireEnchantment(AttackComponent baseAttack) : base(baseAttack) { }

    public override int CalculateDamage()
    {
        int baseDamage = base.CalculateDamage();
        int fireDamage = 5;
        Debug.Log($&quot;造成 {baseDamage} 物理伤害 + {fireDamage} 火焰伤害&quot;);
        return baseDamage + fireDamage;
    }
}

// 冰霜附魔
public class IceEnchantment : AttackDecorator
{
    public IceEnchantment(AttackComponent baseAttack) : base(baseAttack) { }

    public override int CalculateDamage()
    {
        int baseDamage = base.CalculateDamage();
        int iceDamage = 3;
        Debug.Log($&quot;造成 {baseDamage} 物理伤害 + {iceDamage} 冰霜伤害 + 减速效果&quot;);
        return baseDamage + iceDamage;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;10. MVC 与 MVVM 架构&lt;/h2&gt;
&lt;h3&gt;MVC（Model-View-Controller）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Model（模型）     ←→     View（视图）
       ↑                    ↑
       └──── Controller ────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;部分&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;th&gt;游戏开发应用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;数据和业务逻辑&lt;/td&gt;
&lt;td&gt;角色数据、道具数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;View&lt;/td&gt;
&lt;td&gt;UI 显示&lt;/td&gt;
&lt;td&gt;血条、伤害数字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td&gt;处理输入，协调 Model 和 View&lt;/td&gt;
&lt;td&gt;输入控制、UI 事件响应&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;MVVM（Model-View-ViewModel）&lt;/h3&gt;
&lt;p&gt;适用于复杂 UI 系统：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;：数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt;：界面（不直接操作数据）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ViewModel&lt;/strong&gt;：界面逻辑，数据绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// ViewModel 示例
public class HealthBarViewModel : MonoBehaviour
{
    public Observable&amp;lt;int&amp;gt; CurrentHealth { get; } = new();
    public Observable&amp;lt;int&amp;gt; MaxHealth { get; } = new();

    private void Start()
    {
        // 绑定到 View
        CurrentHealth.OnChanged += UpdateHealthBar;
    }

    public void TakeDamage(int damage)
    {
        CurrentHealth.Value = Mathf.Max(0, CurrentHealth.Value - damage);
    }

    private void UpdateHealthBar(int health)
    {
        healthBarImage.fillAmount = (float)health / MaxHealth.Value;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;设计模式选用指南&lt;/h2&gt;
&lt;h3&gt;按场景选择&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;推荐模式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;全局唯一对象&lt;/td&gt;
&lt;td&gt;单例模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;事件通知系统&lt;/td&gt;
&lt;td&gt;观察者模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;角色/AI 状态&lt;/td&gt;
&lt;td&gt;状态机模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;频繁创建销毁的对象&lt;/td&gt;
&lt;td&gt;对象池模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;输入处理/撤销重做&lt;/td&gt;
&lt;td&gt;命令模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对象创建&lt;/td&gt;
&lt;td&gt;工厂模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;复杂系统整合&lt;/td&gt;
&lt;td&gt;外观模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;算法替换&lt;/td&gt;
&lt;td&gt;策略模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buff/装备强化&lt;/td&gt;
&lt;td&gt;装饰器模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI 系统&lt;/td&gt;
&lt;td&gt;MVC/MVVM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;避免过度设计&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;不要为了用模式而用模式&lt;/li&gt;
&lt;li&gt;简单问题简单解决&lt;/li&gt;
&lt;li&gt;代码可读性优先&lt;/li&gt;
&lt;li&gt;后续维护成本考虑&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;设计模式是工具，不是目的。掌握核心模式，理解其适用场景：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;核心价值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;单例&lt;/td&gt;
&lt;td&gt;全局访问点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;观察者&lt;/td&gt;
&lt;td&gt;解耦事件通知&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;状态机&lt;/td&gt;
&lt;td&gt;清晰的状态管理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;对象池&lt;/td&gt;
&lt;td&gt;性能优化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;命令&lt;/td&gt;
&lt;td&gt;封装请求/撤销&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;工厂&lt;/td&gt;
&lt;td&gt;封装创建逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;外观&lt;/td&gt;
&lt;td&gt;简化复杂系统&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;策略&lt;/td&gt;
&lt;td&gt;算法可替换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;装饰器&lt;/td&gt;
&lt;td&gt;动态扩展功能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;实践建议&amp;lt;/span&amp;gt;：从简单模式开始，在实际项目中体会模式的威力。遇到问题时思考&quot;这个问题是否有成熟的解决方案&quot;，而非强行套用模式。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;《设计模式：可复用面向对象软件的基础》&lt;/li&gt;
&lt;li&gt;《游戏编程模式》- Robert Nystrom&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unity.com/cn/how-to/architect-game-application-code&quot;&gt;Unity 官方设计模式教程&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文结合游戏开发实战经验，系统整理了常用设计模式及其 Unity 实现方式。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity+Cursor AI IDE开发实战指南</title><link>https://gzhblog.cn/posts/2026-03/unity-cursor-ai-ide%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-03/unity-cursor-ai-ide%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</guid><description>详细讲解如何将Cursor AI代码编辑器与Unity结合使用，实现智能代码补全、项目理解、多模型切换等高效开发流程。</description><pubDate>Sun, 01 Mar 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;AI 代码编辑器正在改变开发者的编码方式。Cursor 是一款基于 VS Code 的 AI IDE，完美兼容 Unity 开发环境。本文详细讲解如何将 Cursor 与 Unity 结合，实现高效智能开发。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Cursor 简介&lt;/h2&gt;
&lt;h3&gt;什么是 Cursor&lt;/h3&gt;
&lt;p&gt;Cursor 是一款内置 AI 能力的代码编辑器（AI IDE），基于 Visual Studio Code 设计，完全兼容 VS Code 的插件生态系统和快捷键。&lt;/p&gt;
&lt;h3&gt;核心特点&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;智能代码生成&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;根据自然语言或代码上下文自动生成代码片段、函数、类&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;内置 AI 聊天&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;直接在编辑器内与 AI 对话，询问代码解释、获取优化建议&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;代码库理解&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;分析整个项目结构，提供跨文件的精准建议&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;错误检测修复&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自动识别逻辑错误、语法问题、性能瓶颈&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;多模型支持&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;支持 GPT、Claude、Gemini 等多种模型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;下载地址&lt;/strong&gt;：https://cursor.com/download（Windows 可选 System 版本）&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;AI IDE vs 网页问答式 AI&lt;/h2&gt;
&lt;h3&gt;对比&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;维度&lt;/th&gt;
&lt;th&gt;AI IDE&lt;/th&gt;
&lt;th&gt;网页问答式 AI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;项目理解&lt;/td&gt;
&lt;td&gt;直接理解整个工程&lt;/td&gt;
&lt;td&gt;需要手动复制上传代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;上下文&lt;/td&gt;
&lt;td&gt;完整不丢失&lt;/td&gt;
&lt;td&gt;复制不全易缺失&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;代码修改&lt;/td&gt;
&lt;td&gt;一键应用&lt;/td&gt;
&lt;td&gt;手动粘贴回文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;新建脚本&lt;/td&gt;
&lt;td&gt;AI 自动创建&lt;/td&gt;
&lt;td&gt;需手动创建文件再粘贴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;批量修改&lt;/td&gt;
&lt;td&gt;支持多文件&lt;/td&gt;
&lt;td&gt;单文件操作&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;重构项目示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;网页 AI 流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;复制代码 → 复制报错 → 粘贴修改 → 来回切换网页 → 上下文丢失 → 容易理解错误
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;AI IDE 流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;打开项目 → 直接问 → 一键应用修改 → 逐步优化 → 不用离开编辑器
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;关联 Unity 编辑器&lt;/h2&gt;
&lt;h3&gt;配置步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Unity 菜单栏 &lt;strong&gt;Edit &amp;gt; Preferences &amp;gt; External Tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External Script Editor&lt;/strong&gt; 点击 &lt;strong&gt;Browse&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;找到 Cursor 安装路径，点击 &lt;strong&gt;Cursor.exe&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;External Script Editor 切换为 Cursor&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;让 Cursor 理解项目架构&lt;/h2&gt;
&lt;h3&gt;方法一：手动打开项目文件夹&lt;/h3&gt;
&lt;p&gt;在 Cursor 中点击 &lt;strong&gt;Open Folder&lt;/strong&gt;，定位到 Unity 工程文件夹（有 Assets 文件级的层级）。&lt;/p&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;不推荐&amp;lt;/span&amp;gt;：每次打开脚本都要重新设置。&lt;/p&gt;
&lt;h3&gt;方法二：配置 External Script Editor Args（推荐）&lt;/h3&gt;
&lt;p&gt;在 Unity 的 &lt;strong&gt;Preferences &amp;gt; External Tools &amp;gt; External Script Editor Args&lt;/strong&gt; 输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-r -g &quot;$(File):$(Line):$(Column)&quot; &quot;$(ProjectPath)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;参数作用&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$(File)&lt;/code&gt; — 文件路径&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$(Line)&lt;/code&gt; — 行号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$(Column)&lt;/code&gt; — 列号&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$(ProjectPath)&lt;/code&gt; — 项目根目录路径&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;效果&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;双击 Console 报错能定位到具体行&lt;/li&gt;
&lt;li&gt;Cursor 自动识别项目根目录&lt;/li&gt;
&lt;li&gt;所有工程通用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;方法三：导入第三方包（实现代码智能提示）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Git 导入&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装 Git：https://git-scm.com/&lt;/li&gt;
&lt;li&gt;Unity &lt;strong&gt;Package Manager &amp;gt; + &amp;gt; Install package from git URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;填入：&lt;code&gt;https://github.com/boxqkrtm/com.unity.ide.cursor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;安装完成后在 &lt;strong&gt;Preferences &amp;gt; External Tools&lt;/strong&gt; 会发现界面变化&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;本地导入&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下载 Zip：Code &amp;gt; Download Zip&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Package Manager &amp;gt; + &amp;gt; Install package from disk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择 package.json&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;生成 .csproj 文件&lt;/h3&gt;
&lt;p&gt;如果只生成了 .sln，需要：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Preferences &amp;gt; External Tools&lt;/strong&gt; 勾选相关选项&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Regenerate Project files&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;注&amp;lt;/span&amp;gt;：.sln 和 .csproj 只是让编辑器理解项目结构，不参与编译，删掉不影响运行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;安装代码智能插件&lt;/h2&gt;
&lt;h3&gt;推荐的插件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;插件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.NET SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C# 开发环境，必须安装&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C#&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C# 语言支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unity Code Snippets&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;快速调用 Unity 生命周期函数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;安装步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;View &amp;gt; Extensions&lt;/strong&gt;（或 Ctrl+Shift+X）&lt;/li&gt;
&lt;li&gt;搜索并安装插件（注意供应商名称一致）&lt;/li&gt;
&lt;li&gt;.NET SDK 会自动安装，如失败手动安装：https://dotnet.microsoft.com/zh-cn/download/dotnet&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Cursor AI 提示词设置&lt;/h2&gt;
&lt;h3&gt;Rules 全局规则&lt;/h3&gt;
&lt;p&gt;点击 &lt;strong&gt;设置按钮 &amp;gt; Rules, Skills, Subagents &amp;gt; Rules&lt;/strong&gt;，可设置全局提示词规则。&lt;/p&gt;
&lt;h3&gt;Unity 开发提示词模板&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;- 请用中文回复
- 节约 Token，简洁明了
- 禁止在项目中乱写新脚本
- 如需新建脚本，请先确认脚本名称和位置
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;两种 Rule 类型&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;作用域&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Rule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;账户全局，换电脑也生效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project Rule&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;仅当前项目生效&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;添加 Unity API 参考&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;Settings &amp;gt; Indexing &amp;amp; Docs &amp;gt; Add Doc&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL：&lt;code&gt;https://docs.unity3d.com/ScriptReference/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;提问时输入 &lt;code&gt;@Unity Doc&lt;/code&gt; 获取官方 API 答案&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;.cursorignore 配置&lt;/h2&gt;
&lt;h3&gt;作用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;提升 AI 响应速度（跳过无关文件）&lt;/li&gt;
&lt;li&gt;提高建议准确性（避免无效上下文）&lt;/li&gt;
&lt;li&gt;保护敏感信息&lt;/li&gt;
&lt;li&gt;减少干扰，上下文更干净&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Unity 开发配置模板&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Unity 生成文件
Library/
Temp/
Obj/
Build/
Logs/

# IDE
.vs/
.vscode/
.idea/

# 版本控制
.git/
.svn/

# 依赖
Packages/
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;协作模式详解&lt;/h2&gt;
&lt;h3&gt;四种模式&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;定位&lt;/th&gt;
&lt;th&gt;使用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;项目蓝图规划师&lt;/td&gt;
&lt;td&gt;大型功能启动初期，理清思路&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自主执行代理&lt;/td&gt;
&lt;td&gt;多文件复杂任务，自动创建/修改代码&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ask&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;技术咨询顾问&lt;/td&gt;
&lt;td&gt;解答疑问、解释代码，不修改文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Edit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;精准编辑助手&lt;/td&gt;
&lt;td&gt;对选中代码块进行精准修改&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Plan 示例&lt;/h3&gt;
&lt;p&gt;输入：&lt;code&gt;/plan 为游戏设计一个包含任务接取、追踪与奖励的完整任务系统&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;AI 会生成详细规划文档，列出需要创建/修改的脚本、关键步骤、技术思路。&lt;/p&gt;
&lt;h3&gt;Agent 示例&lt;/h3&gt;
&lt;p&gt;输入：&lt;code&gt;/agent 请实现一个完整的金币收集系统&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;AI 自动创建脚本、处理文件引用关系、运行相关命令。&lt;/p&gt;
&lt;h3&gt;Ask 示例&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;选中代码：&quot;请解释一下这段状态机是如何实现状态切换的？&quot;&lt;/li&gt;
&lt;li&gt;直接提问：&quot;Unity 中的协程（Coroutine）主要用来解决什么问题？&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Edit 示例&lt;/h3&gt;
&lt;p&gt;选中移动代码，输入：&lt;code&gt;将这段基于 Transform 的移动改为基于物理引擎（Rigidbody）驱动，并添加重力影响&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;工作流建议&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Plan（理清思路）→ Ask（解决疑问）→ Agent（核心实现）→ Edit（细节微调）
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;模型切换&lt;/h2&gt;
&lt;h3&gt;付费版功能&lt;/h3&gt;
&lt;p&gt;在聊天框点击 &lt;strong&gt;Auto&lt;/strong&gt; 可切换不同模型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;免费版默认，随机选择&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus&lt;/td&gt;
&lt;td&gt;需要付费订阅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT Codex&lt;/td&gt;
&lt;td&gt;需要付费订阅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：中国大陆 IP 无法使用 Claude 模型，需要 VPN + TUN 模式。&lt;/p&gt;
&lt;h3&gt;添加自定义模型&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Settings &amp;gt; Models&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Override OpenAI Base URL&lt;/strong&gt; 填写 Base URL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI API Key&lt;/strong&gt; 填写 API Key&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;+ Add Custom Model&lt;/strong&gt; 添加自定义模型&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Cursor + Unity 高效开发要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;关联 Unity&lt;/strong&gt;：External Script Editor 配置 Cursor.exe&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解项目&lt;/strong&gt;：配置 Args 参数 + 导入第三方包&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;智能提示&lt;/strong&gt;：安装 .NET SDK 和 C# 插件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置规则&lt;/strong&gt;：编写 Unity 开发提示词模板&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置 .cursorignore&lt;/strong&gt;：提升 AI 效率&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;灵活使用四种模式&lt;/strong&gt;：Plan &amp;gt; Agent &amp;gt; Ask &amp;gt; Edit&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心优势&amp;lt;/span&amp;gt;：AI IDE 能直接理解整个项目架构，支持多文件批量修改，一键应用代码，远超网页问答式 AI 的开发体验。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://cursor.com/download&quot;&gt;Cursor 官网&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/boxqkrtm/com.unity.ide.cursor&quot;&gt;Cursor Unity IDE 包&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/ScriptReference/&quot;&gt;Unity 官方 API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料，结合 Cursor AI IDE 实战经验。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>UE5 智能指针完全指南</title><link>https://gzhblog.cn/posts/2026-02/2026-02-26-ue5-smart-pointer/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/2026-02-26-ue5-smart-pointer/</guid><description>深入解析Unreal Engine 5的智能指针系统，涵盖TSharedPtr、TWeakPtr、TSharedRef的用法，以及在游戏开发中的实战技巧</description><pubDate>Thu, 26 Feb 2026 20:22:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;智能指针是现代C++开发的重要工具，UE5提供了自己的智能指针实现——TSmartPointer系列（&lt;code&gt;TSharedPtr&lt;/code&gt;、&lt;code&gt;TWeakPtr&lt;/code&gt;、&lt;code&gt;TSharedRef&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;本文系统讲解UE5智能指针的用法，帮你写出更安全的内存管理代码。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;为什么使用智能指针？&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;传统指针&lt;/th&gt;
&lt;th&gt;智能指针&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;手动new/delete&lt;/td&gt;
&lt;td&gt;自动内存管理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;容易内存泄漏&lt;/td&gt;
&lt;td&gt;RAII原则&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;悬空指针难排查&lt;/td&gt;
&lt;td&gt;引用计数安全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;所有权不明确&lt;/td&gt;
&lt;td&gt;所有权清晰&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;基础类型&lt;/h2&gt;
&lt;h3&gt;TSharedPtr 共享指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;CoreMinimal.h&quot;

// 创建共享指针
TSharedPtr&amp;lt;FVector&amp;gt; Position = MakeShared&amp;lt;FVector&amp;gt;();

// 使用默认构造
TSharedPtr&amp;lt;FVector&amp;gt; Direction = nullptr;

// 从原始指针创建
FVector* RawPtr = new FVector(1, 0, 0);
TSharedPtr&amp;lt;FVector&amp;gt; Speed = TSharedPtr&amp;lt;FVector&amp;gt;(RawPtr);

// 访问内容
float X = Position-&amp;gt;X;
float Y = (*Direction).Y;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TSharedRef 共享引用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 不可为空的共享指针
TSharedRef&amp;lt;FVector&amp;gt; Ref = MakeShared&amp;lt;FVector&amp;gt;();
Ref-&amp;gt;X = 100.f;

// TSharedRef 可以安全转换为 TSharedPtr
TSharedPtr&amp;lt;FVector&amp;gt; Ptr = Ref;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TWeakPtr 弱指针&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 不增加引用计数的观察者
TWeakPtr&amp;lt;FVector&amp;gt; WeakPosition;

// 从SharedPtr创建
TSharedPtr&amp;lt;FVector&amp;gt; Shared = MakeShared&amp;lt;FVector&amp;gt;();
TWeakPtr&amp;lt;FVector&amp;gt; Weak = Shared;

// 检查有效性
if (Weak.IsValid())
{
    // 安全使用
    TSharedPtr&amp;lt;FVector&amp;gt; Locked = Weak.Pin();
    if (Locked)
    {
        UE_LOG(LogTemp, Warning, TEXT(&quot;X: %f&quot;), Locked-&amp;gt;X);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;引用计数&lt;/h2&gt;
&lt;h3&gt;引用计数原理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;TSharedPtr&amp;lt;FVector&amp;gt; Ptr1 = MakeShared&amp;lt;FVector&amp;gt;();
// 引用计数 = 1

TSharedPtr&amp;lt;FVector&amp;gt; Ptr2 = Ptr1;
// 引用计数 = 2

{
    TSharedPtr&amp;lt;FVector&amp;gt; Ptr3 = Ptr1;
    // 引用计数 = 3
}
// Ptr3 销毁，引用计数 = 2

Ptr1.Reset();
// 引用计数 = 1

Ptr2.Reset();
// 引用计数 = 0，对象自动销毁
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;手动引用计数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 共享引用计数对象
TSharedRef&amp;lt;int32&amp;gt; CountRef = MakeShared&amp;lt;int32&amp;gt;();
*CountRef = 0;

TWeakPtr&amp;lt;int32&amp;gt; Weak = CountRef;

// 在需要的地方增加引用
{
    TSharedPtr&amp;lt;int32&amp;gt; Temp = CountRef;
    (*CountRef)++;
} // Temp 销毁但不影响原引用
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;线程安全&lt;/h2&gt;
&lt;h3&gt;线程安全版本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// TSharedPtr 默认非线程安全
TSharedPtr&amp;lt;FVector&amp;gt; ThreadUnsafe;

// 线程安全版本
TSharedPtr&amp;lt;FVector, ESPMode::ThreadSafe&amp;gt; ThreadSafe;

// 在多线程环境中使用
void WorkerThread()
{
    TSharedPtr&amp;lt;FVector, ESPMode::ThreadSafe&amp;gt; LocalCopy = GlobalPtr;
    if (LocalCopy)
    {
        // 安全访问
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;实战用法&lt;/h2&gt;
&lt;h3&gt;缓存管理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class UCachedDataManager : public UObject
{
public:
    // 缓存计算结果
    TSharedPtr&amp;lt;FVector&amp;gt; GetCachedDirection(FName Key)
    {
        if (Cache.Contains(Key))
        {
            return Cache[Key];
        }

        TSharedPtr&amp;lt;FVector&amp;gt; NewData = ComputeDirection(Key);
        Cache.Add(Key, NewData);
        return NewData;
    }

private:
    TMap&amp;lt;FName, TSharedPtr&amp;lt;FVector&amp;gt;&amp;gt; Cache;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;观察者模式&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class FObserverManager
{
public:
    void Register(TWeakPtr&amp;lt;FSubject&amp;gt; Observer)
    {
        Observers.Add(Observer);
    }

    void Notify()
    {
        // 只通知有效且存在的观察者
        for (auto It = Observers.CreateIterator(); It; ++It)
        {
            if (It-&amp;gt;IsValid())
            {
                if (TSharedPtr&amp;lt;FSubject&amp;gt; Observer = It-&amp;gt;Pin())
                {
                    Observer-&amp;gt;OnNotify();
                }
            }
            else
            {
                It.RemoveCurrent();
            }
        }
    }

private:
    TArray&amp;lt;TWeakPtr&amp;lt;FSubject&amp;gt;&amp;gt; Observers;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;异步加载&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;TSharedPtr&amp;lt;FAsyncLoadTask&amp;gt; LoadAssetAsync(const FSoftObjectPath&amp;amp; Path)
{
    return TSharedPtr&amp;lt;FAsyncLoadTask&amp;gt;(new FAsyncLoadTask(Path));
}

void OnAssetLoaded(TSharedPtr&amp;lt;FAssetData&amp;gt; Asset)
{
    if (Asset.IsValid())
    {
        // 使用加载的资产
        ProcessAsset(Asset);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;与UObject的关系&lt;/h2&gt;
&lt;h3&gt;重要限制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ⚠️ 智能指针不能指向UObject（因为UObject有自己的垃圾回收）
// 错误用法
TSharedPtr&amp;lt;AActor&amp;gt; Actor = MakeShared&amp;lt;AActor&amp;gt;(); // 编译错误！

// ✅ 正确做法：使用UObject的垃圾回收
TWeakObjectPtr&amp;lt;AActor&amp;gt; ActorPtr;

// 或使用TScriptInterface
TScriptInterface&amp;lt;IActorInterface&amp;gt; ActorInterface;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;TWeakObjectPtr&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 用于引用UObject而不影响GC
TWeakObjectPtr&amp;lt;AActor&amp;gt; CachedActor;

// 存储
CachedActor = MyActor;

// 检查有效性
if (CachedActor.IsValid())
{
    AActor* Actor = CachedActor.Get();
    // 安全使用
}

// 或使用Pin
if (TSharedPtr&amp;lt;AActor&amp;gt; Pinned = CachedActor.Pin())
{
    // 使用Pinned
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: 和标准库智能指针的区别？&lt;/h3&gt;
&lt;p&gt;A: UE5的TSmartPointer不依赖标准库，更轻量，且与UE的内存管理系统集成。&lt;/p&gt;
&lt;h3&gt;Q: 什么时候用TWeakPtr？&lt;/h3&gt;
&lt;p&gt;A: 观察者、缓存、避免循环引用时。&lt;/p&gt;
&lt;h3&gt;Q: 可以用在蓝图吗？&lt;/h3&gt;
&lt;p&gt;A: 智能指针是纯C++特性，蓝图无法直接使用。蓝图中用Object指针和接口。&lt;/p&gt;
&lt;h3&gt;Q: 性能如何？&lt;/h3&gt;
&lt;p&gt;A: 略有开销（引用计数），但比手动管理安全。游戏关键路径注意测试。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;UE5智能指针核心要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TSharedPtr&lt;/strong&gt; — 共享所有权，自动释放&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TSharedRef&lt;/strong&gt; — 不可为空的共享指针&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TWeakPtr&lt;/strong&gt; — 观察者，不增加引用计数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TWeakObjectPtr&lt;/strong&gt; — UObject专用弱引用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ESPMode::ThreadSafe&lt;/strong&gt; — 多线程版本&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;智能指针是编写安全、优雅C++代码的利器。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/smart-pointers-in-unreal-engine&quot;&gt;UE5智能指针文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-cpp-programming&quot;&gt;C++编程指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你掌握了UE5智能指针的用法！&lt;/p&gt;
</content:encoded></item><item><title>UE5 PicoVR开发环境配置与基础教程</title><link>https://gzhblog.cn/posts/2026-02/2026-02-25-ue5-picovr-development/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/2026-02-25-ue5-picovr-development/</guid><description>详细记录Unreal Engine 5配置PicoVR开发环境的过程，涵盖插件安装、真机调试、基础交互实现等核心步骤</description><pubDate>Wed, 25 Feb 2026 20:13:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Pico是国内领先的VR设备厂商，其Pico Neo系列和Pico 4系列支持Unreal Engine开发。本文记录UE5配置PicoVR开发环境的完整流程，帮你快速上手VR开发。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;环境要求&lt;/h2&gt;
&lt;h3&gt;硬件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pico Neo 3 / Neo 4 / Pico 4 企业版&lt;/li&gt;
&lt;li&gt;PC VR模式：RTX 1060及以上显卡&lt;/li&gt;
&lt;li&gt;USB数据线（支持数据传输）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;软件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Unreal Engine 5.0 - 5.4&lt;/li&gt;
&lt;li&gt;Pico VR SDK for Unreal&lt;/li&gt;
&lt;li&gt;Android Studio（用于配置Android SDK）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤一：下载Pico VR SDK&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;a href=&quot;https://developer.pico-interactive.com/&quot;&gt;Pico开发者中心&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;注册/登录开发者账号&lt;/li&gt;
&lt;li&gt;下载最新的 &lt;strong&gt;Pico XR Unreal SDK&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;解压得到 &lt;code&gt;PicoVR_UE我要刷屏SDK_v2.x.x&lt;/code&gt; 文件夹&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤二：安装Pico插件&lt;/h2&gt;
&lt;h3&gt;方法一：编辑器内安装&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开UE5编辑器&lt;/li&gt;
&lt;li&gt;编辑 → 插件 → 搜索 &quot;Pico&quot;&lt;/li&gt;
&lt;li&gt;找到 &lt;strong&gt;PicoXR&lt;/strong&gt; 插件，启用&lt;/li&gt;
&lt;li&gt;重启编辑器&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;方法二：手动安装&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;将SDK中的 &lt;code&gt;PicoVR&lt;/code&gt; 文件夹复制到项目 &lt;code&gt;Plugins&lt;/code&gt; 目录下&lt;/li&gt;
&lt;li&gt;右键 &lt;code&gt;.uproject&lt;/code&gt; 文件 → Generate Visual Studio project files&lt;/li&gt;
&lt;li&gt;用VS重新生成项目&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤三：配置Android SDK&lt;/h2&gt;
&lt;p&gt;Pico VR需要配置Android SDK环境：&lt;/p&gt;
&lt;h3&gt;1. 安装Android Studio&lt;/h3&gt;
&lt;p&gt;下载 &lt;a href=&quot;https://developer.android.com/studio&quot;&gt;Android Studio&lt;/a&gt;，安装时勾选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android SDK&lt;/li&gt;
&lt;li&gt;Android SDK Platform-Tools&lt;/li&gt;
&lt;li&gt;Android SDK Build-Tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 配置环境变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ANDROID_HOME = C:\Users\你的用户名\AppData\Local\Android\Sdk
ANDROID_SDK_ROOT = %ANDROID_HOME%
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 下载Pico专有SDK&lt;/h3&gt;
&lt;p&gt;在Pico开发者中心下载 &lt;strong&gt;Pico Mobile SDK&lt;/strong&gt;，解压后包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pico_vr_unity_aar&lt;/code&gt; — AAR库&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pico_vr_unity_libs&lt;/code&gt; — SO库&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤四：配置项目&lt;/h2&gt;
&lt;h3&gt;修改 &lt;code&gt;DefaultEngine.ini&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/Maps/VRTemplate_P ICO Map

[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName=&quot;/Script/VRBasePI CO&quot;,NewGameName=&quot;/Script/你的项目名&quot;)
+ActiveGameNameRedirects=(OldGameName=&quot;/Script/VRBoilerplate&quot;,NewGameName=&quot;/Script/你的项目名&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;配置Build.cs&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 你的项目.Build.cs
ExtraModuleNames.AddRange(new string[] { &quot;PicoVR&quot; });
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤五：配置VR Pawn&lt;/h2&gt;
&lt;h3&gt;创建VR Pawn&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;创建新的 &lt;code&gt;Blueprint Class&lt;/code&gt;，父类选择 &lt;strong&gt;Pawn&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;命名 &lt;code&gt;BP_VRPawn&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;添加组件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Camera&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;绑定到VR相机&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MotionControllerComponent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;左右手柄追踪&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PicoVRMotionControllerComponent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pico专用手柄组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SceneComponent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;VR追踪根节点&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;相机设置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在Pawn的BeginPlay中
UCameraComponent* Camera = Cast&amp;lt;UCameraComponent&amp;gt;(GetComponentByClass(UCameraComponent::StaticClass()));
if (Camera)
{
    Camera-&amp;gt;bLockToHmd = true;
    Camera-&amp;gt;bUsePawnControlRotation = false;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤六：配置手柄交互&lt;/h2&gt;
&lt;h3&gt;Pico手势追踪&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 获取手部骨骼数据
UPicoXRHandComponent* LeftHand = FindComponentByClass&amp;lt;UPicoXRHandComponent&amp;gt;();
if (LeftHand)
{
    FVector PalmPosition = LeftHand-&amp;gt;GetPalmPosition();
    FRotator PalmRotation = LeftHand-&amp;gt;GetPalmRotation();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;抓取物体&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 实现抓取逻辑
void AVRPawn::GrabObject(USceneComponent* Interactable)
{
    if (Interactable &amp;amp;&amp;amp; !bIsGrabbing)
    {
        bIsGrabbing = true;
        GrabbedComponent = Interactable;
        
        // 附加到手柄
        AttachToComponent(MotionControllerRight, FAttachmentTransformRules::SnapToTargetIncludingScale);
    }
}

void AVRPawn::ReleaseObject()
{
    if (bIsGrabbing &amp;amp;&amp;amp; GrabbedComponent)
    {
        bIsGrabbing = false;
        GrabbedComponent-&amp;gt;DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
        GrabbedComponent = nullptr;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;步骤七：真机调试&lt;/h2&gt;
&lt;h3&gt;1. 启用开发者模式&lt;/h3&gt;
&lt;p&gt;在Pico头显中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 通用 → 开发者模式 → 开启&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 连接设备&lt;/h3&gt;
&lt;p&gt;通过USB连接PC，头显中确认&quot;允许USB调试&quot;&lt;/p&gt;
&lt;h3&gt;3. 打包项目&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;文件 → 打包项目 → Android → Android (ASTC)&lt;/li&gt;
&lt;li&gt;选择输出目录&lt;/li&gt;
&lt;li&gt;等待打包完成&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. 安装APK&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;adb install -r 你的项目.apk
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 启动&lt;/h3&gt;
&lt;p&gt;在头显中找到你的应用，点击运行&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: 打包失败，提示SDK版本不对？&lt;/h3&gt;
&lt;p&gt;A: 检查 &lt;code&gt;Project Settings → Platforms → Android SDK&lt;/code&gt;，确保Android SDK路径正确，且NDK版本为21以上。&lt;/p&gt;
&lt;h3&gt;Q: 手柄不显示？&lt;/h3&gt;
&lt;p&gt;A: 确保Pico XR Plugin已正确启用，且项目使用的Pawn正确继承了VR模板。&lt;/p&gt;
&lt;h3&gt;Q: 画面撕裂/延迟高？&lt;/h3&gt;
&lt;p&gt;A: 开启VR内固定的注视点渲染（Fixed Foveated Rendering），并确保使用单通道立体渲染。&lt;/p&gt;
&lt;h3&gt;Q: 如何调参优化性能？&lt;/h3&gt;
&lt;p&gt;A: 在 &lt;code&gt;PicoXRGeneralSettings&lt;/code&gt; 中调整：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Foveation Level（注视点渲染等级）&lt;/li&gt;
&lt;li&gt;Spawn Priority（生成优先级）&lt;/li&gt;
&lt;li&gt;Pixel Density（像素密度）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Pico VR开发流程总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;下载SDK&lt;/strong&gt; → Pico开发者中心获取&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安装插件&lt;/strong&gt; → 编辑器内或手动安装&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置Android&lt;/strong&gt; → SDK环境变量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;配置Pawn&lt;/strong&gt; → VR相机+手柄组件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;真机调试&lt;/strong&gt; → 开发者模式+ADB安装&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Pico的生态在国内VR开发中较为成熟，适合快速验证VR游戏原型。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pico-interactive.com/&quot;&gt;Pico开发者中心&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.pico-interactive.com/sdk/unreal/engine&quot;&gt;Pico XR Unreal SDK文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/DevelopingForXR/XRDevelopment/VR/&quot;&gt;UE5 VR开发官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你可以开始PicoVR开发之旅了！&lt;/p&gt;
</content:encoded></item><item><title>UE5 委托与代理完全指南</title><link>https://gzhblog.cn/posts/2026-02/2025-02-25-ue5-delegate-guide/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/2025-02-25-ue5-delegate-guide/</guid><description>深入解析Unreal Engine 5的委托系统，涵盖声明、绑定、广播、解绑等操作，以及多播代理和动态委托的实战用法</description><pubDate>Sat, 21 Feb 2026 20:20:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;委托（Delegate）是UE5中实现事件驱动编程的核心机制。它允许你在一个对象上绑定函数回调，并在特定事件发生时触发调用。&lt;/p&gt;
&lt;p&gt;本文系统讲解UE5委托的各种用法，帮你掌握这一重要的C++技能。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;委托基础&lt;/h2&gt;
&lt;h3&gt;为什么使用委托？&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;传统回调&lt;/th&gt;
&lt;th&gt;委托优势&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;函数指针&lt;/td&gt;
&lt;td&gt;类型安全&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;接口回调&lt;/td&gt;
&lt;td&gt;声明简洁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;消息中心&lt;/td&gt;
&lt;td&gt;解耦设计&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;声明委托&lt;/h2&gt;
&lt;h3&gt;单播委托&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 声明一个返回bool、接受int参数的委托
DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPlayerAction, int32)

// 或者使用宏自动生成
DECLARE_MULTICAST_DELEGATE_OneParam(FOnDamageDelegate, float)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;参数规则&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 无参数
DECLARE_DELEGATE(FSimpleDelegate)

// 单参数（最多8个）
DECLARE_DELEGATE_OneParam(FOneParamDelegate, int32)
DECLARE_DELEGATE_TwoParams(FTwoParamDelegate, int32, FString)
DECLARE_DELEGATE_ThreeParams(FThreeParamDelegate, AActor*, float, FVector)

// 返回值
DECLARE_DELEGATE_RetVal(bool, FReturnBoolDelegate)
DECLARE_DELEGATE_RetVal_OneParam(int32, FReturnIntDelegate, float)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;绑定函数&lt;/h2&gt;
&lt;h3&gt;绑定普通成员函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class AMyActor : public AActor
{
    UFUNCTION()
    void HandleDamage(float Damage);

    void BindDelegate();
};

void AMyActor::BindDelegate()
{
    // 假设有一个委托对象
    FOnDamageDelegate OnDamage;

    // 绑定到成员函数
    OnDamage.AddDynamic(this, &amp;amp;AMyActor::HandleDamage);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绑定静态函数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void MyStaticFunction(float Damage)
{
    UE_LOG(LogTemp, Warning, TEXT(&quot;Static handler: %f&quot;), Damage);
}

void AMyActor::BindStatic()
{
    FOnDamageDelegate OnDamage;
    OnDamage.AddStatic(&amp;amp;MyStaticFunction);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绑定Lambda&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void AMyActor::BindLambda()
{
    FOnDamageDelegate OnDamage;

    // C++14+ 支持Lambda
    int32 Count = 0;
    OnDamage.AddLambda([this, Count](float Damage) {
        UE_LOG(LogTemp, Warning, TEXT(&quot;Lambda: %f, Count: %d&quot;), Damage, Count);
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;多播代理&lt;/h2&gt;
&lt;h3&gt;声明与使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 声明多播委托
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHealthChanged, float /*OldHealth*/, float /*NewHealth*/);

// 在类中定义
UPROPERTY()
FOnHealthChanged OnHealthChangedDelegate;

// 广播（调用所有绑定的函数）
void AMyCharacter::TakeDamage(float Damage)
{
    float OldHealth = Health;
    Health -= Damage;
    OnHealthChangedDelegate.Broadcast(OldHealth, Health);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;添加/移除监听&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void AMyActor::RegisterCallbacks(AMyCharacter* Character)
{
    if (Character)
    {
        // 添加监听
        Character-&amp;gt;OnHealthChangedDelegate.AddDynamic(this, &amp;amp;AMyActor::OnHealthChanged);

        // 移除监听
        Character-&amp;gt;OnHealthChangedDelegate.RemoveDynamic(this, &amp;amp;AMyActor::OnHealthChanged);
    }
}

void AMyActor::OnHealthChanged(float OldHealth, float NewHealth)
{
    UE_LOG(LogTemp, Warning, TEXT(&quot;Health: %f -&amp;gt; %f&quot;), OldHealth, NewHealth);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;清空所有绑定&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 清空所有监听
OnHealthChangedDelegate.Clear();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;动态委托&lt;/h2&gt;
&lt;h3&gt;动态多播代理&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 用于蓝图支持的动态委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSpellCast, FVector, Location, float, Damage);

// 实现
UPROPERTY(BlueprintAssignable)
FOnSpellCast OnSpellCastDelegate;

// 在Blueprint中绑定
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;生成函数签名&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;宏&lt;/th&gt;
&lt;th&gt;生成函数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AddDynamic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AddUFunction&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RemoveDynamic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RemoveUFunction&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IsBound&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IsBoundToGameInstance&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;实战：事件系统&lt;/h2&gt;
&lt;h3&gt;定义事件管理器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// UGameInstanceSubsystem 实现全局事件
UCLASS()
class UEventManager : public UGameInstanceSubsystem, public FTickableGameObject
{
    GENERATED_BODY()

public:
    // 玩家相关事件
    FDelegateHandle OnPlayerDamagedHandle;
    FDelegateHandle OnPlayerDiedHandle;

    virtual void Initialize(FSubsystemCollectionBase&amp;amp; Collection) override;
    virtual void Deinitialize() override;

    // 事件广播
    void BroadcastPlayerDamaged(AActor* Player, float Damage);
    void BroadcastPlayerDied(AActor* Player);

    // 事件监听
    void ListenPlayerDamaged(UObject* Listener, FName FunctionName);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void UEventManager::Initialize(FSubsystemCollectionBase&amp;amp; Collection)
{
    Super::Initialize(Collection);
}

void UEventManager::BroadcastPlayerDamaged(AActor* Player, float Damage)
{
    // 广播到所有监听者
    OnPlayerDamaged.Broadcast(Player, Damage);
}

void UEventManager::ListenPlayerDamaged(UObject* Listener, FName FunctionName)
{
    if (Listener &amp;amp;&amp;amp; !FunctionName.IsNone())
    {
        FScriptDelegate Delegate;
        Delegate.BindUFunction(Listener, FunctionName);
        OnPlayerDamaged.Add(Delegate);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: AddDynamic报错？&lt;/h3&gt;
&lt;p&gt;A: 确保目标函数声明为 &lt;code&gt;UFUNCTION()&lt;/code&gt;，且接受正确参数。&lt;/p&gt;
&lt;h3&gt;Q: 内存泄漏？&lt;/h3&gt;
&lt;p&gt;A: 确保在 &lt;code&gt;EndPlay&lt;/code&gt; 或 &lt;code&gt;Deinitialize&lt;/code&gt; 中调用 &lt;code&gt;RemoveDynamic&lt;/code&gt; 或 &lt;code&gt;Clear&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;Q: 委托与接口怎么选？&lt;/h3&gt;
&lt;p&gt;A: 简单1对1回调用委托，多对多用多播代理，需要蓝图支持用动态委托。&lt;/p&gt;
&lt;h3&gt;Q: 如何调试委托绑定？&lt;/h3&gt;
&lt;p&gt;A: 使用 &lt;code&gt;IsBound()&lt;/code&gt; 检查，或在Broadcast前加日志。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;UE5委托核心要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;单播委托&lt;/strong&gt; — 一对一回调&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多播代理&lt;/strong&gt; — 一对多广播&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;动态委托&lt;/strong&gt; — 蓝图支持&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生命周期&lt;/strong&gt; — 记得解绑防止泄漏&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;委托是实现解耦和事件驱动编程的利器。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/delegates-and-lamba-expressions-in-unreal-engine&quot;&gt;UE5委托官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-cpp-programming&quot;&gt;C++编程指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你掌握了UE5委托的核心用法！&lt;/p&gt;
</content:encoded></item><item><title>UE5 网络同步完全指南</title><link>https://gzhblog.cn/posts/2026-02/2026-02-17-ue5-network-replication/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/2026-02-17-ue5-network-replication/</guid><description>深入解析Unreal Engine 5的网络同步机制，涵盖Actor复制、RPC调用、变量同步、心跳检测等核心概念，帮你快速掌握多人游戏网络开发</description><pubDate>Tue, 17 Feb 2026 20:17:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;网络同步是多人游戏开发的核心技术栈。UE5提供了强大且灵活的网络框架，封装了底层的Socket和序列化管理，让开发者能专注于游戏逻辑而非网络细节。&lt;/p&gt;
&lt;p&gt;本文系统讲解UE5的网络同步机制，帮你从零构建多人游戏。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;h3&gt;服务器-客户端架构&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;游戏权威来源，负责验证所有操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;接收服务器数据，本地预测执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Listen Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;玩家充当服务器（常见于P2P）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dedicated Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;专用服务器，无图形渲染&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;复制模式&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Actor Replicate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;自动同步Actor状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RPC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;远程过程调用，触发特定函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Variable Replicate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;变量值同步&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Actor复制基础&lt;/h2&gt;
&lt;h3&gt;启用复制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在Actor类的构造函数中启用
AActor::AActor()
{
    bReplicates = true;  // 启用复制
    NetUpdateFrequency = 10.f;  // 更新频率
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;设置复制距离&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 默认10米内复制，可通过SetReplicateMovement自定义
SetReplicateMovement(true);

// 或使用NetCullDistanceSquared
NetCullDistanceSquared = 100000000.f;  // 10000米平方
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;属性同步&lt;/h2&gt;
&lt;h3&gt;声明复制属性&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在Actor的头文件中
UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    // 同步生命值
    UPROPERTY(Replicated, BlueprintReadWrite, Category = &quot;Attributes&quot;)
    float Health = 100.f;

    // 仅服务器可修改
    UPROPERTY(ReplicatedUsing = OnRep_Score, BlueprintReadOnly, Category = &quot;Game&quot;)
    int32 Score = 0;

protected:
    UFUNCTION()
    void OnRep_Score(int32 OldScore);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现GetLifetimeReplicatedProps&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void AMyCharacter::GetLifetimeReplicatedProps(TArray&amp;lt;FLifetimeProperty&amp;gt;&amp;amp; OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    DOREPLIFETIME(AMyCharacter, Health);
    DOREPLIFETIME(AMyCharacter, Score);
}

void AMyCharacter::OnRep_Score(int32 OldScore)
{
    // 分数变化时调用（客户端）
    UE_LOG(LogTemp, Warning, TEXT(&quot;Score changed: %d -&amp;gt; %d&quot;), OldScore, Score);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;RPC远程调用&lt;/h2&gt;
&lt;h3&gt;服务器调用客户端&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在头文件中声明
UFUNCTION(Server, Reliable)
void ServerTakeDamage(float DamageAmount, AController* InstigatorController);

UFUNCTION(Client, Reliable)
void ClientShowDamageNumber(float Damage, FVector Location);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 实现
void AMyCharacter::ServerTakeDamage_Implementation(float DamageAmount, AController* InstigatorController)
{
    // 服务器验证并应用伤害
    Health -= DamageAmount;
    OnRep_Health();

    // 通知所有客户端显示伤害数字
    ClientShowDamageNumber(DamageAmount, GetActorLocation());
}

void AMyCharacter::ClientShowDamageNumber_Implementation(float Damage, FVector Location)
{
    // 客户端播放伤害数字特效
    ShowFloatingDamageText(Damage, Location);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;客户端调用服务器&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;UFUNCTION(Server, Reliable, WithValidation)
void ServerPurchaseItem(int32 ItemID);

void AMyCharacter::ServerPurchaseItem_Implementation(int32 ItemID)
{
    // 服务器验证并处理购买逻辑
    if (CanAfford(ItemID))
    {
        GiveItem(ItemID);
        DeductGold(ItemID);
    }
}

bool AMyCharacter::ServerPurchaseItem_Validate(int32 ItemID)
{
    // 安全检查
    return ItemID &amp;gt;= 0 &amp;amp;&amp;amp; ItemID &amp;lt; MaxItems;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;角色移动同步&lt;/h2&gt;
&lt;h3&gt;角色移动组件配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    // 确认网络配置
    GetCharacterMovement()-&amp;gt;bReplicateInputs = true;
    GetCharacterMovement()-&amp;gt;bServerAcceptClientAuthoritativePosition = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;预测性移动&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在PlayerController中启用预测
void AMyPlayerController::BeginPlay()
{
    Super::BeginPlay();

    // 启用客户端预测
    if (IsLocalPlayerController())
    {
        Player = Cast&amp;lt;AUnrealPlayer&amp;gt;(GetPawn());
        if (Player)
        {
            Player-&amp;gt;SetReplicateMovement(true);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;网络更新频率&lt;/h2&gt;
&lt;h3&gt;优化NetUpdateFrequency&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 高频更新（如玩家）
NetUpdateFrequency = 30.f;  // 每秒30次

// 低频更新（如NPC）
NPC-&amp;gt;NetUpdateFrequency = 10.f;  // 每秒10次

// 静态物体
StaticMeshActor-&amp;gt;NetUpdateFrequency = 1.f;  // 每秒1次
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;条件复制&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 仅在值变化时复制
DOREPLIFETIME_CONDITION(AMyCharacter, Health, COND_OwnerOnly);

// 条件枚举
// COND_None - 无条件复制
// COND_OwnerOnly - 仅所有者
// COND_InitialOnly - 仅初始化时
// COND_SkipOwner - 跳过所有者
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;连接状态检测&lt;/h2&gt;
&lt;h3&gt;心跳检测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 在PlayerController中
void AMyPlayerController::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);

    // 检查连接状态
    if (IsLocalController())
    {
        UNetConnection* Connection = Player-&amp;gt;GetNetConnection();
        if (Connection)
        {
            float RoundTripTime = Connection-&amp;gt;GetRoundTripTime();
            UE_LOG(LogTemp, Warning, TEXT(&quot;RTT: %.2f ms&quot;), RoundTripTime * 1000.f);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;断开检测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void AMyGameMode::NotifyLogout(AController* Exiting)
{
    // 清理玩家数据
    AMyPlayerState* PS = Exiting-&amp;gt;GetPlayerState&amp;lt;AMyPlayerState&amp;gt;();
    if (PS)
    {
        SavePlayerData(PS);
        RemoveFromLeaderboard(PS);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;Q: 客户端移动不流畅？&lt;/h3&gt;
&lt;p&gt;A: 启用客户端预测和服务器校准。在 &lt;code&gt;DefaultEngine.ini&lt;/code&gt; 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[/Script/EngineSettings]
n.VerifyPeer=false  // 避免服务器校准延迟
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q: RPC调用失败？&lt;/h3&gt;
&lt;p&gt;A: 检查：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;函数是否声明为 &lt;code&gt;UFUNCTION(Server/Client)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;函数签名是否一致&lt;/li&gt;
&lt;li&gt;bReplicates是否启用&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Q: 大量Actor同步卡顿？&lt;/h3&gt;
&lt;p&gt;A: 使用 &lt;code&gt;Relevancy&lt;/code&gt; 规则或 &lt;code&gt;NetDriveDistance&lt;/code&gt; 限制复制范围。优先同步玩家关心的Actor。&lt;/p&gt;
&lt;h3&gt;Q: 如何调试网络？&lt;/h3&gt;
&lt;p&gt;A: 使用控制台命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;net. 1&lt;/code&gt; - 显示网络debug信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;net.ShowReps&lt;/code&gt; - 显示复制详情&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p.NetSmoothingMode&lt;/code&gt; - 网络平滑模式&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;UE5网络同步核心要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Actor复制&lt;/strong&gt; — bReplicates + NetUpdateFrequency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性同步&lt;/strong&gt; — DOREPLIFETIME + OnRep回调&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RPC调用&lt;/strong&gt; — UFUNCTION(Server/Client) + _Validate&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;移动同步&lt;/strong&gt; — 客户端预测 + 服务器校准&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频率优化&lt;/strong&gt; — 动静分离，按需更新&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;多人游戏开发的关键是理解服务器权威和客户端预测的配合。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-network-architecture&quot;&gt;UE5网络同步官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/firesharkstudios/Blues-Network-Compendium&quot;&gt;网络compendium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/community/learning/courses/Vdf/unreal-engine-multiplayer-concept-overview&quot;&gt;UE5 Multiplayer指南&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;🎉 &lt;strong&gt;恭喜！&lt;/strong&gt; 现在你掌握了UE5网络同步的核心技术！&lt;/p&gt;
</content:encoded></item><item><title>Unity XR性能优化核心知识完全指南</title><link>https://gzhblog.cn/posts/2026-02/unity-xr%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/unity-xr%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%A0%B8%E5%BF%83%E7%9F%A5%E8%AF%86%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</guid><description>从CPU到GPU，从渲染到内存，深度解析Unity XR性能优化的核心知识点，涵盖Draw Call、批次处理、Overdraw、内存带宽、UI优化等关键领域。</description><pubDate>Sun, 15 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;XR（VR/AR/MR）应用对性能要求极为严苛——需要同时保证高帧率渲染、物理计算、交互系统稳定运行。稍有卡顿就会严重影响用户体验。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;渲染批次基础&lt;/h2&gt;
&lt;h3&gt;Draw Call 与 SetPass&lt;/h3&gt;
&lt;p&gt;理解渲染批次，先要搞清楚 &lt;strong&gt;Draw Call&lt;/strong&gt; 和 &lt;strong&gt;SetPass&lt;/strong&gt; 的区别。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Draw Call&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CPU 向 GPU 发送的一条命令，告诉 GPU &quot;画这个网格（Mesh）&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SetPass Call&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;统计 GPU 准备渲染状态（切换 Shader、贴图、Blend 模式等）的次数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;简单比喻：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SetPass&lt;/strong&gt; = 工厂里工人为下一个生产任务&lt;strong&gt;更换工具、调整机器设置、准备好原材料&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Draw Call&lt;/strong&gt; = 工人&lt;strong&gt;开始用这套设置实际组装产品&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;一个 SetPass Call 可以为一次或多次 Draw Call 服务&lt;/strong&gt;，只要这些 Draw Call 使用完全相同的渲染状态。这就是合批（Batching）的基础。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四大批次技术&lt;/h2&gt;
&lt;h3&gt;动态批次（Dynamic Batching）&lt;/h3&gt;
&lt;p&gt;Unity 在运行时由 CPU 自动合并满足限制条件的多个动态物体网格，合成一个大网格一次性提交给 GPU。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 动态批次自动生效，无需手动开启
// 适用场景：大量小型相同材质物体（如场景草地、碎石）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;重要&amp;lt;/span&amp;gt;：动态批次是 &lt;strong&gt;内置渲染管线（Build-in）&lt;/strong&gt; 的重要优化手段。在 SRP（URP/HDRP）管线中主要采用 &lt;strong&gt;SRP Batcher&lt;/strong&gt;，URP 渲染管线可以使用动态批次。&lt;/p&gt;
&lt;h3&gt;静态批次（Static Batching）&lt;/h3&gt;
&lt;p&gt;对不移动的物体标记 &lt;strong&gt;Batching Static&lt;/strong&gt;，Unity 在构建时合并相同材质的物体。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在 Inspector 中勾选 Batching Static
// 或使用 API 强制合并
StaticBatchingUtility.Combine(gameObject);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;静态批次规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须勾选 Batching Static&lt;/li&gt;
&lt;li&gt;必须相同材质、相同渲染状态（Blend/ZTest/Pass）&lt;/li&gt;
&lt;li&gt;每批次最多 64,000 个顶点&lt;/li&gt;
&lt;li&gt;运行时不能改变 Transform（位置、旋转、缩放）&lt;/li&gt;
&lt;li&gt;物体不能改变网格数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;内存警告&amp;lt;/span&amp;gt;：静态批次会占用额外内存，因为合并后的网格是唯一存在的。合并物体越多，顶点越多，内存越大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;释放静态批次内存：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 对象不再使用时，手动释放
Resources.UnloadAsset(meshData);
AssetBundle.Unload(true);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GPU Instancing&lt;/h3&gt;
&lt;p&gt;GPU Instancing 允许 CPU 一次 Draw Call 通知 GPU 渲染 N 个相同网格但不同变换/颜色的物体。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理对比：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;技术&lt;/th&gt;
&lt;th&gt;CPU 端&lt;/th&gt;
&lt;th&gt;GPU 端&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;动态/静态批次&lt;/td&gt;
&lt;td&gt;合并网格，有计算和内存开销&lt;/td&gt;
&lt;td&gt;收到一个大网格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU Instancing&lt;/td&gt;
&lt;td&gt;只传递数据，开销小&lt;/td&gt;
&lt;td&gt;承担更多矩阵运算&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;// Shader 中添加支持
#pragma multi_compile_instancing

// 材质球上勾选 Enable GPU Instancing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;适用场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;大量相同物体（树木、森林、岩石、子弹等）&lt;/li&gt;
&lt;li&gt;实例之间只有 Transform、颜色、UV 偏移等微小变化&lt;/li&gt;
&lt;li&gt;动态物体，但网格和材质固定，性能瓶颈在 CPU 时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不适用场景：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个物体材质/贴图不同&lt;/li&gt;
&lt;li&gt;实例数量很少（几十个以内，开销可能反而更大）&lt;/li&gt;
&lt;li&gt;大量独立网格和材质&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SkinnedMeshRenderer（不支持）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;SRP Batcher&lt;/h3&gt;
&lt;p&gt;SRP Batcher 是 &lt;strong&gt;SRP（URP/HDRP）&lt;/strong&gt; 下的 CPU 优化技术，通过将物体和材质数据缓存到 GPU，减少计算和上传开销。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不直接减少 Draw Call 数量&lt;/strong&gt;，但减少 CPU 到 GPU 的数据传输&lt;/li&gt;
&lt;li&gt;让同一 Shader 的不同材质对象共用已缓存的 GPU 常量缓冲&lt;/li&gt;
&lt;li&gt;Draw Call 数量不变，但 &lt;strong&gt;CPU 渲染线程时间显著下降&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：Frame Debugger 中显示的是 SRP Batch，而不是每个物体的单独 Draw Call。实际上 GPU 仍在分别绘制，CPU 端提交被优化了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SRP Batcher 使用要求：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shader 必须声明 UnityPerDraw 和 UnityPerMaterial 的 Constant Buffer&lt;/li&gt;
&lt;li&gt;对象不能使用 MaterialPropertyBlocks&lt;/li&gt;
&lt;li&gt;对象需要是 MeshRenderer 或 SkinnedMeshRenderer&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;四种技术优先级&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;渲染管线&lt;/th&gt;
&lt;th&gt;优先级顺序&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;内置渲染管线&lt;/td&gt;
&lt;td&gt;静态批次 &amp;gt; GPU Instancing &amp;gt; 动态批次 &amp;gt; 单独绘制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SRP 管线&lt;/td&gt;
&lt;td&gt;静态批次 &amp;gt; SRP Batcher &amp;gt; GPU Instancing &amp;gt; 单独绘制&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：静态批次执行后就不会再执行 GPU Instancing，但会执行 SRP Batcher。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;GPU 性能优化&lt;/h2&gt;
&lt;h3&gt;Overdraw（过度绘制）&lt;/h3&gt;
&lt;p&gt;Overdraw 指同一像素在一帧中被重复绘制多次，上一次计算结果会被覆盖或叠加。&lt;/p&gt;
&lt;h4&gt;优化思路一：针对不透明物体&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 从前向后渲染&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unity 内部处理不透明物体已采用这种方式&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;Early-Z&lt;/strong&gt;（提前深度测试）在片元着色器运行之前丢弃被遮挡的片元&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 遮挡剔除（Occlusion Culling）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Window &amp;gt; Rendering &amp;gt; Occlusion Culling
// 避免完全不可见的物体进入渲染
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;优化思路二：针对半透明物体&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 减少透明层数&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI 背景、HUD 尽量合并图层，少用大面积透明混合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Alpha Test 替代 Alpha Blend&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;树叶、栅栏等使用 Alpha Test（Clip）替代 Alpha Blend，让深度测试能提前丢弃片元&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 排序优化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;透明物体按从远到近绘制，减少不必要的重复混合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 特殊替代方案&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;粒子系统远处改用广告牌效果，降低粒子数量和透明叠加&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;优化思路三：针对 UI 系统&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1. 图集 + 批次&lt;/strong&gt; — 减少 Draw Call，但避免大图集带来过多 Overdraw&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 减少叠层&lt;/strong&gt; — UI 设计时避免多层全屏半透明面板&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. RectMask2D 替代 Mask&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通 Mask 相当于多了一层全屏绘制，浪费填充率&lt;/li&gt;
&lt;li&gt;RectMask2D 是更轻量级的矩形裁剪，不会产生额外的全屏 OverDraw&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Stencil 遮罩&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复杂形状裁剪使用 Stencil（模板缓冲），需要在自定义 Shader 中实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. UI Renderer 独立渲染&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;避免与 3D 场景互相叠加 OverDraw&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内存带宽优化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;内存带宽&lt;/strong&gt; = 单位时间内，显存（VRAM）和 GPU 内部之间能传输的数据量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GPU 架构比喻：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ALU = 只做加减乘除、逻辑运算的工人&lt;/li&gt;
&lt;li&gt;Shader Core = 负责跑一条着色器指令的一组工人&lt;/li&gt;
&lt;li&gt;SM（流式多处理器）= 一个小工厂，内有成百上千个工人&lt;/li&gt;
&lt;li&gt;显存 = 存储原料的大仓库&lt;/li&gt;
&lt;li&gt;内存带宽 = 从大仓库运原料到小工厂的输送带&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;优化思路：减少显存读写量，让 GPU 少搬运，多干活&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;1. 减少分辨率和像素相关开销&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;降低渲染分辨率（动态分辨率、注视点渲染）&lt;/li&gt;
&lt;li&gt;降低抗锯齿采样&lt;/li&gt;
&lt;li&gt;减少 OverDraw&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. 优化纹理相关开销&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用 Mipmap&lt;/strong&gt;：远处物体采样低分辨率贴图，减少带宽浪费&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩纹理格式&lt;/strong&gt;：ASTC/ETC2 等压缩格式显著减少带宽占用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;贴图合并&lt;/strong&gt;：把粗糙度、金属度纹理合并，减少采样次数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;合理分辨率&lt;/strong&gt;：角色贴图、UI 纹理尽量使用满足需求的最小分辨率&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. 提高缓存利用率&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;避免 Shader 中随机纹理采样，尽量连续访问&lt;/li&gt;
&lt;li&gt;减少冗余读取，能一次采样合并的就合并&lt;/li&gt;
&lt;li&gt;前向渲染排序让 Early-Z 提前丢掉被挡片元&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;4. 减少不必要的数据存储&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;避免冗余拷贝（同一贴图拷贝到多个 RT）&lt;/li&gt;
&lt;li&gt;利用 GPU 共享内存缓存重复计算的数据&lt;/li&gt;
&lt;li&gt;压缩 G-Buffer（延迟渲染阶段存储几何信息的缓冲区）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;5. 优化 RenderTarget&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;使用低精度格式&lt;/li&gt;
&lt;li&gt;屏幕后处理效果用 1/2 或 1/4 分辨率处理后放大&lt;/li&gt;
&lt;li&gt;多个后处理效果合并到一个 Pass，避免重复拷贝&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;网格优化&lt;/h2&gt;
&lt;h3&gt;顶点与片元基础&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;顶点&lt;/strong&gt; = 3D 模型的&quot;钢筋骨架&quot;，决定模型形状&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;片元&lt;/strong&gt; = 光栅化阶段产生的候选像素点，决定屏幕上每个像素的最终颜色&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GPU 渲染流程：先有骨架（顶点），再填满砖块（片元），最后变成完整画面。&lt;/p&gt;
&lt;h3&gt;顶点优化思路&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;从本质上减少顶点以及顶点相关的计算&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;降低顶点数量&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LOD（Level of Detail）&lt;/li&gt;
&lt;li&gt;网格简化&lt;/li&gt;
&lt;li&gt;使用 Impostor（公告牌替代品）优化远距离物体&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Billboard（广告牌）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多角度烘焙到球面或半球面纹理图集&lt;/li&gt;
&lt;li&gt;Octahedral Impostor 优化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;蒙皮优化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少骨骼数量、减少顶点权重、合并骨骼等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;顶点动画优化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把复杂的程序形变改为 VAT（顶点动画纹理）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;顶点着色器优化&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;减少 v2f 里插值数量（TEXCOORD 通道）&lt;/li&gt;
&lt;li&gt;移动端着色器中数值类型尽量用 half&lt;/li&gt;
&lt;li&gt;避免大量 sin/cos/pow，用 x*x 替代 pow(x,2.0)&lt;/li&gt;
&lt;li&gt;预计算复杂函数结果到纹理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;合批处理&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;剔除先于变化&lt;/strong&gt; — 使用遮挡剔除、视锥剔除&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;片元优化思路&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;从本质上减少片元以及片元相关的计算&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;降低分辨率（动态分辨率）&lt;/li&gt;
&lt;li&gt;减少全屏 Pass&lt;/li&gt;
&lt;li&gt;避免 Early-Z 失效（透明物体会导致失效）&lt;/li&gt;
&lt;li&gt;Shader 中避免 if/else 分支，用 step/lerp 替代&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;内存堆栈优化&lt;/h2&gt;
&lt;h3&gt;常见内存分配问题与解决方案&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误：每帧分配字符串
void Update() {
    string name = gameObject.name; // 每次都分配新字符串
}

// ✅ 正确：使用 String.Empty 或缓存
private string tempName = String.Empty;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;优化建议清单：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;序号&lt;/th&gt;
&lt;th&gt;优化项&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;定期清理资源&lt;/td&gt;
&lt;td&gt;Resources.UnloadUnusedAssets()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;字符串拼接&lt;/td&gt;
&lt;td&gt;使用 StringBuilder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;配置数据&lt;/td&gt;
&lt;td&gt;使用 Scriptable Object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;标签比较&lt;/td&gt;
&lt;td&gt;使用 Gameobject.CompareTag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;协程缓存&lt;/td&gt;
&lt;td&gt;避免 new WaitForSeconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;LINQ&lt;/td&gt;
&lt;td&gt;避免使用 LINQ 和正则表达式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;分帧执行&lt;/td&gt;
&lt;td&gt;Update 逻辑改为每 N 帧执行一次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;禁用调试日志&lt;/td&gt;
&lt;td&gt;正式发布时关闭 Debug.Log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;删除空函数&lt;/td&gt;
&lt;td&gt;删除空的 Update/Start 等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;缓存组件&lt;/td&gt;
&lt;td&gt;不要在 Update 里 GetComponent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;字符串&lt;/td&gt;
&lt;td&gt;使用 String.Empty 替代 &quot;&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;数据结构&lt;/td&gt;
&lt;td&gt;单纯传递数据用 struct 而非 class&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;对象池&lt;/td&gt;
&lt;td&gt;频繁创建对象时用缓存池&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;分帧执行示例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;private int interval = 3;

void Update()
{
    if (Time.frameCount % interval == 0)
    {
        ExampleExpensiveFunction();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;禁用调试日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class GameLog : MonoBehaviour
{
    [SerializeField] private bool logEnable = true;

    private void Awake()
    {
        Debug.unityLogger.logEnabled = logEnable;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Shader 参数优化&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 一般做法
material.SetFloat(&quot;_DissolveAmount&quot;, 1);

// ✅ 优化做法：使用 PropertyToID
private int dissolveAmountProperty = Shader.PropertyToID(&quot;_DissolveAmount&quot;);
private MaterialPropertyBlock propertyBlock;

void Update()
{
    targetRenderer.GetPropertyBlock(propertyBlock);
    propertyBlock.SetFloat(dissolveAmountProperty, 1);
    targetRenderer.SetPropertyBlock(propertyBlock);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CPU-Slicing&lt;/h3&gt;
&lt;p&gt;将一些脚本的 Update 逻辑放在一个统一的 Update 管理器，分帧执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 第一帧执行 A 脚本 Update
// 第二帧执行 B 脚本 Update
// ...
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;贴图与物理优化&lt;/h2&gt;
&lt;h3&gt;贴图优化&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;XR 一体机建议贴图 Max Size 设为 1024&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;Mipmap 设置&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;推荐值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MipMap&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;td&gt;远处使用低分辨率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filter Mode&lt;/td&gt;
&lt;td&gt;Trilinear&lt;/td&gt;
&lt;td&gt;在 mip 级别间平滑过渡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MipMap Filter&lt;/td&gt;
&lt;td&gt;Kaiser&lt;/td&gt;
&lt;td&gt;过滤更清晰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aniso Level&lt;/td&gt;
&lt;td&gt;4~8&lt;/td&gt;
&lt;td&gt;各项异性，角度越大越清晰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrap Mode&lt;/td&gt;
&lt;td&gt;Repeat&lt;/td&gt;
&lt;td&gt;重复模式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;VR 头显推荐配置&amp;lt;/span&amp;gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MipMap: ON&lt;/li&gt;
&lt;li&gt;Filter Mode: Trilinear&lt;/li&gt;
&lt;li&gt;MipMap Filter: Kaiser&lt;/li&gt;
&lt;li&gt;Aniso Level: 4~8&lt;/li&gt;
&lt;li&gt;Wrap Mode: Repeat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;UI 贴图设置&lt;/strong&gt;：Clamp + 不需要 MipMap&lt;/p&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：开启 Mipmap 的贴图会多占用 33% 显存。&lt;/p&gt;
&lt;h3&gt;物理优化&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优化项&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;减少 MeshCollider&lt;/td&gt;
&lt;td&gt;少用复杂网格碰撞体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除无用碰撞体&lt;/td&gt;
&lt;td&gt;删除没用的碰撞器和刚体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prebake Collision Meshes&lt;/td&gt;
&lt;td&gt;Player &amp;gt; Optimation 勾选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse Collision Callbacks&lt;/td&gt;
&lt;td&gt;Project Settings &amp;gt; Physics 勾选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;关闭可读写&lt;/td&gt;
&lt;td&gt;模型导入设置中关闭 Read/Write&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;UI 优化深度解析&lt;/h2&gt;
&lt;h3&gt;Canvas 重建机制&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Canvas 的本质作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收集子节点的 UI 元素（Graphic）信息&lt;/li&gt;
&lt;li&gt;处理渲染顺序（Sorting、Camera、RenderMode）&lt;/li&gt;
&lt;li&gt;管理重建，监听子物体的变动&lt;/li&gt;
&lt;li&gt;批量生成 UI 顶点网格、更新材质信息&lt;/li&gt;
&lt;li&gt;最终在渲染阶段合批并提交给 GPU&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;核心问题&amp;lt;/span&amp;gt;：一个 Canvas 统一管理它下面所有子 UI 元素，若更新其中一个子元素会让整个 Canvas 重建。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;导致 Canvas 重建的操作：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改 RectTransform（位置、尺寸、角度）&lt;/li&gt;
&lt;li&gt;动态布局变化（LayoutGroup、ContentSizeFitter）&lt;/li&gt;
&lt;li&gt;频繁改变父子关系&lt;/li&gt;
&lt;li&gt;透明度变化&lt;/li&gt;
&lt;li&gt;修改材质&lt;/li&gt;
&lt;li&gt;脚本修改 Shader&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;动静分离策略&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;大 Canvas&lt;/td&gt;
&lt;td&gt;500 个 UI + 血条 + 按钮... → 血条变化 → 重建 500 个 UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分离 Canvas&lt;/td&gt;
&lt;td&gt;450 个静态 UI + 小 Canvas(50 个动态血条) → 血条变化只重建 50 个&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：Canvas 不是越大越好，合并后可能产生更多 DrawCall。&lt;/p&gt;
&lt;h3&gt;UI 优化清单&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;不需要交互的取消勾选 Raycast target&lt;/li&gt;
&lt;li&gt;移除默认的 Graphic caster&lt;/li&gt;
&lt;li&gt;手动指定 Canvas 的 event camera&lt;/li&gt;
&lt;li&gt;少用自适应布局组件&lt;/li&gt;
&lt;li&gt;使用 SpriteAtlas 图集&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;canvas.enabled = false&lt;/code&gt; 替代 &lt;code&gt;SetActive&lt;/code&gt;（避免 UI 重建）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免用 Animator 控制 UI&lt;/strong&gt;，优先使用：
&lt;ul&gt;
&lt;li&gt;DoTween 插件做缓动效果&lt;/li&gt;
&lt;li&gt;协程或 Lerp 实现插值动画&lt;/li&gt;
&lt;li&gt;Shader 实现动态效果（进度条流光、动态血条等）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;音频优化&lt;/h2&gt;
&lt;h3&gt;音频加载类型选择&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Load Type&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decompress On Load&lt;/td&gt;
&lt;td&gt;加载即解压，占用内存多&lt;/td&gt;
&lt;td&gt;短音效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compressed In Memory&lt;/td&gt;
&lt;td&gt;内存中压缩，占 CPU&lt;/td&gt;
&lt;td&gt;中等长度音效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Streaming&lt;/td&gt;
&lt;td&gt;边读边播，占用内存最少&lt;/td&gt;
&lt;td&gt;BGM 等长音频&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;音频优化建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preload Audio Data&lt;/strong&gt;：短音效勾选，长音效关闭&lt;/li&gt;
&lt;li&gt;使用 &lt;strong&gt;WAV 格式&lt;/strong&gt;（未压缩，解码快）&lt;/li&gt;
&lt;li&gt;推荐压缩格式：&lt;strong&gt;OGG/MP3&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;音频压缩可节省存储空间&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结：性能优化检查清单&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优化领域&lt;/th&gt;
&lt;th&gt;核心要点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;渲染批次&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;减少 Draw Call，根据管线选择合适的批次技术&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GPU 优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;减少 Overdraw，使用遮挡剔除，优化 Shader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;内存带宽&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;使用 Mipmap，压缩纹理格式，减少不必要的数据传输&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;网格优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LOD，减少三角面，使用合适的公告牌技术&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;内存堆栈&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;减少堆分配，缓存组件，使用对象池，分帧执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI 优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;动静分离，避免 Canvas 重建，使用图集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;音频优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;选择合适的 Load Type，使用压缩格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;贴图优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;合理分辨率，启用 Mipmap，选择压缩格式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;物理优化&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;简化碰撞体，启用回调复用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;工具建议&amp;lt;/span&amp;gt;：使用 Unity Profiler 和 Frame Debugger 定期检查性能瓶颈，针对性优化。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/Batch.html&quot;&gt;Unity 官方：渲染批次文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/GPUInstancing.html&quot;&gt;Unity 官方：GPU Instancing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/2023.2/Manual/srp-batcher.html&quot;&gt;Unity 官方：SRP Batcher&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.unity.com/tutorial/optimizing-unity-ui&quot;&gt;Unity UI 优化指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://unity.com/cn/how-to/unity-ui-optimization-tips&quot;&gt;Unity UI 优化技巧&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整合自 Unity XR 训练营学习资料，结合 PDF 与 Word 两份文档编写。如有疑问欢迎交流！&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity XR 多人联机注意事项与故障排查</title><link>https://gzhblog.cn/posts/2026-02/unity-xr%E5%A4%9A%E4%BA%BA%E8%81%94%E6%9C%BA%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E4%B8%8E%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/unity-xr%E5%A4%9A%E4%BA%BA%E8%81%94%E6%9C%BA%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E4%B8%8E%E6%95%85%E9%9A%9C%E6%8E%92%E6%9F%A5/</guid><description>详细讲解Unity XR多人联机开发中的注意事项，包括IP配置、安卓打包设置、网络权限、以及常见问题的解决方案。</description><pubDate>Tue, 10 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;XR 多人联机开发比普通 PC 多人联机更复杂，涉及网络配置、安卓打包、权限设置等问题。本文基于 Unity XR 训练营资料，总结 XR 多人联机开发的注意事项与故障排查方法。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;IP 地址配置&lt;/h2&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;重要&amp;lt;/span&amp;gt;：复制 IP 地址时检查前面和后面有没有多复制了空格，特别是后面不能带有空格。&lt;/p&gt;
&lt;h3&gt;查看电脑 IP&lt;/h3&gt;
&lt;p&gt;Windows 系统：&lt;strong&gt;Win + R&lt;/strong&gt; → 输入 &lt;strong&gt;cmd&lt;/strong&gt; → 输入 &lt;strong&gt;ipconfig&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;本地网络测试&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;检查项&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unity Transport Address&lt;/td&gt;
&lt;td&gt;替换为电脑的 IP 地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allow Remote Connections&lt;/td&gt;
&lt;td&gt;必须勾选（容易遗漏）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Wifi 设置&lt;/h2&gt;
&lt;p&gt;使用本地网络测试时，最好把 &lt;strong&gt;Wifi 设为专用网络&lt;/strong&gt;。公用网络有时候容易连不上。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;手机作为服务端&lt;/h2&gt;
&lt;h3&gt;网络知识&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;网络类型&lt;/th&gt;
&lt;th&gt;IP 特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wi-Fi 私有网络&lt;/td&gt;
&lt;td&gt;通常是 192.x.x.x 私有 IP 地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;移动数据（4G/5G）&lt;/td&gt;
&lt;td&gt;公共 IP 地址&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;手机开热点注意事项&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;手机（移动数据）→ 开启热点 → 电脑连接
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;电脑获得的 192.x.x.x IP 是&lt;strong&gt;电脑的私有 IP&lt;/strong&gt;，不是手机的&lt;/li&gt;
&lt;li&gt;手机作为服务端时，需要在手机的 Wi-Fi 设置里查看&lt;strong&gt;路由器为手机分配的 IP 地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;两个设备的 IP 可能不同，但都在同一个局域网内&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;XR 安卓打包设置&lt;/h2&gt;
&lt;h3&gt;降低 Android API Level&lt;/h3&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;问题&amp;lt;/span&amp;gt;：Quest 开发需要 Android 12（API Level 32），但某些手机（如华为 P30 Pro）打包会报错&quot;解析包时出现错误&quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决&lt;/strong&gt;：把 &lt;strong&gt;Minimum API Level&lt;/strong&gt; 调低。&lt;/p&gt;
&lt;h3&gt;关闭 XR 插件&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;XR Plug-in Management&lt;/strong&gt; 的安卓端&lt;strong&gt;取消勾选&lt;/strong&gt; XR 的底层插件。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;安卓端网络权限设置&lt;/h2&gt;
&lt;h3&gt;AndroidManifest 配置&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;Assets/Plugins/Android/AndroidManifest.xml&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;uses-permission android:name=&quot;android.permission.INTERNET&quot; /&amp;gt;
&amp;lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Project Settings 配置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Edit &amp;gt; Project Settings &amp;gt; Player &amp;gt; Android&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Internet Access&lt;/strong&gt; 设为 &lt;strong&gt;Require&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;本地测试关闭防火墙&lt;/h2&gt;
&lt;p&gt;测试本地联机时，&lt;strong&gt;关闭电脑防火墙&lt;/strong&gt;或添加防火墙例外规则。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;故障排查手册&lt;/h2&gt;
&lt;h3&gt;1. 服务端 IP 地址是否正确&lt;/h3&gt;
&lt;p&gt;检查 Windows 的 ipconfig 查询到的 IP 是否正确配置在 Unity Transport 的 Address 中。&lt;/p&gt;
&lt;h3&gt;2. Quest 头显网络是否已连接&lt;/h3&gt;
&lt;p&gt;确保头显已连接 Wi-Fi 并且与电脑在同一局域网内。&lt;/p&gt;
&lt;h3&gt;3. 打包程序是否误设为 Server/Host&lt;/h3&gt;
&lt;p&gt;如果电脑作为服务端，打包的程序需要&lt;strong&gt;启动 Client&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;4. 是否勾选允许远程连接&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;Unity Transport&lt;/strong&gt; 脚本：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Address&lt;/strong&gt; 填入电脑 IP（无多余空格）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allow Remote Connections&lt;/strong&gt; 必须勾选&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Wi-Fi 是否设为专用网络&lt;/h3&gt;
&lt;p&gt;公用网络可能阻止局域网连接。&lt;/p&gt;
&lt;h3&gt;6. 安卓端网络权限&lt;/h3&gt;
&lt;p&gt;检查清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] AndroidManifest 添加 INTERNET 权限&lt;/li&gt;
&lt;li&gt;[ ] AndroidManifest 添加 ACCESS_NETWORK_STATE 权限&lt;/li&gt;
&lt;li&gt;[ ] Project Settings 的 Internet Access 设为 Require&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7. 防火墙是否关闭&lt;/h3&gt;
&lt;p&gt;尝试关闭防火墙进行测试。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;常见错误与解决方案&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;解决方案&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;无法连接&lt;/td&gt;
&lt;td&gt;检查 IP 是否正确、防火墙是否关闭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;打包安装失败&lt;/td&gt;
&lt;td&gt;降低 Minimum API Level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;权限弹窗没出现&lt;/td&gt;
&lt;td&gt;检查 OVRManager 的 Permission Request On Startup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;画面不同步&lt;/td&gt;
&lt;td&gt;检查 NetworkVariable 的 Server 写权限&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟高&lt;/td&gt;
&lt;td&gt;使用有线网络而非 Wi-Fi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;MR 多人联机特殊注意&lt;/h2&gt;
&lt;h3&gt;空间对齐&lt;/h3&gt;
&lt;p&gt;MR 多人联机需要&lt;strong&gt;空间对齐&lt;/strong&gt;，确保多个玩家的虚拟坐标系与现实空间对应。&lt;/p&gt;
&lt;h3&gt;权威判定&lt;/h3&gt;
&lt;p&gt;所有影响游戏结果的逻辑必须在 &lt;strong&gt;Server 端&lt;/strong&gt; 执行，Client 只负责申请和显示。&lt;/p&gt;
&lt;h3&gt;输入同步&lt;/h3&gt;
&lt;p&gt;XR 输入（头显位置、手柄控制器）是实时流，需要持续同步到远端 Player。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;XR 多人联机注意事项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IP 配置&lt;/strong&gt;：Address 无空格、勾选 Allow Remote Connections&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络设置&lt;/strong&gt;：专用 Wi-Fi、同一局域网、防火墙关闭&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安卓打包&lt;/strong&gt;：降低 API Level、关闭 XR 插件、配置网络权限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故障排查&lt;/strong&gt;：按清单逐项检查（IP → 网络 → 权限 → 防火墙）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;调试建议&amp;lt;/span&amp;gt;：先用 PC 双开测试，确认功能正常后再打包到安卓设备。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity MR二维码识别开发实战指南</title><link>https://gzhblog.cn/posts/2026-02/unity-mr%E4%BA%8C%E7%BB%B4%E7%A0%81%E8%AF%86%E5%88%AB%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/unity-mr%E4%BA%8C%E7%BB%B4%E7%A0%81%E8%AF%86%E5%88%AB%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</guid><description>详细讲解Unity MR开发中如何实现二维码识别追踪，涵盖Meta摄像头API配置、ZXing插件集成、稳定识别框架等内容。</description><pubDate>Mon, 09 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;二维码识别是 MR 开发中的重要技术，可以用于室内导航、物体定位、多人协作等场景。本文基于 Unity XR 训练营资料，详细讲解如何在 Meta Quest 设备上实现二维码识别与追踪功能。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;摄像头 API 基础&lt;/h2&gt;
&lt;h3&gt;前置要求&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;要求&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quest 系统版本&lt;/td&gt;
&lt;td&gt;v74 及以上（推荐 v81+）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设备&lt;/td&gt;
&lt;td&gt;Quest 3 或 Quest 3s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分辨率&lt;/td&gt;
&lt;td&gt;最大 1280x1280（v83+）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;图像延迟&lt;/td&gt;
&lt;td&gt;40-60 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：从 v76 开始，使用摄像头 API 的应用已支持上架 Quest 商店。&lt;/p&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;限制&amp;lt;/span&amp;gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目前不适合处理移动对象&lt;/li&gt;
&lt;li&gt;不适合识别小的或远的图像和文本&lt;/li&gt;
&lt;li&gt;SDK 目前无法在串流模式下生效，需要打包到头显测试&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;官方样例工程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;PassthroughCameraApiSamples&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CameraViewer：将摄像头画面渲染到 RawImage&lt;/li&gt;
&lt;li&gt;CameraToWorld：相机坐标转世界坐标&lt;/li&gt;
&lt;li&gt;BrightnessEstimation：环境亮度估计&lt;/li&gt;
&lt;li&gt;MultiObjectDetection：AI 语义物体识别（Unity Sentis）&lt;/li&gt;
&lt;li&gt;ShaderSample：虚拟水面倒影特效&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Quest Vision Kit&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Color Picker：吸取现实颜色转虚拟画笔&lt;/li&gt;
&lt;li&gt;Object Detection：AI 语义识别&lt;/li&gt;
&lt;li&gt;QR Code Tracking：二维码识别追踪（ZXing）&lt;/li&gt;
&lt;li&gt;Shader Samples：毛玻璃特效&lt;/li&gt;
&lt;li&gt;OpenAI Vision：基于 GPT 的视觉助手&lt;/li&gt;
&lt;li&gt;WebRTC Streaming：网络串流摄像头画面&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;核心脚本：PassthroughCameraAccess&lt;/h2&gt;
&lt;p&gt;Meta XR SDK V81+ 将摄像头 API 集成到了 MRUK 开发包中，使用统一的 &lt;strong&gt;PassthroughCameraAccess&lt;/strong&gt; 脚本。&lt;/p&gt;
&lt;h3&gt;配置步骤&lt;/h3&gt;
&lt;h4&gt;步骤一：添加脚本&lt;/h4&gt;
&lt;p&gt;通过 &lt;strong&gt;Building Block&lt;/strong&gt; 快速添加：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;菜单栏 &lt;strong&gt;Meta &amp;gt; Tools &amp;gt; Building Blocks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Passthrough&lt;/strong&gt; 标签&lt;/li&gt;
&lt;li&gt;拖入 &lt;strong&gt;Passthrough Camera Access&lt;/strong&gt; 模块&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;会自动添加：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[BuildingBlock] Passthrough Camera Access&lt;/code&gt; — 核心脚本&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[BuildingBlock] Passthrough Camera Visualizer&lt;/code&gt; — 摄像画面渲染 Quad&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;步骤二：添加透视模块（可选）&lt;/h4&gt;
&lt;p&gt;透视场景下使用摄像头 API 是常见场景，但纯 VR 场景也可以使用，根据项目需求决定是否添加。&lt;/p&gt;
&lt;h4&gt;步骤三：环境修复&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Meta XR Tools &amp;gt; Project Setup Tool&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在安卓端和 PC 端点击 &lt;strong&gt;Fix All&lt;/strong&gt; 和 &lt;strong&gt;Apply All&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;主要消除红色警告&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;步骤四：检查 OVRManager&lt;/h4&gt;
&lt;p&gt;在玩家物体（Camera Rig）的 &lt;strong&gt;OVRManager&lt;/strong&gt; 脚本中，确保 &lt;strong&gt;Enable Passthrough Camera Access&lt;/strong&gt; 已勾选。&lt;/p&gt;
&lt;h4&gt;步骤五：请求摄像头权限&lt;/h4&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;重要&amp;lt;/span&amp;gt;：第一次打开程序会弹出权限请求，用户必须点击允许。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法一：勾选权限自动请求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 OVRManager 的 &lt;strong&gt;Permission Request On Startup&lt;/strong&gt; 下勾选：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Passthrough Camera Access&lt;/strong&gt; — 摄像头访问权限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scene&lt;/strong&gt; — 空间数据权限（二维码定位需要）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：如果点击拒绝，后续不会再次弹窗且无法获取画面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法二：代码调用&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using UnityEngine;
using Meta.XR.MRUK;

public class RequestCameraPermissions : MonoBehaviour
{
    private void Awake()
    {
        OVRPermissionsRequester.Request(new[]
        {
            OVRPermissionsRequester.Permission.Scene,
            OVRPermissionsRequester.Permission.PassthroughCameraAccess
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;步骤六：检查 AndroidManifest&lt;/h4&gt;
&lt;p&gt;确保 &lt;code&gt;Assets/Plugins/Android/AndroidManifest.xml&lt;/code&gt; 中包含：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;uses-permission android:name=&quot;com.oculus.permission.HEADSET_CAMERA&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如没有，运行 &lt;strong&gt;Meta &amp;gt; Tools &amp;gt; Update AndroidManifest.xml&lt;/strong&gt; 自动更新。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二维码识别样例&lt;/h2&gt;
&lt;h3&gt;官方工程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Quest Vision Kit&lt;/strong&gt;：https://github.com/xrdevrob/QuestCameraKit&lt;/p&gt;
&lt;h3&gt;配置步骤&lt;/h3&gt;
&lt;h4&gt;1. 下载工程并打开&lt;/h4&gt;
&lt;p&gt;克隆或下载 GitHub 工程，用 Unity 打开。&lt;/p&gt;
&lt;h4&gt;2. 解决报错&lt;/h4&gt;
&lt;p&gt;首次打开可能报错，选择 &lt;strong&gt;Ignore&lt;/strong&gt; 忽略。&lt;/p&gt;
&lt;h4&gt;3. 下载 ZXing 插件&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;下载：https://github.com/micjahn/ZXing.Net/releases/tag/v0.16.10.0&lt;/li&gt;
&lt;li&gt;解压后找到 &lt;code&gt;unity/zxing.unity.dll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;复制到 &lt;code&gt;Assets/Plugins/&lt;/code&gt; 文件夹&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;4. 配置 ZXingDefineSymbolChecker&lt;/h4&gt;
&lt;p&gt;打开脚本，修改 &lt;code&gt;HasZXingDLL&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static bool HasZXingDLL()
{
    var files = Directory.GetFiles(Application.dataPath, &quot;*ZXing.dll&quot;, SearchOption.AllDirectories)
                         .Concat(Directory.GetFiles(Application.dataPath, &quot;*zxing.dll&quot;, SearchOption.AllDirectories))
                         .Concat(Directory.GetFiles(Application.dataPath, &quot;*zxing.unity.dll&quot;, SearchOption.AllDirectories));
    Debug.Log($&quot;Has ZxingDLL:{files.Any()}&quot;);
    return files.Any();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后等待编译完成，自动生成 &lt;strong&gt;ZXING_ENABLED&lt;/strong&gt; 符号。&lt;/p&gt;
&lt;p&gt;如未自动生成，手动在 &lt;strong&gt;Edit &amp;gt; Project Settings &amp;gt; Player &amp;gt; Other Settings &amp;gt; Scripting Define Symbols&lt;/strong&gt; 中添加。&lt;/p&gt;
&lt;h4&gt;5. 配置权限&lt;/h4&gt;
&lt;p&gt;在 &lt;strong&gt;OVRManager&lt;/strong&gt; 的 &lt;strong&gt;Permission Requests On Startup&lt;/strong&gt; 下勾选 &lt;strong&gt;Passthrough Camera Access&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;6. 打包测试&lt;/h4&gt;
&lt;p&gt;确保头显系统版本为 v81+，打包测试。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;移植到自己的项目&lt;/h2&gt;
&lt;h3&gt;Unity 6 之前的版本&lt;/h3&gt;
&lt;p&gt;XR Plug-in Management 安卓端勾选 &lt;strong&gt;Oculus&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Unity 6 及以上&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;XR Plug-in Management 安卓端勾选 &lt;strong&gt;OpenXR&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;导入包：&lt;code&gt;com.unity.xr.meta-openxr&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;移植文件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;zxing.unity.dll&lt;/code&gt; → &lt;code&gt;Assets/Plugins/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Assets/Samples/QRCodeTracking&lt;/code&gt; → 自己的项目&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Assets/Samples/Common&lt;/code&gt; → 自己的项目&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] ZXING_ENABLED 符号已配置&lt;/li&gt;
&lt;li&gt;[ ] OVRManager 的 Passthrough Camera Access 已勾选&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;二维码稳定识别框架&lt;/h2&gt;
&lt;p&gt;样例工程的缺陷：识别位置稳定，但旋转角度不稳定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决思路&lt;/strong&gt;：多次采样位姿数据，进行平均滤波。&lt;/p&gt;
&lt;h3&gt;框架集成包&lt;/h3&gt;
&lt;p&gt;核心物体：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;物体&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QrCodeStasticDisplayManager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;稳定识别核心算法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CustomQRCodeMarkerSpawner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;指定二维码和生成的物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ResetStableController&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;处理重定位后重新扫码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;次要物体（空间锚点相关）：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;脚本&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QRCodeTrackEvent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提供事件，OnImageStabilized 为稳定后调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CustomQRCodeMarkerControllerBase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;继承并重写 ShowObjByGivenPose&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实际项目需求&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;指定二维码识别&lt;/strong&gt;：只识别特定二维码&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多二维码分组&lt;/strong&gt;：场地内多个二维码，同组只识别一个&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重定位恢复&lt;/strong&gt;：重定位后重新扫码恢复定位&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;空间锚点保存&lt;/strong&gt;：借助空间锚点固定虚拟场景&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭扫码节省性能&lt;/strong&gt;：识别成功后关闭扫码功能&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二维码坐标轴朝向&lt;/h3&gt;
&lt;p&gt;MR 大空间扫码通常是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;竖直码&lt;/strong&gt;：贴在墙上，Z 轴垂直于二维码平面&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;水平码&lt;/strong&gt;：贴在桌上，Y 轴朝上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;配置规则&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;竖直码：虚拟场景 Z 轴与二维码法线朝向一致&lt;/li&gt;
&lt;li&gt;水平码：虚拟场景 Z 轴与二维码 Y 轴一致&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;权限弹窗没出现&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;检查 OVRManager 的 Permission Request On Startup 是否勾选&lt;/li&gt;
&lt;li&gt;使用代码手动请求权限&lt;/li&gt;
&lt;li&gt;在头显 &lt;strong&gt;设置 &amp;gt; 隐私与安全 &amp;gt; 应用权限 &amp;gt; 头戴设备摄像头&lt;/strong&gt; 手动开启&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;摄像头画面显示白色&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;检查 AndroidManifest 是否有摄像头权限&lt;/li&gt;
&lt;li&gt;运行 Project Setup Tool 的 Fix All&lt;/li&gt;
&lt;li&gt;检查 OVRManager 的 Enable Passthrough Camera Access 是否勾选&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二维码识别不稳定&lt;/h3&gt;
&lt;p&gt;使用稳定识别框架，进行多次采样滤波。&lt;/p&gt;
&lt;h3&gt;空间锚点保存失败&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;需要设备账号开启双重身份认证&lt;/li&gt;
&lt;li&gt;手机 APP 需要开启开发者模式&lt;/li&gt;
&lt;li&gt;代激活/租的设备可能无法使用空间锚点&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;二维码识别 MR 开发流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境配置&lt;/strong&gt;：安装 MRUK → 配置 PassthroughCameraAccess → 请求权限&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集成 ZXing&lt;/strong&gt;：下载插件 → 配置符号 → 打包测试&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;稳定识别&lt;/strong&gt;：使用滤波框架 → 处理重定位 → 空间锚点辅助&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：识别成功后关闭扫码&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心建议&amp;lt;/span&amp;gt;：从官方样例工程开始学习，逐步移植到自己的项目。二维码识别涉及坐标转换、位姿滤波等多个环节，需要多调试理解原理。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xrdevrob/QuestCameraKit&quot;&gt;Quest Vision Kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/micjahn/ZXing.Net&quot;&gt;ZXing.Net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.oculus.com/documentation/unity/&quot;&gt;Meta XR SDK 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料，结合 Meta 官方样例与稳定识别框架实战经验。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity MR大空间部署与同步系统开发指南</title><link>https://gzhblog.cn/posts/2026-02/unity-mr%E5%A4%A7%E7%A9%BA%E9%97%B4%E9%83%A8%E7%BD%B2%E4%B8%8E%E5%90%8C%E6%AD%A5%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-02/unity-mr%E5%A4%A7%E7%A9%BA%E9%97%B4%E9%83%A8%E7%BD%B2%E4%B8%8E%E5%90%8C%E6%AD%A5%E7%B3%BB%E7%BB%9F%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</guid><description>详细讲解Unity MR大空间部署的核心技术，包括二维码空间定位、角色对齐、Quest-PC同步变换系统等内容。</description><pubDate>Sun, 01 Feb 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;MR 大空间部署是 XR 开发中的高级课题，需要解决空间对齐、角色对齐、跨设备同步等问题。本文基于 Unity XR 训练营资料，详细讲解 MR 大空间部署的核心技术与 Quest-PC 同步系统。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;空间对齐基础&lt;/h2&gt;
&lt;h3&gt;什么是空间对齐？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;空间对齐&lt;/strong&gt;：让所有人看到的虚拟世界在现实中的同一个位置。&lt;/p&gt;
&lt;h3&gt;为什么需要空间对齐？&lt;/h3&gt;
&lt;p&gt;假设同一个现实空间有两个玩家，如果不对齐坐标系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;玩家 A 在自己的 (0, 0, 1) 发射子弹&lt;/li&gt;
&lt;li&gt;同步到玩家 B 的世界后，子弹位置会跑偏&lt;/li&gt;
&lt;li&gt;导致 A 和 B 看到的虚拟物体在实际位置上对不上&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;核心思想&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;先在现实世界建立&quot;统一的空间坐标系&quot;，再在这个坐标系里做联机同步。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;场景结构&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;现实世界（二维码）
   ↓
Root（所有虚拟世界的根节点）
   ├── RootHead
   ├── RootLeftHand
   ├── RootRightHand
   ├── NetworkPlayerRoot（网络玩家的父物体）
   └── 所有虚拟物体
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;第一步：空间对齐&lt;/h2&gt;
&lt;h3&gt;原理&lt;/h3&gt;
&lt;p&gt;不管戴上头显时的初始站位在哪，&lt;strong&gt;识别到二维码后都会将 Root 重置到二维码的位置和朝向&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;相当于把虚拟世界转换到了一个以二维码为原点的&quot;现实坐标系&quot;下。&lt;/p&gt;
&lt;h3&gt;效果&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;所有人扫同个二维码，就会把所有人的 Root 对齐到固定不动的现实坐标系&lt;/li&gt;
&lt;li&gt;因为虚拟世界会作为 Root 的子物体，所以每个人眼中的虚拟世界在现实当中位于同个位置&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;可替代方案&lt;/h3&gt;
&lt;p&gt;二维码可以替换为其他能被头显追踪到的固定物体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;空间锚点&lt;/li&gt;
&lt;li&gt;Marker&lt;/li&gt;
&lt;li&gt;任何能被头显获取到坐标的物体&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;第二步：角色对齐&lt;/h2&gt;
&lt;h3&gt;目的&lt;/h3&gt;
&lt;p&gt;对齐网络空间。让每个玩家的网络虚拟化身对齐到现实的身体上。&lt;/p&gt;
&lt;h3&gt;表面上&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;虚拟头和现实头进行对齐&lt;/li&gt;
&lt;li&gt;虚拟手和现实手进行对齐&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实际上&lt;/h3&gt;
&lt;p&gt;对齐网络空间，确保多人联机时每个玩家看到的位置一致。&lt;/p&gt;
&lt;h3&gt;LocalInput（本地输入映射）&lt;/h3&gt;
&lt;p&gt;负责把头显的真实追踪数据映射到 Root 坐标系下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;二维码对齐的是 Root
头和手只是往这个 Root 里填数据
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;NetworkPlayer（网络玩家物体）&lt;/h3&gt;
&lt;p&gt;让所有网络玩家物体生活在同一个 Root 空间中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设置为 Root 子物体&lt;/li&gt;
&lt;li&gt;代码中在 Root 下新建 NetworkPlayerRoot&lt;/li&gt;
&lt;li&gt;把网络玩家物体的原点重置到 Root 空间的原点（与二维码原点一致）&lt;/li&gt;
&lt;li&gt;之后网络玩家物体的坐标就是相对于 Root 的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：Netcode 中想把一个网络物体作为另一个物体的子物体，另一个物体也必须是网络物体（有 NetworkObject），且需要先通过网络加载出来才能作为其他网络物体的父物体。&lt;/p&gt;
&lt;h3&gt;同步原理&lt;/h3&gt;
&lt;p&gt;因为之前记录过现实头显位置的 RootHead 等物体也作为了 Root 的子物体，和重置过的网络玩家在同一个空间下，所以直接通过&lt;strong&gt;相对于 Root 的本地坐标&lt;/strong&gt;来将网络玩家物体匹配到现实中的玩家身体上。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;开发注意事项&lt;/h2&gt;
&lt;h3&gt;写代码前考虑两点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;这件事是不是由服务端说了算？&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;这段代码是不是我这个客户端能控制的？&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Quest-PC 物体同步系统&lt;/h2&gt;
&lt;h3&gt;问题背景&lt;/h3&gt;
&lt;p&gt;识别二维码成功后，需要在头显里调整虚拟物体的位置让虚拟物体和现实空间产生匹配。但是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个调整后的位置没法同步到 Unity 编辑器里&lt;/li&gt;
&lt;li&gt;只有 Unity 编辑器的位置调整过了，下次打包扫码得到的位置才是正确的&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;传统方式&lt;/h3&gt;
&lt;p&gt;头显端调完后不要关闭程序，手动记录 Immersive Debugger 调试窗口的 Inspector 面板记录的调整后模型的 localPosition、localRotation、localScale，再手动在 Unity 编辑器里一个个修改。&lt;/p&gt;
&lt;h3&gt;同步系统方案&lt;/h3&gt;
&lt;p&gt;通过&lt;strong&gt;网络进行同步&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;电脑编辑器（Server）
        ↓ 连接
头显（Client）
        ↓
把头显中修改的物体 Transform 数据传回电脑
        ↓
数据保存在本地
        ↓
退出运行后，把数据应用回编辑器中的物体
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;系统使用&lt;/h3&gt;
&lt;h4&gt;样例场景配置&lt;/h4&gt;
&lt;p&gt;&quot;Quest PC调试同步系统&quot; 物体为核心物体：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;运行设备&lt;/th&gt;
&lt;th&gt;Quest 物体&lt;/th&gt;
&lt;th&gt;PC 物体&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;头显&lt;/td&gt;
&lt;td&gt;激活&lt;/td&gt;
&lt;td&gt;隐藏&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;电脑编辑器&lt;/td&gt;
&lt;td&gt;隐藏&lt;/td&gt;
&lt;td&gt;激活&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;Unity 编辑器拓展面板&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Windows &amp;gt; Tools &amp;gt; Quest 同步变换工具&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在&lt;strong&gt;服务器配置&lt;/strong&gt;处引用场景中的 PC 物体&lt;/li&gt;
&lt;li&gt;运行 Unity 编辑器启动同步服务&lt;/li&gt;
&lt;li&gt;手动指定根物体会自动引用 PC 物体上引用的根物体&lt;/li&gt;
&lt;li&gt;连接成功后 Quest 和头显都会显示已连接的相关信息&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;同步流程&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;头显端&lt;/strong&gt;：调好位置后，在面板上点击&quot;手动保存当前变换&quot;&lt;/li&gt;
&lt;li&gt;将调整的数据（根物体所有嵌套子物体的 Transform）保存到本地&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;退出运行模式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击&quot;加载变换数据&quot;，把之前同步的数据加载到根物体的所有嵌套子物体上&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;提示&amp;lt;/span&amp;gt;：加载变换后记得&quot;停止同步服务&quot;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;二维码空间定位系统&lt;/h2&gt;
&lt;h3&gt;核心组件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Root&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;虚拟世界根节点，与二维码位置对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LocalInput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;本地输入映射到 Root 坐标系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NetworkPlayerRoot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;网络玩家父物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QRCodeStasticDisplayManager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;稳定二维码识别&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;同步流程&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;二维码识别成功
   ↓
Root 位置和朝向与二维码一致
   ↓
LocalInput 将头显追踪数据映射到 Root 坐标系
   ↓
NetworkPlayer 同步到所有客户端
   ↓
所有玩家看到相同的虚拟世界
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;二维码识别不稳定&lt;/h3&gt;
&lt;p&gt;使用&lt;strong&gt;稳定识别框架&lt;/strong&gt;，进行多次采样滤波。&lt;/p&gt;
&lt;h3&gt;网络物体无法作为子物体&lt;/h3&gt;
&lt;p&gt;确保父物体有 &lt;strong&gt;NetworkObject&lt;/strong&gt; 组件，且先通过网络加载完成。&lt;/p&gt;
&lt;h3&gt;位置同步有偏差&lt;/h3&gt;
&lt;p&gt;检查 LocalInput 的映射是否正确，确保所有物体都在 Root 坐标系下。&lt;/p&gt;
&lt;h3&gt;同步数据丢失&lt;/h3&gt;
&lt;p&gt;确保保存后不要提前退出运行模式，按顺序操作：保存 → 退出运行 → 加载数据。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;MR 大空间部署核心要点：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;步骤&lt;/th&gt;
&lt;th&gt;内容&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;空间对齐&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;扫二维码将 Root 对齐到现实坐标系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;角色对齐&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LocalInput 映射 + NetworkPlayer 在同一 Root 空间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;同步系统&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quest-PC 网络同步变换数据&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心原则&amp;lt;/span&amp;gt;：先建立统一现实坐标系，再做联机同步。所有物体都在 Root 坐标系下工作。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/Packages/com.unity.netcode@latest/&quot;&gt;Unity Netcode for GameObjects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.oculus.com/documentation/unity/&quot;&gt;Meta XR SDK 文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料，结合大空间部署与同步系统实战经验。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity Netcode for GameObjects 多人联机开发基础</title><link>https://gzhblog.cn/posts/2026-01/unity-%E5%A4%9A%E4%BA%BA%E8%81%94%E6%9C%BA%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-01/unity-%E5%A4%9A%E4%BA%BA%E8%81%94%E6%9C%BA%E5%BC%80%E5%8F%91%E5%9F%BA%E7%A1%80/</guid><description>详细讲解Unity多人联机开发的核心概念，包括权威同步、Client-Server-Host架构、NetworkVariable与RPC的使用方法。</description><pubDate>Sun, 25 Jan 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;多人联机是 XR 开发中的重要方向，可以让多个用户在同一个 MR 空间中协作。本文基于 Unity XR 训练营资料，系统讲解 Unity 多人联机开发的核心概念与 Netcode for GameObjects 的使用方法。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;核心概念：空间对齐与权威&lt;/h2&gt;
&lt;h3&gt;空间对齐&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;为什么需要对齐？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设同一个现实空间有两个玩家，玩家在 Unity 中的初始坐标都是 (0, 0, 0)。但玩家戴上头显时，头显在现实中的初始位置和朝向决定了玩家的 Unity 原点位置和朝向。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;后果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;玩家 A 在自己的 (0, 0, 1) 发射子弹&lt;/td&gt;
&lt;td&gt;如果同步到玩家 B 的世界，B 看到的子弹在实际位置上对不上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;需要统一的坐标系&lt;/td&gt;
&lt;td&gt;才能保证 A 和 B 看到的东西在现实中一致&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;权威（Authority）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;权威&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;谁拥有最终决定权&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;服务端&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;就是权威，最终由它说了算&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;权威同步&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;同步权威判定后的结果&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Client、Server、Host 区别&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client（客户端）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;玩家本人，作为当事人操作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Server（服务端）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;规则本身，作为裁判判定结果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Host&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server + Client 在同一台设备，一边当玩家一边当裁判&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;Host 写逻辑需要区分 Server/Client&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;h3&gt;权威规则示例&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;错误做法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Client 自己改血量、位置、状态 ✗&lt;/li&gt;
&lt;li&gt;Client 自己判定命中 ✗&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;正确流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client（玩家）发射子弹
↓
Server 判断子弹有没有命中
↓
得出结果：命中/未命中
↓
把结果同步给所有人
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;子弹生成流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client 扣下扳机
↓
Client 向 Server 申请开枪
↓
Server 验证：冷却时间？弹药？状态？
↓
Server 实例化子弹物体
↓
Server 把子弹同步给 Client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心原则&amp;lt;/span&amp;gt;：写代码前想好这段代码是在当玩家，还是当裁判。凡是能够影响别人结果的，都是由 Server 进行生成和判定。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;联机框架选择&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;框架&lt;/th&gt;
&lt;th&gt;费用&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unity Netcode for Gameobjects（NGO）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;免费&lt;/td&gt;
&lt;td&gt;Unity 官方维护，学习资料多，可自建服务器或使用云服务器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mirror&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;免费，开源&lt;/td&gt;
&lt;td&gt;写法与 NGO 相似，教程少，只能自建服务器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Photon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;商业&lt;/td&gt;
&lt;td&gt;免费版最大 20 人同时在线，只能用官方云服务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;Netcode for GameObjects 安装配置&lt;/h2&gt;
&lt;h3&gt;安装&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;Unity Package Manager&lt;/strong&gt; 的 &lt;strong&gt;Unity Registry&lt;/strong&gt; 搜索 &lt;strong&gt;Netcode for Gameobjects&lt;/strong&gt;，进行安装。&lt;/p&gt;
&lt;h3&gt;基础配置&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在场景中新增一个空物体（如 &lt;code&gt;Network Manager&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;在物体上添加 &lt;strong&gt;NetworkManager&lt;/strong&gt; 脚本&lt;/li&gt;
&lt;li&gt;一个场景只能有一个 NetworkManager 脚本&lt;/li&gt;
&lt;li&gt;在 NetworkManager 的 &lt;strong&gt;Network Transport&lt;/strong&gt; 处选择 &lt;strong&gt;Unity Transport&lt;/strong&gt; 作为网络传输层&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;多实例调试工具 ParrelSync&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;下载&lt;/strong&gt;：https://github.com/VeriorPies/ParrelSync&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;下载 unitypackage 并导入 Unity&lt;/li&gt;
&lt;li&gt;点击菜单栏 &lt;strong&gt;ParrelSync &amp;gt; Clone Manager&lt;/strong&gt; 克隆 Unity 工程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Open In New Editor&lt;/strong&gt; 打开&lt;/li&gt;
&lt;li&gt;可在多个相同工程里模拟不同客户端/服务端&lt;/li&gt;
&lt;li&gt;主工程修改后克隆的工程会同步修改&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;常用组件&lt;/h2&gt;
&lt;h3&gt;NetworkManager&lt;/h3&gt;
&lt;p&gt;整个联机世界的开关，负责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启动 Server / Client / Host&lt;/li&gt;
&lt;li&gt;管理玩家连接&lt;/li&gt;
&lt;li&gt;管理 Player Prefab&lt;/li&gt;
&lt;li&gt;管理网络生命周期&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Player Prefab&lt;/h3&gt;
&lt;p&gt;每个客户端玩家在联机世界中代表自己的游戏对象，相当于自己在联机世界的化身。&lt;/p&gt;
&lt;p&gt;在 &lt;strong&gt;NetworkManager&lt;/strong&gt; 组件的 &lt;strong&gt;Player Prefab&lt;/strong&gt; 字段中进行指定。&lt;/p&gt;
&lt;h3&gt;NetworkObject&lt;/h3&gt;
&lt;p&gt;联机世界的身份证，允许物体进入联机世界。&lt;/p&gt;
&lt;h3&gt;NetworkBehaviour&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MonoBehaviour&lt;/td&gt;
&lt;td&gt;单机逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NetworkBehaviour&lt;/td&gt;
&lt;td&gt;联机逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;继承自 NetworkBehaviour 的脚本能够处理联机逻辑。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;NetworkVariable&lt;/h2&gt;
&lt;h3&gt;特点&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;用途&lt;/td&gt;
&lt;td&gt;用于同步状态，无法同步行为&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可同步&lt;/td&gt;
&lt;td&gt;血量、子弹数量、是否死亡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;不可同步&lt;/td&gt;
&lt;td&gt;开枪动作、播放动画&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;工作机制&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;自动同步&lt;/strong&gt;：值改变后，NGO 自动处理网络通信（只有值变化后才同步，不是每帧同步）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;状态同步&lt;/strong&gt;：中途加入的客户端会自动同步服务器的当前状态&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变更回调&lt;/strong&gt;：提供 OnValueChanged 事件，可在值改变时执行自定义逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;代码写法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 权威规则
// Server 写（只能服务端修改数值，修改后所有客户端会自动同步）
// Client 读

public class HealthSystem : NetworkBehaviour
{
    public NetworkVariable&amp;lt;int&amp;gt; health = new NetworkVariable&amp;lt;int&amp;gt;(100);

    // Server 端修改
    public void TakeDamage(int damage)
    {
        health.Value -= damage; // 只有 Server 有权限
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;常见错误&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;错误一：Client 直接改 NetworkVariable&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误：客户端自己执行
health.Value -= 100;

// 后果：客户端没有权利判定结果
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;错误二：用 NetworkVariable 同步瞬时事件&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误：开枪是瞬时事件，不是长期状态
isShooting.Value = true;

// 后果：玩家一直维持开枪状态
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;错误三：每帧修改 NetworkVariable&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ❌ 错误：Update 里不断改血量
void Update()
{
    health.Value = currentHealth;
}

// 后果：性能消耗大
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;XR 输入同步例外&amp;lt;/span&amp;gt;：XR 中需要实时同步头显、手柄输入到远端 Player，这是输入流需要持续更新。&lt;/p&gt;
&lt;h3&gt;IsOwner 含义&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;我有没有资格发请求&lt;/strong&gt;。当前生成的 Player 是不是由我这个客户端控制的。&lt;/p&gt;
&lt;p&gt;例如：客户端 A 先加入，然后客户端 B 加入。A 的世界里生成了代表 B 的 Player，但 A 客户端的这个 B 不归 A 管，所以不会触发 Move。而 B 世界里生成的 B 归自己管，所以一生成就调用 Move。&lt;/p&gt;
&lt;h3&gt;IsOwner 和 IsLocalPlayer 区别&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IsOwner&lt;/td&gt;
&lt;td&gt;当前客户端是否拥有这个 NetworkObject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IsLocalPlayer&lt;/td&gt;
&lt;td&gt;是否是本地玩家&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;RPC（Remote Procedure Call）&lt;/h2&gt;
&lt;p&gt;RPC 是联机世界的对话方式，一般用于&lt;strong&gt;瞬时的动作或事件&lt;/strong&gt;（玩家开枪、播放音效、触发爆炸）。&lt;/p&gt;
&lt;h3&gt;ServerRPC（Client → Server）&lt;/h3&gt;
&lt;p&gt;客户端向服务端发起请求，执行的代码在服务端执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ServerRpc]
void ShootServerRpc()
{
    // 执行在服务端
    Debug.Log(&quot;Server 处理开枪逻辑&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ClientRPC（Server → Client）&lt;/h3&gt;
&lt;p&gt;服务端向客户端发起通知。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ClientRpc]
void ExplodeClientRpc()
{
    // 执行在所有客户端
    PlayExplosionEffect();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;官方新手入门样例&lt;/h2&gt;
&lt;h3&gt;HelloWorldPlayer 与 HelloWorldManager&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;执行流程&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client 按按钮
↓
ServerRpc
↓
Server 决定位置
↓
NetworkVariable 同步结果
↓
所有 Client 更新显示
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;核心要点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;空间对齐&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MR 多人联机需要统一坐标系&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;权威&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server 说了算，Client 只负责申请和显示&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NetworkVariable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;同步状态，Server 写 Client 读&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RPC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;瞬时动作/事件，ServerRpc/ClientRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心原则&amp;lt;/span&amp;gt;：写代码前想好是在当玩家还是当裁判。影响别人结果的逻辑，必须由 Server 执行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/Packages/com.unity.netcode@latest/&quot;&gt;Netcode for GameObjects 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/VeriorPies/ParrelSync&quot;&gt;ParrelSync 多开工具&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity MR透视与传送门开发指南</title><link>https://gzhblog.cn/posts/2026-01/unity-mr%E9%80%8F%E8%A7%86%E4%B8%8E%E4%BC%A0%E9%80%81%E9%97%A8%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-01/unity-mr%E9%80%8F%E8%A7%86%E4%B8%8E%E4%BC%A0%E9%80%81%E9%97%A8%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97/</guid><description>详细讲解Unity MR透视开发的核心技术，包括Passthrough配置、深度测试原理、MR传送门实现（Depth Only Shader与模板测试Stencil两种方案）。</description><pubDate>Thu, 15 Jan 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;MR（混合现实）透视技术让虚拟物体与现实环境融为一体，是当前 XR 开发的热点方向。本文基于 Unity XR 训练营资料，详细讲解 MR 透视开发的核心技术，包括基础配置、深度测试原理、以及 MR 传送门的实现方案。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;透视基础配置&lt;/h2&gt;
&lt;h3&gt;1. 添加 Passthrough 模块&lt;/h3&gt;
&lt;p&gt;在 &lt;strong&gt;Building Blocks&lt;/strong&gt; 中找到 &lt;strong&gt;Passthrough&lt;/strong&gt; 模块，拖到场景中。&lt;/p&gt;
&lt;h3&gt;2. 配置 Camera Rig&lt;/h3&gt;
&lt;p&gt;在玩家物体（CameraRig）上的 &lt;strong&gt;OVRManager&lt;/strong&gt; 脚本中进行配置。&lt;/p&gt;
&lt;h3&gt;3. 配置相机背景&lt;/h3&gt;
&lt;p&gt;在玩家物体的相机（CenterEyeAnchor）中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Camera 组件的 Background Type&lt;/strong&gt; 设为 &lt;strong&gt;Solid Color&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background 颜色&lt;/strong&gt; RGB 设为 &lt;strong&gt;0&lt;/strong&gt;，透明度（A）设为 &lt;strong&gt;0&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 关闭 MR 安全边界&lt;/h3&gt;
&lt;h4&gt;方法一：在 AndroidManifest 中配置&lt;/h4&gt;
&lt;p&gt;在 &lt;code&gt;Assets/Plugins/Android/AndroidManifest.xml&lt;/code&gt; 中添加：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;uses-feature android:name=&quot;com.oculus.feature.BOUNDARYLESS_APP&quot; android:required=&quot;true&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法二：彻底关闭 Quest 安全边界&lt;/h4&gt;
&lt;p&gt;在头显中：&lt;strong&gt;设置 &amp;gt; 开发者 &amp;gt; 实际空间功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：关闭后头显录屏里的现实环境是黑的。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;透视材质&lt;/h2&gt;
&lt;h3&gt;创建透视材质&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;新建一个 &lt;strong&gt;Material&lt;/strong&gt; 材质&lt;/li&gt;
&lt;li&gt;Shader 参数设为 &lt;strong&gt;Oculus/SelectivePassthrough&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Render Queue 规则&lt;/h3&gt;
&lt;p&gt;透视材质的 &lt;strong&gt;Render Queue&lt;/strong&gt; 一般需要 &lt;strong&gt;大于 2000&lt;/strong&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;规则&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unity 不透明物体默认 Render Queue&lt;/td&gt;
&lt;td&gt;2000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;透视物体在后方时&lt;/td&gt;
&lt;td&gt;需要先渲染不透明物体，再渲染透视物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render Queue 越大&lt;/td&gt;
&lt;td&gt;越后渲染&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;天空盒设置&lt;/h3&gt;
&lt;p&gt;打开 &lt;strong&gt;Window &amp;gt; Rendering &amp;gt; Lighting&lt;/strong&gt;，将 &lt;strong&gt;Environment&lt;/strong&gt; 下的 &lt;strong&gt;skybox material&lt;/strong&gt; 设为 &lt;strong&gt;None&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这样才能看到正确的透视物体和不透明物体的前后遮挡关系。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;深度与渲染队列基础&lt;/h2&gt;
&lt;h3&gt;深度（Depth）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;深度&lt;/strong&gt; = 物体离摄像机的远近，决定谁在前、谁在后，是实现正确遮挡关系的基础。&lt;/p&gt;
&lt;h3&gt;深度测试（Depth Test）&lt;/h3&gt;
&lt;p&gt;&quot;谁近谁赢&quot;的淘汰规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当一个像素想被画到屏幕上时&lt;/li&gt;
&lt;li&gt;它将自己的深度值与深度缓冲区中当前存储的值进行比较&lt;/li&gt;
&lt;li&gt;如果它&quot;更近&quot;，通过测试，颜色显示并更新深度缓冲区&lt;/li&gt;
&lt;li&gt;如果它&quot;更远&quot;，被淘汰，颜色不绘制&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Render Queue&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Render Queue&lt;/strong&gt; = 渲染队列，决定物体渲染顺序。值越大，越后渲染。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;MR 传送门：方法一 Depth Only Shader&lt;/h2&gt;
&lt;h3&gt;核心原理&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shader 类型&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Depth Only Shader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只写入深度，不渲染颜色&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;透视材质&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;显示现实环境&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;渲染流程示例：MR 虚拟门窗&lt;/h3&gt;
&lt;p&gt;假设深度缓冲区比较规则是&quot;小等于&quot;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步&lt;/strong&gt;：虚拟场景先渲染，往深度缓冲区写入深度信息&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步&lt;/strong&gt;：渲染门&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;门材质为 &lt;strong&gt;Depth Only Shader&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;门深度 &amp;lt; 虚拟场景（门在虚拟场景前面）&lt;/li&gt;
&lt;li&gt;通过深度测试，深度缓冲区更新为门的深度值&lt;/li&gt;
&lt;li&gt;门本身不渲染颜色，所以重叠区域仍显示虚拟场景&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第三步&lt;/strong&gt;：渲染墙（透视材质）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;墙深度 &amp;gt; 门（墙在门后面）&lt;/li&gt;
&lt;li&gt;未通过深度测试，墙与门重叠部分不显示&lt;/li&gt;
&lt;li&gt;墙深度 &amp;lt; 虚拟场景（墙在虚拟场景前面）&lt;/li&gt;
&lt;li&gt;通过深度测试，墙与虚拟场景重叠部分渲染出墙的颜色&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;重要&amp;lt;/span&amp;gt;：需要用透视材质物体把人从 6 个面包裹起来，挡住外侧的虚拟场景。&lt;/p&gt;
&lt;p&gt;如果是&lt;strong&gt;室内空间&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;让透视材质物体与现实中的墙壁、天花板、地面贴合&lt;/li&gt;
&lt;li&gt;相当于造一个和现实空间结构一样的虚拟室内空间&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;MR 传送门：方法二 模板测试（Stencil Test）&lt;/h2&gt;
&lt;h3&gt;模板缓冲区（Stencil Buffer）&lt;/h3&gt;
&lt;p&gt;模板缓冲区可以为屏幕上的每一个像素点保存一个值。&lt;/p&gt;
&lt;h3&gt;模板测试流程&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在渲染过程中，用模板缓冲区中保存的值与预设的&lt;strong&gt;参考值（Reference Value）&lt;/strong&gt; 作比较&lt;/li&gt;
&lt;li&gt;根据比较结果决定是否更新相应像素点的颜色值&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;比较规则示例&lt;/h3&gt;
&lt;p&gt;设置参考值为 1，只保留值为 1 的部分：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;条件&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;模板缓冲区值 = 参考值&lt;/td&gt;
&lt;td&gt;通过测试，显示颜色&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;否则&lt;/td&gt;
&lt;td&gt;不显示&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Unity 中实现步骤&lt;/h3&gt;
&lt;h4&gt;第一步：添加 Stencil Shader&lt;/h4&gt;
&lt;p&gt;给传送门区域添加 &lt;strong&gt;Stencil Shader&lt;/strong&gt;，作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不显示颜色&lt;/li&gt;
&lt;li&gt;不参与深度测试&lt;/li&gt;
&lt;li&gt;只在模板缓冲区中把值设为设定的 ID（如 1）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应用此材质的物体在屏幕上显示区域的&lt;strong&gt;模板缓冲区值就被设为了 1&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;第二步：设置虚拟世界 Layer&lt;/h4&gt;
&lt;p&gt;随便取一个 Layer 名字，方便辨识即可（如 &lt;code&gt;StencilMask1&lt;/code&gt;）。&lt;/p&gt;
&lt;h4&gt;第三步：配置 URP 过滤器&lt;/h4&gt;
&lt;p&gt;找到项目中使用的 &lt;strong&gt;URP 配置文件（Universal Renderer Data）&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;Filtering &amp;gt; Layer Mask&lt;/strong&gt; 中，&lt;strong&gt;取消勾选&lt;/strong&gt;刚才为虚拟世界设置的 Layer&lt;/li&gt;
&lt;li&gt;这样场景中就看不到虚拟世界了&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;第四步：添加 Renderer Feature&lt;/h4&gt;
&lt;p&gt;在 URP 配置文件中添加 &lt;strong&gt;Renderer Feature&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;点击 &lt;strong&gt;Add Renderer Feature&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择 &lt;strong&gt;Render Objects&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;配置参数：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Layer Mask&lt;/td&gt;
&lt;td&gt;虚拟世界的 Layer&lt;/td&gt;
&lt;td&gt;控制哪些物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stencil Value&lt;/td&gt;
&lt;td&gt;与 Stencil Shader 一致（如 1）&lt;/td&gt;
&lt;td&gt;参考值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compare Function&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Equal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;只渲染Stencil值为1的部分&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;其他值&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Keep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;保持原值&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;核心原理&lt;/h3&gt;
&lt;p&gt;在 Layer 为 &lt;code&gt;StencilMask1&lt;/code&gt; 的物体中，&lt;strong&gt;只渲染 Stencil 值为 1 的部分&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;什么部分 Stencil 值为 1？
= &lt;strong&gt;传送门区域与虚拟世界在屏幕上重合的部分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为传送门区域的材质为 Stencil Shader，限定了该显示区域的 Stencil 值为 1。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;两种方法对比&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Depth Only Shader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;简单直接&lt;/td&gt;
&lt;td&gt;需要物体把人 6 面包裹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stencil Test&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;灵活，可做任意形状&lt;/td&gt;
&lt;td&gt;配置较复杂&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;透视物体不透明&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;检查 &lt;strong&gt;Render Queue&lt;/strong&gt; 是否 &amp;gt; 2000&lt;/li&gt;
&lt;li&gt;检查天空盒是否已关闭（设为 None）&lt;/li&gt;
&lt;li&gt;检查 Camera 的 Background Type 是否为 Solid Color&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;安全边界弹窗&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;在 AndroidManifest 中添加 &lt;code&gt;BOUNDARYLESS_APP&lt;/code&gt; 配置&lt;/li&gt;
&lt;li&gt;或在头显设置中彻底关闭&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;深度测试不工作&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;检查 Camera 的 &lt;strong&gt;Depth Texture&lt;/strong&gt; 是否开启&lt;/li&gt;
&lt;li&gt;检查各物体的 &lt;strong&gt;Render Queue&lt;/strong&gt; 设置&lt;/li&gt;
&lt;li&gt;确保渲染顺序正确（先不透明物体，再透视物体）&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;MR 透视开发要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;基础配置&lt;/strong&gt;：Building Blocks Passthrough → OVRManager → 相机背景 → 关闭安全边界&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;透视材质&lt;/strong&gt;：Oculus/SelectivePassthrough，Render Queue &amp;gt; 2000&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Depth Only Shader&lt;/strong&gt;：只写深度不写颜色，实现虚拟物体与现实环境遮挡&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stencil Test&lt;/strong&gt;：更灵活，可以实现任意形状的传送门效果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;选择建议&amp;lt;/span&amp;gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单场景用 &lt;strong&gt;Depth Only Shader&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;复杂形状（如圆形、异形传送门）用 &lt;strong&gt;Stencil Test&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.meta.com/horizon/documentation/unity/unity-boundaryless/&quot;&gt;Meta 官方文档：Boundaryless&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/SL-SubShaderTags.html&quot;&gt;Unity 官方：Shader Render Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/&quot;&gt;URP Renderer Feature&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料，结合 MR 透视与传送门开发实战经验。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity物理碰撞系统完全指南</title><link>https://gzhblog.cn/posts/2026-01/unity%E7%89%A9%E7%90%86%E7%A2%B0%E6%92%9E%E7%B3%BB%E7%BB%9F%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-01/unity%E7%89%A9%E7%90%86%E7%A2%B0%E6%92%9E%E7%B3%BB%E7%BB%9F%E5%AE%8C%E5%85%A8%E6%8C%87%E5%8D%97/</guid><description>详细讲解Unity物理碰撞系统的核心概念，包括刚体与碰撞体的区别、常用属性设置、Mesh Collider使用以及碰撞检测条件。</description><pubDate>Fri, 09 Jan 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;物理碰撞是 Unity 游戏开发中的核心系统之一，正确理解和使用物理碰撞能让游戏交互更加真实自然。本文基于 Unity XR 训练营资料，系统讲解 Unity 物理碰撞系统的核心概念与实际应用。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;核心概念：刚体与碰撞体&lt;/h2&gt;
&lt;h3&gt;刚体（Rigidbody）&lt;/h3&gt;
&lt;p&gt;让游戏对象受物理引擎驱动，模拟质量、重力、受力运动。&lt;/p&gt;
&lt;h3&gt;碰撞体（Collider）&lt;/h3&gt;
&lt;p&gt;定义游戏物体的物理形状和边界，用于碰撞检测，是几何上的碰撞体积。&lt;/p&gt;
&lt;h3&gt;协同工作&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;刚体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提供动力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提供可碰撞的形体&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两者协同工作：刚体提供物理驱动力，碰撞体提供可碰撞的形状。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;刚体重要属性&lt;/h2&gt;
&lt;h3&gt;1. 质量（Mass）&lt;/h3&gt;
&lt;p&gt;决定物体的惯性。质量越大的物体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;越难被推动&lt;/li&gt;
&lt;li&gt;碰撞时产生的动量越大&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：应根据物体大小合理设置质量比例，避免失衡。&lt;/p&gt;
&lt;h3&gt;2. 阻力与角阻力（Drag / Angular Drag）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Drag&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;影响物体直线移动时的阻力&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Angular Drag&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;影响物体旋转时的阻力&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;数值越大，物体运动衰减越快。&lt;/p&gt;
&lt;h3&gt;3. 使用重力（Use Gravity）&lt;/h3&gt;
&lt;p&gt;勾选后，对象会受到持续向下的重力影响。&lt;/p&gt;
&lt;h3&gt;4. 是否运动学（Is Kinematic）&lt;/h3&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;关键属性&amp;lt;/span&amp;gt;&lt;/p&gt;
&lt;p&gt;启用后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;刚体不再受物理力（重力、碰撞力）的影响&lt;/li&gt;
&lt;li&gt;只能通过直接设置 Transform（位置、旋转）来移动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移动平台&lt;/li&gt;
&lt;li&gt;开关门&lt;/li&gt;
&lt;li&gt;由动画或脚本精确控制的物体&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. 碰撞检测模式（Collision Detection）&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;性能&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Discrete（离散）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;默认模式，可能导致高速物体&quot;穿模&quot;&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Continuous（连续）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;适合高速物体&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Continuous Dynamic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;动态物体使用连续检测&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;建议&amp;lt;/span&amp;gt;：对于高速移动的物体（如子弹），使用 Continuous 或 Continuous Dynamic 以确保碰撞准确。&lt;/p&gt;
&lt;h3&gt;6. 约束（Constraints）&lt;/h3&gt;
&lt;p&gt;可以&quot;冻结&quot;对象在特定方向上的移动或旋转。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例&lt;/strong&gt;：2D 游戏角色&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;冻结 Z 轴移动（防止前后移动）&lt;/li&gt;
&lt;li&gt;冻结 X、Y 轴旋转（防止倾斜）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;碰撞体重要属性&lt;/h2&gt;
&lt;h3&gt;Is Trigger&lt;/h3&gt;
&lt;p&gt;勾选后碰撞体成为&lt;strong&gt;触发器&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不再具有物理阻挡作用&lt;/li&gt;
&lt;li&gt;其他物体可以穿过它&lt;/li&gt;
&lt;li&gt;会触发特定事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;事件函数&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OnTriggerEnter&lt;/code&gt; — 进入触发器时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OnTriggerStay&lt;/code&gt; — 停留在触发器时&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OnTriggerExit&lt;/code&gt; — 离开触发器时&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;收集物品&lt;/li&gt;
&lt;li&gt;检测玩家进入特定区域（检查点、陷阱）&lt;/li&gt;
&lt;li&gt;触发区域事件&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Mesh Collider（网格碰撞体）&lt;/h2&gt;
&lt;h3&gt;工作原理&lt;/h3&gt;
&lt;p&gt;Mesh Collider 根据模型的网格信息生成对应形状的碰撞体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mesh 参数引用的是模型的网格&lt;/li&gt;
&lt;li&gt;与 Mesh Filter 中的 Mesh 参数相同&lt;/li&gt;
&lt;li&gt;模型网格由许多三角面组成&lt;/li&gt;
&lt;li&gt;Mesh Collider 给每个三角面添加碰撞体&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;特点&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;碰撞体更精确表示模型形状&lt;/td&gt;
&lt;td&gt;性能消耗增大&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Convex 参数&lt;/h3&gt;
&lt;p&gt;如果带有 Mesh Collider 的物体身上有刚体，需要&lt;strong&gt;勾选 Convex&lt;/strong&gt; 参数才能让刚体拥有物理效果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Convex&lt;/strong&gt; = 凸体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会填充模型网格凹进去的部分&lt;/li&gt;
&lt;li&gt;最多支持 &lt;strong&gt;255 个三角面&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;模型面数太大不适合用 Mesh Collider&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;碰撞发生的条件&lt;/h2&gt;
&lt;h3&gt;碰撞 vs 触发器&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;th&gt;应用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞（Collision）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;产生阻挡，可传递力&lt;/td&gt;
&lt;td&gt;物理碰撞、弹跳、阻挡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;触发器（Trigger）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;无阻挡，触发事件&lt;/td&gt;
&lt;td&gt;收集物品、区域检测&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;碰撞的条件&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;两个物体都要有 Collider&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;至少一个物体有 Rigidbody&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物体间有相对运动&lt;/strong&gt;（或其中一个受重力影响）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;触发器的条件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;条件&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;两个物体都有 Collider&lt;/td&gt;
&lt;td&gt;碰撞体存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;至少一个物体有 Rigidbody&lt;/td&gt;
&lt;td&gt;提供物理引擎驱动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;至少一个 Collider 勾选 Is Trigger&lt;/td&gt;
&lt;td&gt;触发器模式&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;代码示例&lt;/h2&gt;
&lt;h3&gt;碰撞检测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class CollisionExample : MonoBehaviour
{
    void OnCollisionEnter(Collision collision)
    {
        Debug.Log($&quot;碰撞到: {collision.gameObject.name}&quot;);
    }

    void OnCollisionStay(Collision collision)
    {
        Debug.Log($&quot;持续碰撞中: {collision.gameObject.name}&quot;);
    }

    void OnCollisionExit(Collision collision)
    {
        Debug.Log($&quot;碰撞结束: {collision.gameObject.name}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;触发器检测&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class TriggerExample : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        Debug.Log($&quot;触发器进入: {other.gameObject.name}&quot;);
    }

    void OnTriggerStay(Collider other)
    {
        Debug.Log($&quot;触发器停留中: {other.gameObject.name}&quot;);
    }

    void OnTriggerExit(Collider other)
    {
        Debug.Log($&quot;触发器离开: {other.gameObject.name}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;XR 开发中的物理碰撞&lt;/h2&gt;
&lt;h3&gt;Quest 中的注意事项&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;碰撞检测模式&lt;/strong&gt;：XR 交互物体多为中低速，Discrete 足够&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mesh Collider 慎用&lt;/strong&gt;：MR 场景复杂，Mesh Collider 性能开销大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 Compound Collider&lt;/strong&gt;：多个简单碰撞体组合代替复杂网格碰撞体&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关闭不必要的物理&lt;/strong&gt;：静态物体不启用刚体&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;XR 交互建议&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;交互类型&lt;/th&gt;
&lt;th&gt;碰撞体建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;抓取物体&lt;/td&gt;
&lt;td&gt;使用 Capsule Collider 或 Box Collider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;手柄射线检测&lt;/td&gt;
&lt;td&gt;使用 Raycast 或 Sphere Collider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;区域触发&lt;/td&gt;
&lt;td&gt;使用 Trigger Collider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题&lt;/h2&gt;
&lt;h3&gt;物体穿模&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;使用 Continuous 碰撞检测&lt;/li&gt;
&lt;li&gt;或降低物体移动速度&lt;/li&gt;
&lt;li&gt;增加碰撞体大小&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;碰撞不触发&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;检查两个物体是否都有 Collider&lt;/li&gt;
&lt;li&gt;检查至少一个物体是否有 Rigidbody&lt;/li&gt;
&lt;li&gt;确保不是两个都是 Kinematic&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Trigger 不触发&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;检查 Collider 是否勾选 Is Trigger&lt;/li&gt;
&lt;li&gt;确保至少一个物体有 Rigidbody&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Mesh Collider 不生效&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;如果有 Rigidbody，必须勾选 Convex&lt;/li&gt;
&lt;li&gt;检查模型面数是否超过 255&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;概念&lt;/th&gt;
&lt;th&gt;要点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;刚体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提供物理驱动力，控制重力、受力运动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞体&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;定义碰撞形状，决定碰撞检测范围&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Is Kinematic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;运动学刚体不受物理力影响&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Is Trigger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;触发器模式，触发事件但不阻挡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mesh Collider&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;精确但性能开销大，注意 Convex 限制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;碰撞检测模式&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;高速物体用 Continuous&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心原则&amp;lt;/span&amp;gt;：刚体提供动力，碰撞体提供形状。合理搭配使用，平衡性能与真实性。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/class-Rigidbody.html&quot;&gt;Unity 官方文档：Rigidbody&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/class-Collider.html&quot;&gt;Unity 官方文档：Colliders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/Manual/PhysicsOverview.html&quot;&gt;Unity 官方文档：Physics Overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity XR开发实战指南</title><link>https://gzhblog.cn/posts/2026-01/unity-xr%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-01/unity-xr%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/</guid><description>从环境搭建到交互开发，系统讲解Unity XR开发的核心流程，涵盖Meta XR SDK配置、抓取交互、手势录制等实用技巧。</description><pubDate>Thu, 08 Jan 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;XR（VR/AR/MR）开发正在快速发展，Meta Quest、PICO、Apple Vision Pro 等设备引领着空间计算时代的到来。本文基于 Unity XR 训练营资料，系统讲解 Unity XR 开发的核心流程与实战技巧。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;开发环境搭建&lt;/h2&gt;
&lt;h3&gt;1. 创建 URP 工程&lt;/h3&gt;
&lt;p&gt;首先在 Unity Hub 中创建一个使用 &lt;strong&gt;URP（Universal Render Pipeline）&lt;/strong&gt; 的工程。URP 是 Meta 推荐使用的渲染管线，提供更好的性能表现。&lt;/p&gt;
&lt;h3&gt;2. 切换安卓平台&lt;/h3&gt;
&lt;p&gt;菜单栏 &lt;strong&gt;File &amp;gt; Build Profile&lt;/strong&gt;，点击 &lt;strong&gt;Android&lt;/strong&gt;，点击 &lt;strong&gt;Switch Platform&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;为什么需要切换平台？&amp;lt;/span&amp;gt;Quest 和 PICO 头显都是基于安卓系统，想要把程序打包发布到这些设备上运行，必须打包出能在安卓系统运行的 APK 文件。&lt;/p&gt;
&lt;h3&gt;3. 配置 XR Plugin Management&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;strong&gt;Edit &amp;gt; Project Settings &amp;gt; XR Plugin Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Install XR Plugin Management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在安卓端和 PC 端都勾选 &lt;strong&gt;OpenXR&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. 导入 Meta XR SDK&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;strong&gt;Window &amp;gt; Package Management &amp;gt; Package Manager&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在 Unity Asset Store 添加以下三个 SDK：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Meta XR Core SDK&lt;/strong&gt; — 核心功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meta XR Interaction SDK&lt;/strong&gt; — 交互系统&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meta MR Utility Kit&lt;/strong&gt; — MR 工具包&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;提示&amp;lt;/span&amp;gt;：安装可能需要科学上网，如果失败请尝试重新安装。&lt;/p&gt;
&lt;h3&gt;5. 环境修复&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;找到 &lt;strong&gt;Meta XR Tools &amp;gt; Project Setup Tool&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;在安卓端和 PC 端都点击 &lt;strong&gt;Fix All&lt;/strong&gt; 和 &lt;strong&gt;Apply All&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;重点消除 &lt;strong&gt;Fix All&lt;/strong&gt; 下的红色警告（未消除会导致程序无法运行）&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;XR Plug-in Management &amp;gt; Project Validation&lt;/strong&gt;，在安卓端和 PC 端点击 &lt;strong&gt;Fix All&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;6. 最终检查&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;检查项&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenXR 安卓端 Render Mode&lt;/td&gt;
&lt;td&gt;设为 &lt;strong&gt;Single Pass Instanced&lt;/strong&gt; 或 &lt;strong&gt;Multi-view&lt;/strong&gt;（性能更好）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enable Interaction Profiles&lt;/td&gt;
&lt;td&gt;应有 &lt;strong&gt;Oculus Touch Controller Profile&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenXR Feature Groups&lt;/td&gt;
&lt;td&gt;勾选 &lt;strong&gt;Meta XR&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;交互系统开发&lt;/h2&gt;
&lt;h3&gt;玩家物体&lt;/h3&gt;
&lt;p&gt;Meta 提供了功能齐全的玩家物体（Building Block），可以快速搭建基础交互功能。&lt;/p&gt;
&lt;h3&gt;抓取交互&lt;/h3&gt;
&lt;h4&gt;近距离抓取&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;在需要添加抓取交互的物体上&lt;strong&gt;鼠标右键&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择 &lt;strong&gt;Interaction SDK &amp;gt; Add Grab Interaction&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Fix All&lt;/strong&gt; 后再点击 &lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Unity 会自动添加抓取交互所需的脚本和组件&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;限制抓取物体的移动、旋转、双手缩放&lt;/h4&gt;
&lt;p&gt;在有 &lt;strong&gt;Grabbable&lt;/strong&gt; 脚本的物体上添加 &lt;strong&gt;Grab Free Transformer&lt;/strong&gt; 脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 配置步骤
1. 把 GrabFreeTransformer 添加到 Grabbable 的 One Grab Transformer 参数
2. 如果需要双手抓取，添加到 Two Grab Transformer 参数
3. 取消勾选 Grabbable 的 Transfer On Second Selection 参数
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;双手缩放&lt;/h4&gt;
&lt;p&gt;将 &lt;strong&gt;GrabFreeTransformer&lt;/strong&gt; 脚本上 &lt;strong&gt;Scale Constraints&lt;/strong&gt; 三个轴的 Constrain 取消勾选，双手就能自由缩放物体大小。&lt;/p&gt;
&lt;h4&gt;限制抓取范围&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;GrabFreeTransformer&lt;/strong&gt; 脚本上的 &lt;strong&gt;Position/Rotation/Scale Constrains&lt;/strong&gt; 分别控制位置、旋转和缩放的允许范围：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 配置参数说明
Constrain：勾选后启用该轴的限制
Min / Max：限制的最小值和最大值
Constraints Are Relative：是否相对于本地坐标
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Constraints Are Relative 示例（以缩放为例）：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设物体原缩放为 (0.8, 0.8, 0.8)，X/Y/Z 缩放最小值 0.3，最大值 2：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;最小缩放&lt;/th&gt;
&lt;th&gt;最大缩放&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;勾选 Relative&lt;/td&gt;
&lt;td&gt;(0.24, 0.24, 0.24)&lt;/td&gt;
&lt;td&gt;(1.6, 1.6, 1.6)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;不勾选&lt;/td&gt;
&lt;td&gt;(0.3, 0.3, 0.3)&lt;/td&gt;
&lt;td&gt;(2, 2, 2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;录制抓取手势&lt;/h2&gt;
&lt;p&gt;Meta 提供了便捷的抓取手势录制工具。&lt;/p&gt;
&lt;h3&gt;录制步骤&lt;/h3&gt;
&lt;h4&gt;第一步：准备工作&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;菜单栏点击 &lt;strong&gt;Meta &amp;gt; Interaction &amp;gt; Hand Grab Pose Recorder&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;引用有 &lt;strong&gt;Hand&lt;/strong&gt; 脚本的右手物体&lt;/li&gt;
&lt;li&gt;需要录制手势的物体需有 &lt;strong&gt;Rigidbody&lt;/strong&gt; 刚体组件&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;第二步：运行录制&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;运行程序，做出抓取手势&lt;/li&gt;
&lt;li&gt;在 Recorder 面板空白处&lt;strong&gt;双击&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;按&lt;strong&gt;空格键&lt;/strong&gt;或点击 &lt;strong&gt;Record HandGrabPose&lt;/strong&gt; 按钮开始录制&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;第三步：保存手势&lt;/h4&gt;
&lt;p&gt;点击 &lt;strong&gt;Save To Collection&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;第四步：加载手势&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;退出运行模式&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Load From Collection&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;保留一个有 &lt;strong&gt;HandGrabPose&lt;/strong&gt; 子物体的 &lt;strong&gt;HandGrabInteractable&lt;/strong&gt;，删除其他的&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;生成镜像手势&lt;/h3&gt;
&lt;p&gt;找到物体身上的 &lt;strong&gt;HandGrabInteractable&lt;/strong&gt; 脚本，点击 &lt;strong&gt;Create Mirrored HandGrabInteractable&lt;/strong&gt; 即可快速生成另一只手的镜像手势。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;远距离抓取&lt;/h2&gt;
&lt;h3&gt;添加远距离抓取&lt;/h3&gt;
&lt;p&gt;选中物体，&lt;strong&gt;鼠标右键&lt;/strong&gt; &amp;gt; &lt;strong&gt;Interaction SDK &amp;gt; Add Distance Grab Interaction&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;三种抓取类型&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grab Relative To Hand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;物体和手同步移动&lt;/td&gt;
&lt;td&gt;像手和物体之间连成一条线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pull Interactable To Hand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;物体飞向手&lt;/td&gt;
&lt;td&gt;最常用，一把物体抓到手上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manipulate In Place&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;远距离操控物体&lt;/td&gt;
&lt;td&gt;像有只无形的&quot;幽灵手&quot;直接抓住物体&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;推荐&amp;lt;/span&amp;gt;：&lt;strong&gt;Pull Interactable To Hand&lt;/strong&gt;，用户体验最自然。&lt;/p&gt;
&lt;h3&gt;Time out Snap Zone&lt;/h3&gt;
&lt;p&gt;当选择 &lt;strong&gt;Pull Interactable To Hand&lt;/strong&gt; 类型时：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;strong&gt;Optional Components&lt;/strong&gt; 下找到 &lt;strong&gt;Time out Snap Zone&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;点击 &lt;strong&gt;Fix&lt;/strong&gt; 再点击 &lt;strong&gt;Create&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;增加功能：松开手后等待一定秒数，物体自动回到初始位置&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;控制返回时间&lt;/strong&gt;：设置 &lt;strong&gt;Snap Interactor&lt;/strong&gt; 中的 &lt;strong&gt;Time out&lt;/strong&gt; 参数。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;触碰物体交互&lt;/h2&gt;
&lt;h3&gt;配置步骤&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;添加近距离抓取交互组件后，&lt;strong&gt;删除&lt;/strong&gt;物体原先的 &lt;strong&gt;Grabbable&lt;/strong&gt; 脚本&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;ISDK_HandGrabInteraction&lt;/strong&gt; 子物体上添加 &lt;strong&gt;InteractableUnityEventWrapper&lt;/strong&gt; 脚本&lt;/li&gt;
&lt;li&gt;将 &lt;strong&gt;HandGrabInteractable&lt;/strong&gt; 引用到 &lt;strong&gt;Interactable View&lt;/strong&gt; 参数上&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;应用场景&lt;/h3&gt;
&lt;p&gt;可以配置 &lt;strong&gt;WhenHover&lt;/strong&gt; 事件获取物体被手触碰的时机，用于：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;触发高亮效果&lt;/li&gt;
&lt;li&gt;播放音效&lt;/li&gt;
&lt;li&gt;启动某些交互逻辑&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;性能优化要点&lt;/h2&gt;
&lt;h3&gt;渲染批次优化&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;技术&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;动态批次&lt;/td&gt;
&lt;td&gt;大量小型相同材质物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;静态批次&lt;/td&gt;
&lt;td&gt;不移动的物体&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU Instancing&lt;/td&gt;
&lt;td&gt;大量相同网格物体（树木、石头等）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SRP Batcher&lt;/td&gt;
&lt;td&gt;URP/HDRP 管线优化&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;贴图优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;XR 一体机建议贴图 &lt;strong&gt;Max Size 设为 1024&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;启用 &lt;strong&gt;Mipmap&lt;/strong&gt; 减少带宽浪费&lt;/li&gt;
&lt;li&gt;使用压缩格式（ASTC/ETC2）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内存优化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;定期调用 &lt;code&gt;Resources.UnloadUnusedAssets()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;StringBuilder&lt;/code&gt; 拼接字符串&lt;/li&gt;
&lt;li&gt;避免在 &lt;code&gt;Update&lt;/code&gt; 中使用 LINQ&lt;/li&gt;
&lt;li&gt;禁用正式版本的 &lt;code&gt;Debug.Log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题与解决&lt;/h2&gt;
&lt;h3&gt;SDK 导入报错&lt;/h3&gt;
&lt;p&gt;如果导入 &lt;strong&gt;Meta XR Core SDK&lt;/strong&gt; 报错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Library\PackageCache\com.meta.xr.sdk.core@xxx\Editor\MetaXRSimulator\XRSimInstallationDetector.cs(87,29):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解决方案&lt;/strong&gt;：打开 &lt;strong&gt;Package Manager&lt;/strong&gt;，在 &lt;strong&gt;In Project&lt;/strong&gt; 中找到 &lt;strong&gt;Unity Version Control&lt;/strong&gt;，点击 &lt;strong&gt;Version History&lt;/strong&gt;，选择带有 &lt;strong&gt;Recommended&lt;/strong&gt; 的版本，点击 &lt;strong&gt;Update&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;项目验证失败&lt;/h3&gt;
&lt;p&gt;如果 &lt;strong&gt;Project Validation&lt;/strong&gt; 有红色警告：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;strong&gt;Project Setup Tool&lt;/strong&gt; 的 &lt;strong&gt;Fix All&lt;/strong&gt; 功能&lt;/li&gt;
&lt;li&gt;检查 &lt;strong&gt;XR Plug-in Management&lt;/strong&gt; 配置&lt;/li&gt;
&lt;li&gt;确保所有平台（安卓/PC）都已正确配置&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;交互不工作&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;检查物体是否有正确的 &lt;strong&gt;Rigidbody&lt;/strong&gt; 组件&lt;/li&gt;
&lt;li&gt;确认 &lt;strong&gt;Layer&lt;/strong&gt; 设置正确（需要包含在交互系统的 Layer 配置中）&lt;/li&gt;
&lt;li&gt;查看 &lt;strong&gt;Frame Debugger&lt;/strong&gt; 检查渲染事件&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Unity XR 开发主要流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;环境搭建&lt;/strong&gt;：创建 URP 项目 → 切换安卓平台 → 配置 XR Plugin → 导入 SDK → 环境修复&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交互开发&lt;/strong&gt;：添加玩家物体 → 配置抓取交互 → 录制手势 → 远距离抓取&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：批次处理 → 贴图优化 → 内存管理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心建议&amp;lt;/span&amp;gt;：使用 Meta 提供的 Building Block 快速原型开发，深入理解 Interaction SDK 的组件架构，遇到问题多查看官方文档和 Frame Debugger。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.oculus.com/documentation/unity/&quot;&gt;Meta XR SDK 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/Packages/com.unity.xr.interaction.toolkit@latest/&quot;&gt;Unity XR Interaction Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.khronos.org/openxr/&quot;&gt;OpenXR 官方规范&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;本文整理自 Unity XR 训练营学习资料，结合 Meta XR SDK 与 Interaction SDK 实战经验。如有疑问欢迎交流！&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Unity脚本生命周期函数详解</title><link>https://gzhblog.cn/posts/2026-01/unity%E8%84%9A%E6%9C%AC%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%87%BD%E6%95%B0/</link><guid isPermaLink="true">https://gzhblog.cn/posts/2026-01/unity%E8%84%9A%E6%9C%AC%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%87%BD%E6%95%B0/</guid><description>深入解析Unity脚本的核心概念与生命周期函数执行顺序，帮助你编写更规范、更高效的Unity脚本。</description><pubDate>Wed, 07 Jan 2026 20:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;Unity脚本是驱动游戏逻辑和行为的核心组件。理解脚本的核心概念和生命周期函数的执行顺序，是编写高效、可维护代码的基础。本文将从实际开发角度，详细讲解Unity脚本的关键知识点。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Unity脚本核心概念&lt;/h2&gt;
&lt;h3&gt;1. 脚本基础：继承 MonoBehaviour&lt;/h3&gt;
&lt;p&gt;所有控制游戏对象（GameObject）行为的脚本都必须直接或间接继承自 &lt;code&gt;MonoBehaviour&lt;/code&gt; 类。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using UnityEngine;

public class MyScript : MonoBehaviour
{
    // 脚本逻辑
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;提示&amp;lt;/span&amp;gt;：&lt;code&gt;MonoBehaviour&lt;/code&gt; 是 Unity 脚本的基类，它提供了与引擎交互的接口。&lt;/p&gt;
&lt;h3&gt;2. 组件交互：GetComponent&amp;lt;T&amp;gt;&lt;/h3&gt;
&lt;p&gt;获取组件引用是与引擎交互的最主要方式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ComponentExample : MonoBehaviour
{
    private Rigidbody rb;
    private Renderer meshRenderer;

    void Awake()
    {
        // 获取组件引用
        rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
        meshRenderer = GetComponent&amp;lt;Renderer&amp;gt;();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;注意&amp;lt;/span&amp;gt;：&lt;code&gt;GetComponent&lt;/code&gt; 会有一定开销，避免在 &lt;code&gt;Update&lt;/code&gt; 中频繁调用。&lt;/p&gt;
&lt;h3&gt;3. 变量与 Inspector 面板&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;public&lt;/code&gt; 或 &lt;code&gt;[SerializeField]&lt;/code&gt; 属性将变量暴露给 Inspector：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class InspectorExample : MonoBehaviour
{
    // 公开变量，直接在Inspector中显示和修改
    public float moveSpeed = 5f;
    public GameObject targetObject;

    // 私有变量但仍显示在Inspector
    [SerializeField] private float rotationSpeed = 90f;
    [SerializeField] private bool isActive = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;生命周期函数详解&lt;/h2&gt;
&lt;p&gt;脚本生命周期定义了脚本从创建到销毁的整个过程中，各个函数被调用的顺序和规则。&lt;/p&gt;
&lt;h3&gt;执行顺序图&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                      脚本实例创建                            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  Awake()         — 脚本实例被创建时立即调用（最早执行）      │
│                     即使脚本未启用也会调用，整个生命周期只执行一次│
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  OnEnable()      — 每次脚本被启用时调用                      │
│                     （组件被勾选 或 所在对象被激活）            │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  Start()         — 首次启用后，Update之前调用               │
│                     只执行一次                                 │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
┌─────────────────────────┐     ┌─────────────────────────────┐
│  FixedUpdate()          │     │  Update()                   │
│  固定时间间隔调用        │     │  每帧调用，频率不固定         │
│  默认0.02秒             │     │                             │
│  用于物理计算            │     │  用于非物理逻辑：             │
│                         │     │  - 输入检测                  │
│                         │     │  - 角色状态                  │
│                         │     │  - 游戏逻辑                  │
└─────────────────────────┘     └─────────────────────────────┘
              │                               │
              └───────────────┬───────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  LateUpdate()      — 所有Update执行完毕后调用               │
│                      常用于摄像机跟随等需要后处理的逻辑          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  OnDisable()       — 脚本被禁用时调用                        │
│                      取消事件监听、停止协程、清理资源           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  OnDestroy()       — 脚本被销毁时调用（对象销毁或场景切换）   │
│                      进行最终的清理工作                        │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;各生命周期函数详解&lt;/h2&gt;
&lt;h3&gt;Awake()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void Awake()
{
    // 初始化变量、获取组件引用
    // 整个生命周期内只执行一次，执行顺序最早
    rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
    health = maxHealth;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;调用时机&lt;/td&gt;
&lt;td&gt;脚本实例被创建时立即调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;是否需要启用&lt;/td&gt;
&lt;td&gt;否，即使脚本禁用也会调用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行次数&lt;/td&gt;
&lt;td&gt;整个生命周期只执行一次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行顺序&lt;/td&gt;
&lt;td&gt;所有生命周期函数中最早&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;典型用途&amp;lt;/span&amp;gt;：初始化变量、获取组件引用。用于设置脚本运行所需的基础状态。&lt;/p&gt;
&lt;h3&gt;OnEnable()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void OnEnable()
{
    // 注册事件监听、启动协程
    GameEvents.OnEnemyKilled += HandleEnemyKilled;
    StartCoroutine(UpdateHealthBar());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;典型用途&amp;lt;/span&amp;gt;：注册事件监听、启动脚本激活时需要立即执行的操作。&lt;/p&gt;
&lt;h3&gt;Start()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void Start()
{
    // 进行依赖于其他组件已初始化完成的逻辑
    // 或者游戏开始前的最终设置
    target = GameObject.FindWithTag(&quot;Player&quot;).transform;
    animator = GetComponent&amp;lt;Animator&amp;gt;();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;调用时机&lt;/td&gt;
&lt;td&gt;脚本首次启用后，在第一次 Update 之前&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;是否需要启用&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;执行次数&lt;/td&gt;
&lt;td&gt;只执行一次&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;典型用途&amp;lt;/span&amp;gt;：进行依赖于其他组件已初始化完成的逻辑，或游戏开始前的最终设置。&lt;/p&gt;
&lt;h3&gt;Update()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void Update()
{
    // 处理非物理相关的实时游戏逻辑
    float horizontal = Input.GetAxis(&quot;Horizontal&quot;);
    float vertical = Input.GetAxis(&quot;Vertical&quot;);

    transform.Translate(horizontal * moveSpeed * Time.deltaTime, 0,
                        vertical * moveSpeed * Time.deltaTime);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;调用时机&lt;/td&gt;
&lt;td&gt;每一帧调用一次&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调用频率&lt;/td&gt;
&lt;td&gt;与设备性能和当前帧渲染量相关，不固定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用场景&lt;/td&gt;
&lt;td&gt;输入检测、角色状态、游戏逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: red; font-weight: bold;&quot;&amp;gt;性能注意&amp;lt;/span&amp;gt;：&lt;code&gt;Update&lt;/code&gt; 每一帧都会执行，避免在其中进行高开销操作，如 &lt;code&gt;GetComponent&lt;/code&gt;、字符串拼接、内存分配。&lt;/p&gt;
&lt;h3&gt;FixedUpdate()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void FixedUpdate()
{
    // 执行与物理计算相关的操作
    // 固定时间间隔调用（默认0.02秒），不受渲染帧率影响
    rb.AddForce(physicsInput * thrustForce);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;调用时机&lt;/td&gt;
&lt;td&gt;固定时间间隔（默认 0.02 秒）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调用频率&lt;/td&gt;
&lt;td&gt;固定，不受渲染帧率影响&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;适用场景&lt;/td&gt;
&lt;td&gt;物理计算、刚体施力&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;为什么物理计算用 FixedUpdate&amp;lt;/span&amp;gt;：物理引擎需要稳定的时间步长，如果用 Update，帧率波动会导致物理模拟不稳定。&lt;/p&gt;
&lt;h3&gt;LateUpdate()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void LateUpdate()
{
    // 常用语摄像机跟随，确保在目标对象移动完成后再更新
    targetPosition = target.position + offset;
    transform.position = Vector3.SmoothDamp(transform.position,
                                           targetPosition,
                                           ref velocity,
                                           smoothTime);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;典型用途&amp;lt;/span&amp;gt;：摄像机跟随、动画后处理等需要在所有 Update 执行完毕后再处理的逻辑。&lt;/p&gt;
&lt;h3&gt;OnDisable()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void OnDisable()
{
    // 取消事件监听、停止协程、清理资源
    GameEvents.OnEnemyKilled -= HandleEnemyKilled;
    StopAllCoroutines();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: orange; font-weight: bold;&quot;&amp;gt;重要&amp;lt;/span&amp;gt;：防止脚本禁用后仍执行不必要的操作，造成难以排查的 bug。&lt;/p&gt;
&lt;h3&gt;OnDestroy()&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void OnDestroy()
{
    // 进行最终的清理工作
    Debug.Log($&quot;{gameObject.name} 已被销毁&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;典型用途&amp;lt;/span&amp;gt;：保存数据、释放资源、记录日志。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;实用代码模板&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;using UnityEngine;

public class LifecycleExample : MonoBehaviour
{
    // Inspector 面板可编辑
    public float moveSpeed = 5f;
    public float rotationSpeed = 90f;

    // 私有变量
    private Rigidbody rb;
    private Vector3 startPosition;

    void Awake()
    {
        // 最早执行，用于初始化
        rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
        startPosition = transform.position;
        Debug.Log(&quot;Awake called&quot;);
    }

    void OnEnable()
    {
        // 每次启用时调用
        Debug.Log(&quot;OnEnable called&quot;);
    }

    void Start()
    {
        // 初始化依赖
        Debug.Log(&quot;Start called&quot;);
    }

    void FixedUpdate()
    {
        // 物理更新（固定频率）
        rb.AddForce(Vector3.forward * moveSpeed);
    }

    void Update()
    {
        // 游戏逻辑更新（每帧）
        float h = Input.GetAxis(&quot;Horizontal&quot;);
        float v = Input.GetAxis(&quot;Vertical&quot;);

        transform.Rotate(0, h * rotationSpeed * Time.deltaTime, 0);
    }

    void LateUpdate()
    {
        // 摄像机跟随等后处理
    }

    void OnDisable()
    {
        // 清理工作
        Debug.Log(&quot;OnDisable called&quot;);
    }

    void OnDestroy()
    {
        // 最终清理
        Debug.Log(&quot;OnDestroy called&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;常见问题与最佳实践&lt;/h2&gt;
&lt;h3&gt;1. Awake vs Start&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;函数&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Awake&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;脚本实例创建时就调用，无需启用，用于基础初始化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;首次启用后才调用，用于需要其他组件已就绪的初始化&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;经验&amp;lt;/span&amp;gt;：基础初始化放 Awake，依赖其他组件的初始化放 Start。&lt;/p&gt;
&lt;h3&gt;2. Update vs FixedUpdate&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;函数&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;每帧调用，频率不固定&lt;/td&gt;
&lt;td&gt;输入检测、角色控制、游戏逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FixedUpdate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;固定间隔，频率固定&lt;/td&gt;
&lt;td&gt;物理计算、刚体操作&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;3. 性能优化建议&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class BadExample : MonoBehaviour
{
    void Update()
    {
        // ❌ 每帧都GetComponent，开销大
        Rigidbody rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
        rb.AddForce(Vector3.forward);
    }
}

public class GoodExample : MonoBehaviour
{
    private Rigidbody rb;

    void Awake()
    {
        // ✅ 缓存引用，只获取一次
        rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
    }

    void Update()
    {
        // ✅ 使用缓存的引用
        rb.AddForce(Vector3.forward);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;理解 Unity 脚本的生命周期函数是开发的基础：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Awake&lt;/strong&gt; — 最先执行，用于基础初始化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OnEnable&lt;/strong&gt; — 每次启用时调用&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start&lt;/strong&gt; — 首次启用后执行，用于依赖初始化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FixedUpdate&lt;/strong&gt; — 固定频率，用于物理计算&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update&lt;/strong&gt; — 每帧调用，用于游戏逻辑&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LateUpdate&lt;/strong&gt; — 所有 Update 后调用，用于后处理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OnDisable&lt;/strong&gt; — 禁用时清理资源&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OnDestroy&lt;/strong&gt; — 销毁时最终清理&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;span style=&quot;color: blue; font-weight: bold;&quot;&amp;gt;核心理念&amp;lt;/span&amp;gt;：在合适的生命周期函数中做合适的事。基础初始化用 Awake，物理计算用 FixedUpdate，游戏逻辑用 Update，摄像机跟随用 LateUpdate。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[Unity 官方文档：脚本生命周期](https://docs.unity3d.com/cn/current/Manual Execution Order.html)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unity3d.com/cn/current/ScriptReference/MonoBehaviour.html&quot;&gt;MonoBehaviour 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;如果你有任何问题，欢迎在评论区交流！&lt;/em&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>