当前位置: 首页 > news >正文

Unreal ARPG笔记

今天我们来拆解一个具体的基于UE5实现的ARPG Demo的内容来了解一个ARPG具体实现需要的内容。

这是一个Github上的开源项目,地址如下:Mecha-NOX/Echoes-of-the-Lost-Sands: A Soulslike RPG developed in UE5 featuring dynamic melee combat, diverse environments, and AI-driven enemies

我们先大体上把这个项目拆解成这么几个部分:

  1. 角色与战斗
  2. 敌人AI与Boss
  3. 物品与交互
  4. 环境与关卡
  5. 用户界面与体验
  6. 输入与架构支持
  7. 存档与数据管理

接下来我们来一个部分一个部分地拆解,因为这似乎是我的第一个Unreal引擎实现的Demo,所以我会添加很多UE相关的内容以及和Unity的对比。

我们先针对UE和Unity的区别有个大概的概念:

在我看来,UE和Unity最大的差异在于两个点:在底层的内容都是基于C++实现的前提下,UE为了保证性能向上封装时依然使用了C++进行封装而Unity为了方便性以及安全性考虑选择了C#进行封装,这导致了UE非常难用(比如内存管理)但是得到了极致的性能,而Unity非常好用的同时难以实现顶级的视觉效果;第二个点则是UE的蓝图系统使得UE比起Unity来说有着更强大的可视化效果,而蓝图的强大可视化也方便了诸如行为树、黑板、状态机等功能的具体部署而Unity这方面没有做的那么好。

角色与战斗

角色移动与操作

聊到角色当然得让角色先动起来,别的不说,基本的移动是必须的,然后作为一个强调动作元素的游戏,往往会多做一个锁定的功能。

具体来说,我们利用UE的Enhanced Input System来实现输入的映射,将具体的输入映射到诸如具体Move、Look、Dodge、LockTarget函数。

这里需要补充一下关于UE特有的Enhanced Input System组件。

Enhanced Input System(增强输入系统)是UE5引入的新一代输入管理框架,替代了传统的Input Mapping和Input Action系统。它提供了更灵活、数据驱动、易扩展的输入处理方式,适用于现代游戏对多平台、多设备、复杂操作的需求。

输入映射上下文(Input Mapping Context,IMC)本质上是一组输入配置的集合。通过激活不同的IMC,同一个按键可以在不同的游戏状态下触发不同的Input Action,只有当前激活的IMC才会响应输入。如果多个IMC同时激活,系统会根据优先级决定哪个IMC先处理输入。因此,IMC的最大作用是让同一个按键在不同场景下拥有不同的功能,避免输入冲突,极大提升了输入管理的灵活性和可扩展性。

回到我们的角色移动部分来,我们以角色移动的实现举例:

玩家通过键盘(WASD)或手柄控制角色在世界中前后左右移动。

void Move(const FInputActionValue& Value);
...
...
EnhancedInput->BindAction(MovementAction, ETriggerEvent::Triggered, this, &ASlashCharacter::Move);
...
...
void ASlashCharacter::Move(const FInputActionValue& Value)
{if (ActionState != EActionState::EAS_Unoccupied) return; // 只有在空闲状态下才能移动const FVector2D MovementVector = Value.Get<FVector2D>();if (Controller){const FRotator ControlRotation = GetControlRotation();const FRotator YawRotation(0.f, ControlRotation.Yaw, 0.f);const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);AddMovementInput(ForwardDirection, MovementVector.Y);const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);AddMovementInput(RightDirection, MovementVector.X);}
}

我们写好一个具体的Move函数之后调用EnhancedInput中的BindAction函数就可以实现一个具体的输入绑定了。

这里首先补充一下BindAction的用法:

BindAction(UInputAction* Action,           // 要绑定的输入动作(资源)ETriggerEvent TriggerEvent,     // 触发类型(如按下、松开、持续等)UObject* Object,                // 回调函数所属对象(通常是this)函数指针                        // 回调函数
)

然后是这个Move函数的含义: 这个函数用于根据玩家的输入控制角色在世界中的前后和左右移动,并确保只有在角色处于空闲状态时才能移动。它会根据当前摄像机的朝向,具体来说,通过获取摄像机的Yaw朝向,计算出世界坐标下的前后和左右方向,然后将玩家输入的二维向量分别映射到这两个方向,实现角色始终相对于摄像机视角的移动。

然后可以看到Move继承自ASlashCharacter类,ASlashCharacter 类继承自 ABaseCharacter 类,而 ABaseCharacter 又继承自 Unreal Engine 的 ACharacter 类。

  • ACharacter:UE自带的角色基类,包含了角色移动、动画、碰撞等最基础的功能。
  • ABaseCharacter:自定义的基础角色类,封装了所有角色(包括玩家和敌人)都需要的通用逻辑,比如受击、死亡、属性组件等。
  • ASlashCharacter:玩家专属角色类,扩展了玩家特有的功能,如输入响应、拾取物品、锁定目标等。

这里说点比较宏观的内容:UE自带了哪些常用的类?

攻击与连击系统

我们的攻击系统比较复杂,支持普通攻击、连击(Combo)、蓄力攻击等多种攻击方式。

普通攻击是指玩家短按攻击键(如鼠标左键)时触发的攻击。实现流程为:按下攻击键时先检测角色是否可攻击,若可以则播放普通攻击动画(动画蒙太奇的第一个Section),在动画的特定帧通过AnimNotify启用武器碰撞,检测命中并造成伤害,动画结束后重置状态,等待下一次攻击输入。

连击是指玩家在攻击动画的“连击窗口”内连续点击攻击键,实现连续攻击。具体做法是:将攻击动画蒙太奇分为多个Section(如Attack1、Attack2、Attack3),在每个Section的合适帧插入AnimNotify以开启“连击窗口”,玩家在窗口内再次按下攻击键时记录连击输入,动画事件检测到连击输入后立即切换到下一个Section,实现流畅连招。若连击结束或未输入,动画回到初始状态。

蓄力攻击则是玩家长按攻击键,松开时释放强力攻击。实现流程为:按下攻击键时记录蓄力开始时间并进入蓄力准备状态,持续按住时角色可播放蓄力准备动画或特效,松开攻击键时计算蓄力时长,根据时长决定蓄力等级和伤害,最后播放对应的蓄力攻击动画并造成更高伤害。

这里会有一个问题:我们如何在输入映射上区分三种攻击呢?和技能不同,我们的这三种攻击往往共享一个键(比如鼠标左键)。

1. Input Action设计

  • 创建一个“攻击”Input Action(如AttackAction)。
  • 在AttackAction的Triggers中同时添加:
  • Tap Trigger:用于检测短按(普通攻击/连击)。
  • Hold Trigger:用于检测长按(蓄力攻击),可设置Hold Time(如0.5秒)。

2. 代码绑定

cppApplyEnhancedInput->BindAction(AttackAction, ETriggerEvent::Started, this, &ASlashCharacter::OnAttackStarted);EnhancedInput->BindAction(AttackAction, ETriggerEvent::Completed, this, &ASlashCharacter::OnAttackReleased);

3. 逻辑处理

  • OnAttackStarted:记录按下时间,准备进入蓄力状态。
  • OnAttackReleased:
  • 判断按下时长是否小于Hold Time:
  • 小于Hold Time:执行普通攻击或连击逻辑(根据当前动画状态和连击窗口判断)。
  • 大于Hold Time:执行蓄力攻击逻辑(根据蓄力时长决定伤害和动画)。

4. 系统自动区分

  • Enhanced Input System会根据Trigger配置自动区分短按和长按,开发者只需在回调函数中根据时长和状态判断,保证逻辑不会冲突。

总结来说就是通过Enhanced Input的Tap和Hold Trigger自动区分,代码中根据时长和状态判断,保证三种输入互不冲突且响应流畅。

受击与死亡

攻击方的“攻击”主要体现在播放攻击动画和在关键帧启用武器的碰撞体,而真正的伤害判定和反馈逻辑则全部由受击方的受击函数来实现。当武器的碰撞体与角色发生碰撞时,动画事件会触发碰撞检测,系统会判断对方是否具备可被攻击的条件(如实现了受击接口或拥有特定Tag)。如果可以受击,就会调用该角色的受击函数。受击函数内部负责播放受击动画、音效、粒子特效,并通过属性组件扣除生命值,同时判断是否进入死亡状态。这样,攻击方只负责动作和碰撞的触发,所有具体的受伤和死亡反馈都集中在受击方的逻辑中实现,保证了系统的清晰和可扩展性。

受到攻击时,攻击方会调用被攻击角色的 GetHit_Implementation 函数(该函数在 ABaseCharacter 类中实现)。

在 GetHit_Implementation 中:

  • 首先判断角色是否还活着(IsAlive())。
  • 如果活着,则播放受击动画(如方向性受击反应)、受击音效和粒子特效。
  • 同时会调用属性组件(UAttributeComponent)的 ReceiveDamage 方法,扣除生命值。
void ABaseCharacter::GetHit_Implementation(const FVector& ImpactPoint, AActor* Hitter)
{if (IsAlive() && Hitter){DirectionalHitReact(Hitter->GetActorLocation()); // 播放受击动画}else Die(); // 如果已死亡则直接进入死亡流程PlayHitSound(ImpactPoint);      // 播放受击音效SpawnHitParticles(ImpactPoint); // 播放受击粒子
}

具体如何实现方向性受击反应?UAttributeComponent又是什么组件?ReceiveDamage具体怎么实现的?

void ABaseCharacter::DirectionalHitReact(const FVector& ImpactPoint)
{FVector Forward = GetActorForwardVector();FVector ToHit = (ImpactPoint - GetActorLocation()).GetSafeNormal();float DotProduct = FVector::DotProduct(Forward, ToHit);// 判断前后左右if (DotProduct > 0.7f)PlayHitReactMontage("HitFront");else if (DotProduct < -0.7f)PlayHitReactMontage("HitBack");else{float CrossZ = FVector::CrossProduct(Forward, ToHit).Z;if (CrossZ > 0)PlayHitReactMontage("HitLeft");elsePlayHitReactMontage("HitRight");}
}

 在 GetHit_Implementation 函数中,传入了攻击者的位置(Hitter->GetActorLocation())。通过计算攻击者与被攻击者之间的相对方向(攻击向量和被攻击者朝向点积判断前后,叉积Z分量判断左右),判断攻击来自哪个方位。

UAttributeComponent 是一个自定义的Actor组件(UActorComponent的子类),用于管理角色的各种属性,如生命值(Health)、体力(Stamina)、灵魂、金币等。

ReceiveDamage(float Damage) 是 UAttributeComponent 的成员函数,用于处理角色受到的伤害。

void UAttributeComponent::ReceiveDamage(float Damage)
{Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
}

clamp函数就是“限制数值在区间内”,防止出现不合理的数值溢出或下溢。 

属性与成长

属性和成长系统通过UAttributeComponent组件实现,负责所有属性的存储、变化和成长逻辑。

属性这一块其实没啥好说的,就是写一个类,然后定义一系列需要管理的属性类,写明白具体各个属性和UI的协同,各个属性的添加或者扣除的逻辑方便其他的类来调用。

// 扣除生命值
void UAttributeComponent::ReceiveDamage(float Damage)
{Health = FMath::Clamp(Health - Damage, 0.f, MaxHealth);
}// 扣除体力
void UAttributeComponent::UseStamina(float StaminaCost)
{Stamina = FMath::Clamp(Stamina - StaminaCost, 0.f, MaxStamina);
}// 恢复体力
void UAttributeComponent::RegenStamina(float DeltaTime)
{Stamina = FMath::Clamp(Stamina + StaminaRegenRate * DeltaTime, 0.f, MaxStamina);
}// 增加灵魂
void UAttributeComponent::AddSouls(int32 NumberOfSouls)
{Souls += NumberOfSouls;
}

升级(Level Up)系统的核心实现思路就是:当角色升级时,根据设定的成长公式或倍率,提升各项属性,并同步到角色的属性组件中。 

武器装备与切换

关于武器和装备,我们主要考虑这么几个部分:如何拾取武器,如何装备武器,如何切换武器,以及其背后带来的属性变化。

// 武器蓝图或C++类中的碰撞事件
void OnOverlapBegin(AActor* OverlappedActor, AActor* OtherActor)
{if (OtherActor->HasTag("Player")){ShowPickupUI(); // 弹出“按E拾取”提示if (PlayerPressedE()){Player->AddWeaponToInventory(this); // 添加到玩家背包或装备栏Destroy(); // 销毁场景中的武器实例}}
}

当玩家靠近武器时,通过碰撞检测触发拾取逻辑,弹出拾取提示并在玩家按下交互键后将武器添加到背包或装备栏,并销毁场景中的武器实例。

// 玩家装备武器时
void EquipWeapon(AWeapon* Weapon)
{FName SocketName = "hand_r_socket"; // 右手插槽Weapon->AttachToComponent(GetMesh(), SocketName); // 绑定到角色骨骼的SocketEquippedWeapon = Weapon;UpdatePlayerStats(Weapon->GetWeaponStats()); // 根据武器属性更新玩家属性
}

装备武器时,将武器的Mesh绑定到角色骨骼的指定Socket(如右手),实现武器与角色动画同步,并根据武器属性实时更新玩家的相关属性。 

// 玩家切换武器时
void SwitchWeapon(AWeapon* NewWeapon)
{if (EquippedWeapon){EquippedWeapon->DetachFromActor(); // 卸下当前武器}EquipWeapon(NewWeapon); // 装备新武器PlaySwitchWeaponEffect(); // 播放切换动画/音效
}

切换武器时,先卸下当前装备的武器,再将新武器绑定到Socket,并播放相应的切换反馈,确保装备和属性的实时同步与视觉表现的流畅。

而关于切换武器造成的数据变化,我们的每个武器类的具体实现就很重要了:

在本项目中,武器类通常是AWeapon,它继承自AItem,并扩展了装备、碰撞、伤害等功能。

// Weapon.h
class AWeapon : public AItem
{// ...UPROPERTY(EditAnywhere, Category = "Weapon Properties")float Damage = 20.f; // 武器伤害UPROPERTY(EditAnywhere, Category = "Weapon Properties")USoundBase* EquipSound; // 装备音效UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")UBoxComponent* WeaponBox; // 武器碰撞体// 其他如特效、攻击范围、攻击速度等属性// ...
};

一般的做法是:将武器的所有数值和配置集中存储在一个数据资产(Data Asset)中,如UWeaponDataAsset,每个武器蓝图实例只需引用一个数据资产,便于批量管理和扩展。

// WeaponDataAsset.h
UCLASS()
class UWeaponDataAsset : public UPrimaryDataAsset
{GENERATED_BODY()
public:UPROPERTY(EditAnywhere)float Damage;UPROPERTY(EditAnywhere)float AttackSpeed;UPROPERTY(EditAnywhere)USoundBase* EquipSound;// 其他属性
};

 具体的读取方法:

// Weapon.h
UCLASS()
class AWeapon : public AItem
{GENERATED_BODY()
public:// ...UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Weapon Properties")UWeaponDataAsset* WeaponData;// ...
};
// Weapon.cpp
void AWeapon::BeginPlay()
{Super::BeginPlay();if (WeaponData){// 读取数据资产中的属性Damage = WeaponData->Damage;AttackSpeed = WeaponData->AttackSpeed;EquipSound = WeaponData->EquipSound;// 你可以将这些值赋给本地变量,或者直接用WeaponData->Damage等}
}

这里我补充一下UE中关于蓝图和C++脚本的区别:

 蓝图(Blueprint)是Unreal Engine(UE)独有的可视化脚本系统,本质上是对C++类的可视化扩展和包装。

如何理解这里的编译执行和解释执行呢?

 这里补充一下Lua和C#:

C#的执行方式确实比较特别。它不是像C++那样直接把源码编译成机器码,而是先将源码编译成中间语言(IL,Intermediate Language),然后在程序运行时由虚拟机(如.NET CLR或Mono)通过JIT(Just-In-Time)即时编译,把中间语言动态转换成机器码执行。这样做的好处是,C#的运行效率比纯解释型语言(如Lua、蓝图)高很多,同时又保留了跨平台和一定的灵活性。缺点是首次运行某段代码时会有JIT编译的开销,但之后如果代码未变,运行速度接近编译型语言。

而Lua和UE蓝图则属于典型的解释执行:它们通常把源代码或节点转换成中间格式(字节码),然后由虚拟机逐行(Lua)或逐节点(蓝图)解释执行。这样虽然灵活、易于热更新,但运行效率比C#和C++低。

既然都说到这里了,我们为何不学习一下Mono具体是什么?

这里我们又引出一个问题了:Mono内存是什么?不要问我为什么问这个问题,因为我面试刚被提问。

 

那既然都了解了Unity的内存管理机制,怎么能不来了解了解UE的呢?

  • UObject GC:定期扫描所有UObject引用树,自动回收无主对象,防止内存泄漏。
  • 资源自动管理:资源对象(如UTexture、USoundWave等)由UE资源系统自动加载和释放。
  • 手动管理:非UObject的C++对象、第三方库对象等,需开发者手动释放。

Unreal Engine的内存分层主要包括:UObject GC层(自动回收)、原生C++内存(手动管理)、资源内存(自动/手动管理)等。它不是纯C++的“野生”内存管理,而是结合了反射、GC和资源系统的现代化内存管理体系,既有高性能也有较好的安全性。

一句话总结:UE会自动回收所有UObject及其资源对象的内存(通过GC和资源管理系统),而非UObject的普通C++对象和第三方内存则需要你手动管理和释放。

敌人AI与Boss

敌人AI需求的功能无非巡逻、感知玩家、状态机切换、攻击动画等等,通常来说会用行为树实现。

这里我想先介绍一下关于状态机和行为树的区别:

在UE中具体如何实现状态机和行为树呢?

答案当然是有现成的。

在Unreal Engine(UE)中,状态机通常通过动画蓝图的可视化状态机系统实现角色动画的切换(如待机、奔跑、攻击、死亡等),而游戏逻辑中的状态机则多用枚举变量配合蓝图或C++代码实现状态的判断与切换;行为树则通过内置的Behavior Tree和Blackboard资源,在可视化编辑器中拖拽节点搭建AI决策逻辑,并由AIController加载和运行,支持复杂的AI行为、条件判断和优先级控制,两者都可以结合蓝图和C++进行自定义扩展。

如何抉择呢?

这个项目中,我们的普通敌人使用简单的状态机而BOSS则使用行为树实现。

状态机的内容我就不再赘述,我就随便拿一部分内容举例:

// 状态管理
void AEnemy::Tick(float DeltaTime)
{if (IsDead()) return;if (EnemyState > EEnemyState::EES_Patrolling){CheckCombatTarget(); // 追击或攻击}else{CheckPatrolTarget(); // 巡逻}
}// 感知玩家
void AEnemy::PawnSeen(APawn* SeenPawn)
{EnemyState = EEnemyState::EES_Chasing;CombatTarget = SeenPawn;
}// 攻击逻辑
void AEnemy::Attack()
{if (CanAttack()){PlayAttackMontage();EnemyState = EEnemyState::EES_Attacking;}
}// 受击与死亡
void AEnemy::GetHit_Implementation(const FVector& ImpactPoint, AActor* Hitter)
{ShowHealthBar();HandleDamage(DamageAmount);if (!IsAlive()){Die();}
}

状态机会在在Tick函数中根据状态执行不同逻辑,这里的Tick函数有必要补充一下:

当然,既然聊到生命周期函数,我们当然不能绕开一个特殊的存在——协程

很遗憾UE没有Unity类似的协程机制,但是UE可以直接设置定时器或者通过异步任务来执行异步操作。 

补充一下这个执行顺序的问题,问就是刚被面试问到然后不知道。

然后是具体行为树的实现,这个东西作为一个可视化编辑的组件,用文字显然不太好陈述,我这里放一个官方的教程链接,大家自行查看吧:虚幻引擎行为树快速入门指南 | 虚幻引擎 5.6 文档 | Epic Developer Community

然后这里我还需要再补充一个内容,关于UE的敌人的感知系统:

AI感知系统是Unreal Engine为AI角色提供的一套“感知世界”的机制,它让AI能够像人一样“看到”、“听到”或“感受到”玩家和其他目标,从而实现如发现玩家、追击、躲避、响应声音等智能行为,大大简化了复杂AI的开发流程。

其用法如下:

其执行流程如下: 

  • 感知组件会在每一帧自动检测周围环境,根据配置的感知类型(如Sight、Hearing)扫描目标。
  • 视觉感知会检测所有在视野范围和角度内、未被遮挡的目标。
  • 听觉感知会检测范围内的声音事件(如玩家奔跑、开枪等)。
  • 检测到目标后,感知系统会生成“感知刺激”(Stimulus),并通过回调函数通知AIController或角色。
  • AI可以根据感知到的刺激类型、强度、持续时间等信息,做出相应的决策(如追击、警觉、逃跑等)。

在Unreal Engine中,只需给敌人添加感知系统(如AIPerceptionComponent),并给玩家等目标添加刺激源(如AIPerceptionStimuliSourceComponent),就能让敌人自动感知玩家。当敌人感知到刺激源时,感知系统会生成刺激(Stimulus),并通过回调函数(如OnTargetPerceptionUpdated)通知AI。你可以在回调中更新黑板(Blackboard)中的变量(如“TargetActor”),行为树会根据黑板变量自动切换到追击、攻击等相关节点,实现完整的智能敌人AI感知与决策流程。

物品与交互

我们所有的物品继承自一个自定义的类AItem类,而AItem类继承自AActor类。

物品具体的交互逻辑正如我之前所言,通过碰撞检测来做。

// 物品拾取
void AItem::OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, ...)
{IPickupInterface* PickupInterface = Cast<IPickupInterface>(OtherActor);if (PickupInterface){PickupInterface->SetOverlappingItem(this); // 玩家可拾取}
}// 玩家按下E键
void ASlashCharacter::EKeyPressed()
{if (OverlappingItem){OverlappingItem->OnPickedUp(this); // 执行拾取逻辑}
}

这里又不得不提一下Unity和UE的碰撞检测相关的函数对比了:

大致的角度上来说,Unity中的Collision就是UE中的Hit,Unity中的Trigger就是UE中的Overlap。 

环境与关卡

具体如何设计环境和关卡我不介绍,那是策划的内容,我在这里主要介绍的是UE中相关的组件。

首先,UE的每一个场景就是一个关卡(.umap文件),UE支持关卡的流式加载(Level Streaming):

开发者可以在主关卡中通过蓝图节点(如“Load Stream Level”和“Unload Stream Level”)或C++代码(如UGameplayStatics::LoadStreamLevel)主动控制子关卡的加载和卸载。通常在玩家靠近某个区域、触发某个事件或满足特定条件时,动态加载需要的子关卡,离开时再卸载,适合需要精确控制关卡内容的场景切换和事件驱动型关卡管理。

又或者在主关卡中放置Level Streaming Volume体积,并将其与子关卡关联。当玩家进入该体积区域时,关联的子关卡会自动加载,离开时自动卸载。无需手动编写逻辑,适合大世界、房间、地牢等区域化场景的无缝切换,极大简化了大地图的内容管理和性能优化。 

更详细的关卡流式加载的内容可以看这篇知乎:(99+ 封私信 / 80 条消息) 虚幻UE5基础知识-关卡流送(Level Streaming) - 知乎

我们还可以实现Packed Level Instance,将关卡作为实例化对象嵌入到主世界,实现地牢、房间等模块化设计。

虚幻引擎中的关卡实例化 | 虚幻引擎 5.6 文档 | Epic Developer Community

最后的世界分区嘛,我其实都已经介绍过了,这个内容比较复杂,我这里多放几篇文献:

虚幻引擎中的世界分区 | 虚幻引擎 5.6 文档 | Epic Developer Community

(99+ 封私信 / 80 条消息) UE5 World Partition不完全指南 - 知乎

然后是环境方面的组件,这个部分我个人觉得不太涉及到代码,大家自行查阅吧。

用户界面与体验

UE中有专门的针对UI的蓝图类:UMG(Unreal Motion Graphics),所有UI界面均通过UMG蓝图(Widget Blueprint)实现,支持可视化编辑、动画、交互等。

我们先来了解一下这个UMG类:

输入与架构支持

输入我们之前已经聊过了,就是我们的Enhanced Input System,基于输入映射上下文,更灵活也更具有扩展性。

这里的架构支持指的则是我们这个项目中集成了Action System与GameplayTags这两个架构。

在虚幻引擎中使用Gameplay标签 | 虚幻引擎 5.5 文档 | Epic Developer Community

存档与数据管理

说到具体数据的存储和存档系统的实现,Unity和UE的方法是否相同呢?

我们来专门聊聊UE实现存档的方法:

总结来说就是,USaveGame子类+UPROPERTY实现动态存档,Data Asset用于静态配置,.ini文件用于简单设置和参数。

http://www.lryc.cn/news/595564.html

相关文章:

  • 《画布角色的双重灵魂:解析Canvas小游戏中动画与碰撞的共生逻辑》
  • Spring Boot注解详解
  • 影刀 RPA:批量修改 Word 文档格式,高效便捷省时省力
  • 通俗易懂卷积神经网络(CNN)指南
  • 海康威视视觉算法岗位30问及详解
  • 多片RFSoC同步,64T 64R
  • STM32小实验四--按键控制LED灯
  • Neo4j 5.x版本的导出与导入数据库
  • 车载软件架构 --- 软件开发面临的问题
  • DAY17 常见聚类算法
  • Spring AI 集成阿里云百炼与 RAG 知识库,实现专属智能助手(框架思路)
  • SpringSecurity 详细介绍(认证和授权)
  • 广东省省考备考(第五十二天7.21)——数量、判断推理(听课后强化训练)
  • 【qml-3】qml与c++交互第二次尝试(类型方式)
  • Android MTK平台预置多张静态壁纸
  • LinkedList与链表(单向)(Java实现)
  • 跨端分栏布局:从手机到Pad的优雅切换
  • 遗像照片尺寸要求及手机制作打印方法
  • DIDCTF-2021第三届长安杯(检材一)
  • LeetCode 每日一题 2025/7/14-2025/7/20
  • Android Studio 的 Gradle 究竟是什么?
  • 力扣刷题 -- 100.相同的树
  • 4.Java创建对象有几种方式?
  • repmgr+pgbouncer实现对业务透明的高可用切换
  • ANSYS 2025 R1软件下载及安装教程|附安装文件
  • 【实战】Dify从0到100进阶--文档解读(10)参数提取HTTP节点
  • 2025年一区SCI-回旋镖气动椭圆优化算法Boomerang Aerodynamic Ellipse-附Matlab免费代码
  • IFN影视官网入口 - 4K影视在线看网站|网页|打不开|下载
  • 【智能协同云图库】智能协同云图库第二期:基于腾讯云 COS 对象存储—开发图片各功能模块
  • next.js刷新页面时二级菜单展开状态判断