UE4垃圾回收空指针导致的崩溃

问题描述:毕业设计中所遇到的疑难杂症。

玩家角色类上保存着大小恒为2的结构体数组,结构体数组中含有指针类型的变量,代码如下。

结构体数组:

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	TArray<FSlotInfo> WeaponSlots;

结构体:

USTRUCT(BlueprintType)
struct  FSlotInfo
{
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(BlueprintReadWrite)
	int32 WeaponID;

	UPROPERTY(BlueprintReadWrite)
	ACDWeaponBase* SlotWeaponPtr;

	UPROPERTY(BlueprintReadWrite)
	FName SocketName;

	UPROPERTY(BlueprintReadWrite)
	bool bIsUsed;

	FSlotInfo(FName soltName)
	{
		WeaponID=-1;
		SlotWeaponPtr=nullptr;
		this->SocketName=soltName;
		bIsUsed=false;
		WeaponID=-1;
	}

	FSlotInfo()
	{

	}

	bool operator==(const FSlotInfo OtherSlot) {
		return this->WeaponID==OtherSlot.WeaponID;
	}
};

解释:此结构体数组对应玩家的武器插槽(受到动画资源限制,导致角色初始就携带一把初始武器,大小写死恒为2)。此前,未增加武器指针时,只保存武器ID,会出现角色只能拾取不同的武器这个Bug。后续为了支持角色拾取相同的武器,在结构体中增加武器指针属性,用于区分武器实例。

报错信息:

LogOutputDevice: Error: Ensure condition failed: bIsValidObjectReference [File:D:/Build/++UE4/Sync/Engine/Source/Runtime/CoreUObject/Private/UObject/UObjectGlobals.cpp] [Line: 3453] Invalid object referenced by the PersistentFrame: 0x00007ffecfd84db1 (Blueprint object: Blueprint /Game/Blueprint/Character/BP_Michelle.BP_Michelle, ReferencingProperty: ObjectProperty /Script/FrontLine.SlotInfo:SlotWeaponPtr, Instance: BP_Michelle_C /Game/Blueprint/Game/Map/UEDPIE_0_KillAll.KillAll:PersistentLevel.BP_Michelle_C_0, Address: 0x0000010981295e0 0) - If you have a reliable repro for this, please contact the development team with it.

问题分析和解决方案:

这个Bug复现率极高,但又不是每次运行都会出现。且出现后,会直接杀死UE进程。

通过程序崩溃信息,找出报错位置:

if (!ensureMsgf(bIsValidObjectReference
			, TEXT("Invalid object referenced by the PersistentFrame: 0x%016llx (Blueprint object: %s, ReferencingProperty: %s, Instance: %s, Address: 0x%016llx) - If you have a reliable repro for this, please contact the development team with it.")
			, (int64)(PTRINT)Object
			, *GetBlueprintObjectNameLambda(GetSerializingObject())
			, GetSerializedProperty() ? *GetSerializedProperty()->GetFullName() : TEXT("NULL")
			, GetSerializedDataContainer() ? *GetSerializedDataContainer()->GetFullName() : TEXT("NULL")
			, (int64)(PTRINT)&Object))
		{
			// clear the property value (it's garbage)... the ubergraph-frame
			// has just lost a reference to whatever it was attempting to hold onto
			Object = nullptr;
		}

发现程序卡死在了这个Ensure,再根据Ensure的参数,找到参数的含义:

protected:
	virtual FArchive& operator<<(UObject*& Object) override
	{
#if !(UE_BUILD_TEST || UE_BUILD_SHIPPING)
		const bool bIsValidObjectReference = (Object == nullptr || Object->IsValidLowLevelFast());

定位到大致问题,因为UE4的垃圾回收机制,会回收无用的标记过的无用变量,而判断无用变量的条件根据上述代码所示就是是否为空指针。

分析到这里,就不难发现导致程序崩溃的原因。角色身上未拾取武器时,武器插槽数组中的指针均为nullptr,但是我的想法是一直保留数组中的空指针,因为游戏中玩家可以反复拾取和丢弃武器。但是UE4的GC机制会判断武器插槽中指针为空时,就把内存回收掉了,导致我下一次使用不了该指针变量。

解决方案:

首先声明,我的解决方案很粗糙,因为要赶毕设的时间。作下简单分享,因为角色具有初始武器,且该武器不能被丢弃,因此玩家身上一直保存着该武器的有效指针。所以我的做法时,在玩家未拾取武器或丢弃武器时,不将武器插槽数组的指针置空,而是指向角色的初始武器,这样UE4的GC机制就会跳过该指针变量。等角色拾取武器时,再将指针重新赋值。这样做后,并没有发现此类的崩溃错误。代码如下:

// 如果丢的是武器插槽1的武器
	for (int i = 0; i < WeaponSlots.Num(); i++)
	{
		if (WeaponSlots[i].SlotWeaponPtr == DropWeapon && WeaponSlots[i].SlotWeaponPtr)
		{
			// 先将当前武器插槽空余出来
			WeaponSlots[i].bIsUsed = false;
			WeaponSlots[i].WeaponID = -1;
			WeaponSlots[i].SlotWeaponPtr = PistolWeapon;

			// 如果丢的武器插槽1的武器,将武器2移动到武器插槽1,空出插槽2以便拾取武器
			if (i == 0)
			{
				if(WeaponSlots[i + 1].bIsUsed)
				{
					WeaponSlots[i].bIsUsed = WeaponSlots[i + 1].bIsUsed;
					WeaponSlots[i].WeaponID = WeaponSlots[i + 1].WeaponID;
					WeaponSlots[i].SlotWeaponPtr = WeaponSlots[i + 1].SlotWeaponPtr;

					WeaponSlots[i].SlotWeaponPtr->AttachToComponent(GetMesh(),
						FAttachmentTransformRules::SnapToTargetNotIncludingScale,
						WeaponSlots[i].SocketName);

					WeaponSlots[i + 1].bIsUsed = false;
					WeaponSlots[i + 1].WeaponID = -1;
					WeaponSlots[i + 1].SlotWeaponPtr = PistolWeapon;
				}
			}
		}
	}

总结:上述解决方案只是为了图快,但并不建议。时间充裕的话,可以重构武器系统。或者在武器基类上定义一个静态变量用作武器的计数,每次实例化武器的时候,将该计数值加一,并保存为区分相同武器不同实例的ID,在角色武器插槽数组中保存该ID,也可区分。

(以上只是一些简单分享)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值