109. UE5 GAS RPG 实现检查点的存档功能

embedded/2024/11/19 18:41:46/

在这一篇文章里,我们接着实现存档的功能,保存当前玩家的生成位置,游戏里有很多中方式去实现玩家的位置存储,这里我们采用检查点的方式,当玩家接触到当前检查点后,我们可以通过检查点进行保存玩家的状态,后续也能够实现检查点移动玩家等等功能。

实现定义角色生成位置

存档加载关卡后,需要一个出生位置,如果场景里有多个PlayerStart,我们如何确定让角色在哪个PlayerStart生成呢?
答案是,我们可以为每个PlayerStart设置标签,然后覆写GameMode的选择初始点的函数来实现。

首先,我们实现对数据的全局存储,这里需要使用到GameInstance,我们将其作为父类,实现一个派生类,来实现自定义的需求。
在这里插入图片描述
设置自定义命名
在这里插入图片描述
在类里,我们存储几个值,一个是切换关卡时需要获取的PlayerStart的标签命名,另外就是如果需要保存,所需的存档名称和索引。

UCLASS()
class RPG_API URPGGameInstance : public UGameInstance
{GENERATED_BODY()public://角色进入关卡后默认生成的PlayerStart的TagUPROPERTY()FName PlayerStartTag = FName();//当前使用的或后续保存内容到的存档名称UPROPERTY()FString LoadSlotName = FString();//当前使用活后续保存的存档索引UPROPERTY()int32 LoadSlotIndex = 0;
};

然后在我们自定义的GameMode里增加一个参数用于设置玩家生成的PlayerStart的标签

	//角色切换关卡后默认生成位置的PlayerStart的标签UPROPERTY(EditDefaultsOnly)FName DefaultPlayerStartTag;//覆写父类的选择PlayerStart函数,修改为可以通过Tag获取生成位置virtual AActor* ChoosePlayerStart_Implementation(AController* Player) override;

函数实现这里,我们会获取到关卡里的所有的PlayerStart,然后从GameInstance获取需要生成的标签,遍历获取到对应的PlayerStart生成,所以,只需要在进入关卡前,将GameInstance的标签修改了然后进入场景时,就可以自动寻找对应的PlayerStart去生成。

AActor* ARPGGameMode::ChoosePlayerStart_Implementation(AController* Player)
{const URPGGameInstance* RPGGameInstance = Cast<URPGGameInstance>(GetGameInstance());//获取关卡里的所有PlayerStart实例TArray<AActor*> Actors;UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), Actors);if(Actors.Num() > 0){//获取到第一个实例对象AActor* SelectedActor = Actors[0];for(AActor* Actor : Actors){if(APlayerStart* PlayerStart = Cast<APlayerStart>(Actor)){//判断PlayerStart的Tag设置是否为指定的Tagif(PlayerStart->PlayerStartTag == RPGGameInstance->PlayerStartTag){SelectedActor = PlayerStart;break;}}}return SelectedActor;}return nullptr;
}

添加PlayerStart标签配置

我们需要在存档里实现对关卡里的开始标签的存储,在读取存档进入关卡时,可以明确知道角色需要在哪里生成。
所以,我们需要在LoadScreenSaveMode(存档类)和存档ViewModel视图模型里增加PlayerStart标签配置属性。

	//存储玩家关卡出生位置的标签UPROPERTY()FName PlayerStartTag;

在创建新存档时,使用GameMode设置的默认PlayerStart标签
在这里插入图片描述
接着在GameMode存储存档时,将存档的视图模型的中的PlayerStart标签存储到存档
在这里插入图片描述
存储没问题了,就是读取存档时,将存档里存储的PlayerStart标签设置给存档的视图模型
在这里插入图片描述
最后,在我们在加载界面视图模型进入游戏的函数里,在调用加载关卡之前,将PlayerStart标签存储到GameInstance里,进入关卡后,然后再通过我们覆写的函数获取对应标签的PlayerStart

void UMVVM_LoadScreen::EnterGameButtonPressed(const int32 Slot)
{ARPGGameMode* RPGGameMode = Cast<ARPGGameMode>(UGameplayStatics::GetGameMode(this));//设置全局数据,方便后续使用URPGGameInstance* RPGGameInstance = Cast<URPGGameInstance>(RPGGameMode->GetGameInstance());RPGGameInstance->LoadSlotName = LoadSlots[Slot]->GetSlotName();RPGGameInstance->LoadSlotIndex = LoadSlots[Slot]->SlotIndex;RPGGameInstance->PlayerStartTag = LoadSlots[Slot]->PlayerStartTag;//进入场景RPGGameMode->TravelToMap(LoadSlots[Slot]);
}

创建蓝图

接下来,我们编译打开UE,在BP_GameMode里设置默认选择的PlayerStart的标签
在这里插入图片描述
接着,我们基于GameInstance类创建一个蓝图
在这里插入图片描述
设置命名
在这里插入图片描述
在项目设置里,将默认的GameInstance修改为我们所需的GameInstance
在这里插入图片描述
最后,我们在场景里设置PlayerStart的标签,注意,如果多个PlayerStart设置了相同的对应的标签,角色将在第一个获取的PlayerStart的位置生成。
在这里插入图片描述

创建检查点

在正常游戏流程里,为了保存游戏进度,开发者会使用某种方式让进度保存下来,比如到达某个进度后自动保存,又或者像生化危机里的打字机。
这里,我们将实现一种检查点的类,在角色接触后,自动保存当前进度。
在这里插入图片描述
命名为检查点类,我们将在里面增加一些额外的内容。
在这里插入图片描述
在类里,我们将增加两个属性,用于显示检查点的模型和触发保存游戏的碰撞盒子

private://检查点显示的模型UPROPERTY(VisibleAnywhere)TObjectPtr<UStaticMeshComponent> CheckpointMesh;//检查点模型使用的碰撞体UPROPERTY(VisibleAnywhere)TObjectPtr<USphereComponent> Sphere;

然后增加一个函数,用于玩家角色和碰撞球碰撞后的逻辑处理

	/*** 球碰撞体和物体发生碰撞后的回调* @param OverlappedComponent 发生重叠事件的自身的碰撞体对象* @param OtherActor 目标的actor对象* @param OtherComp 目标的碰撞体组件* @param OtherBodyIndex 目标身体的索引* @param bFromSweep 是否为瞬移检测到的碰撞* @param SweepResult 如果位置发生过瞬移(直接设置到某处),两个位置中间的内容会记录到此对象内*/UFUNCTION()virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

接着增加一个函数,玩家角色碰撞后的函数处理,主要里面是创建了一个新的材质实例,修改自发光,表示检查点已经激活。

	//当玩家角色和检测点产生碰撞后,检查点被激活触发此函数void HandleGlowEffects();

由于自发光亮起需要时间轴,这个比较方便在蓝图里实现,我们再增加一个需蓝图实现的函数。

	/*** 检查点激活后的处理,需要在蓝图中对其实现* @param DynamicMaterialInstance 传入检查点模型的材质实例*/UFUNCTION(BlueprintImplementableEvent)void CheckpointReached(UMaterialInstanceDynamic* DynamicMaterialInstance);

以下是整个.h文件

// 版权归暮志未晚所有。#pragma once#include "CoreMinimal.h"
#include "GameFramework/PlayerStart.h"
#include "CheckPoint.generated.h"class USphereComponent;
/*** */
UCLASS()
class RPG_API ACheckPoint : public APlayerStart
{GENERATED_BODY()public://构造函数ACheckPoint(const FObjectInitializer& ObjectInitializer);protected:virtual void BeginPlay() override;/*** 球碰撞体和物体发生碰撞后的回调* @param OverlappedComponent 发生重叠事件的自身的碰撞体对象* @param OtherActor 目标的actor对象* @param OtherComp 目标的碰撞体组件* @param OtherBodyIndex 目标身体的索引* @param bFromSweep 是否为瞬移检测到的碰撞* @param SweepResult 如果位置发生过瞬移(直接设置到某处),两个位置中间的内容会记录到此对象内*/UFUNCTION()virtual void OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);/*** 检查点激活后的处理,需要在蓝图中对其实现* @param DynamicMaterialInstance 传入检查点模型的材质实例*/UFUNCTION(BlueprintImplementableEvent)void CheckpointReached(UMaterialInstanceDynamic* DynamicMaterialInstance);//当玩家角色和检测点产生碰撞后,检查点被激活触发此函数void HandleGlowEffects();
private://检查点显示的模型UPROPERTY(VisibleAnywhere)TObjectPtr<UStaticMeshComponent> CheckpointMesh;//检查点模型使用的碰撞体UPROPERTY(VisibleAnywhere)TObjectPtr<USphereComponent> Sphere;
};

在cpp里,我们对函数进行实现,首先在构造函数里,我们实例化模型和碰撞体。

ACheckPoint::ACheckPoint(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
{//关闭帧更新PrimaryActorTick.bCanEverTick = false;//创建检测点显示模型CheckpointMesh = CreateDefaultSubobject<UStaticMeshComponent>("CheckpointMesh");CheckpointMesh->SetupAttachment(GetRootComponent());CheckpointMesh->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); //设置查询并产生物理CheckpointMesh->SetCollisionResponseToChannels(ECR_Block); //设置阻挡所有物体与其重叠//设置球碰撞体Sphere = CreateDefaultSubobject<USphereComponent>("Sphere");Sphere->SetupAttachment(CheckpointMesh);Sphere->SetCollisionEnabled(ECollisionEnabled::QueryOnly); //设置其只用作查询使用Sphere->SetCollisionResponseToChannels(ECR_Ignore); //设置其忽略所有碰撞检测Sphere->SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap); //设置其与Pawn类型物体产生重叠事件
}

在游戏开始时,绑定球碰撞体的重叠函数

void ACheckPoint::BeginPlay()
{Super::BeginPlay();//绑定重叠事件Sphere->OnComponentBeginOverlap.AddDynamic(this, &ACheckPoint::OnSphereOverlap);
}

接着实现重叠函数,在触发重叠时,我们需要实现保存当前的检查点标签,然后在调用碰撞后处理函数

void ACheckPoint::OnSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{//if(OtherActor->ActorHasTag("Player")) //如果只需要判断是不是玩家角色通过标签判断即可if(OtherActor->Implements<UPlayerInterface>()){//修改存档当的检测点IPlayerInterface::Execute_SaveProgress(OtherActor, PlayerStartTag);//如果与碰撞体重叠的是HandleGlowEffects();}
}

然后我们取消碰撞检测,提升性能,并创建一个新的材质实例,调用蓝图函数实现渐变发光效果。

void ACheckPoint::HandleGlowEffects()
{//取消碰撞检查Sphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);//创建一个新材质实例,修改效果UMaterialInstanceDynamic* DynamicMaterialInstance = UMaterialInstanceDynamic::Create(CheckpointMesh->GetMaterial(0), this);CheckpointMesh->SetMaterial(0, DynamicMaterialInstance);CheckpointReached(DynamicMaterialInstance); //触发检查点修改材质后的回调
}

编译代码,打开UE,创建一个基于类的蓝图
在这里插入图片描述
在检测点里,我们需要修改碰撞体大小和检查点的模型
在这里插入图片描述
大致效果如下,可以按需设置
在这里插入图片描述
在平视角,我们需要将PlayerStart和模型水平,这样保证放置的时候,防止PlayerStart的位置靠下,生成角色生成到地面以下。
在这里插入图片描述
拖入场景中,点击End建,检测点将会自动附着到地面,青蓝色箭头是玩家在检查点生成位置和朝向,黄色碰撞球是激活检测点范围。
在这里插入图片描述
接着,我们修改材质,增加自发光相关节点,设置GlowEnd最大亮度,以及GlowControl来控制进度,GlowControl值为1时,将达到亮度的最大值。
在这里插入图片描述
接着,我们创建一个实例,去调节对应的参数。
在这里插入图片描述
接着,在蓝图里,实现碰撞函数回调,使用时间轴修改GlowControl
在这里插入图片描述
在时间轴里去修改更新的值
在这里插入图片描述
我们将放置到场景里的检查点的设置其标签,可以用来实现保存通过标签去寻找位置。
在这里插入图片描述

实现PlayerStart标签的保存

我们要实现玩家角色在场景中接触到检查点后,更新存档,将当前的检查点的值保存到存档里。
首先在GameMode类里增加两个函数,一个用于获取当前使用的存档,另一个是将修改后的存档保存下来。

	//获取到当前游戏进行中所使用的存档数据ULoadScreenSaveGame* RetrieveInGameSaveData() const;/*** 保存游戏中的进度* @param SaveObject 需要保存的数据*/void SaveInGameProgressData(ULoadScreenSaveGame* SaveObject) const;

实现这里,我们可以在GameInstance身上获取到存档使用的Name和Index,通过这两项获取到存档数据。
保存函数这里,我们还需要使用存档的标签去修改GameInstance身上的PlayerStart的标签。然后保存。

ULoadScreenSaveGame* ARPGGameMode::RetrieveInGameSaveData() const
{const URPGGameInstance* RPGGameInstance = Cast<URPGGameInstance>(GetGameInstance());//从游戏实例获取到存档名称和索引const FString InGameLoadSlotName = RPGGameInstance->LoadSlotName;const int32 InGameLoadSlotIndex = RPGGameInstance->LoadSlotIndex;//获取已保存的存档数据return GetSaveSlotData(InGameLoadSlotName, InGameLoadSlotIndex);
}void ARPGGameMode::SaveInGameProgressData(ULoadScreenSaveGame* SaveObject) const
{URPGGameInstance* RPGGameInstance = Cast<URPGGameInstance>(GetGameInstance());//修改下一次复活的检测点RPGGameInstance->PlayerStartTag = SaveObject->PlayerStartTag;//从游戏实例获取到存档名称和索引const FString InGameLoadSlotName = RPGGameInstance->LoadSlotName;const int32 InGameLoadSlotIndex = RPGGameInstance->LoadSlotIndex;//保存存档UGameplayStatics::SaveGameToSlot(SaveObject, InGameLoadSlotName, InGameLoadSlotIndex);
}

接着在玩家角色接口这里增加一个函数,用于碰撞触发后保存存档使用

	//保存游戏进度UFUNCTION(BlueprintNativeEvent, BlueprintCallable)void SaveProgress(const FName& CheckpointTag);

在玩家基类里覆写

virtual void SaveProgress_Implementation(const FName& CheckpointTag) override;

实现这里,我们获取到GameMode,然后获取存档,修改存档数据,并保存回去。

void ARPGHero::SaveProgress_Implementation(const FName& CheckpointTag)
{if(const ARPGGameMode* GameMode = Cast<ARPGGameMode>(UGameplayStatics::GetGameMode(this))){//获取存档ULoadScreenSaveGame* SaveGameData = GameMode->RetrieveInGameSaveData();if(SaveGameData == nullptr) return;//修改存档数据SaveGameData->PlayerStartTag = CheckpointTag;//保存存档GameMode->SaveInGameProgressData(SaveGameData);}
}

我们在与检查点碰撞时,已经调用此函数,实现了存档的修改保存。
在这里插入图片描述
最后,我们在场景里多加几个检查点,来测试效果。创建文档,角色会生成在一个检查点上,然后我们走到另一个检查点上,重新进入游戏,查看角色下一次会不会生成在最后退出的检查点旁边,如果能够证明代码无误。
在这里插入图片描述

实现角色已经激活的检测点一直高亮

我们现在制作的当前检查点效果,还没有实现的一项是,让玩家已经激活的检查点一直保持高亮状态,这样,如果玩家迷路了,可以清除的得知这一段路之前探索过。
为了实现这个效果,我们需要将之前已经探索到的检查点都记录下来,然后在进入场景后,每次激活,将信息记录到存档里。在进入一个新关卡时,在GameMode的BeginPlayer会触发,我们会在此函数里处理检查点是否需要高亮。
首先,我们在SaveGame类增加一个参数,用于存储检查点数组,用于存储角色已经激活的检查点

	//当前已经激活的检测点UPROPERTY()TArray<FName> ActivatedPlayerStatTags = TArray<FName>();

然后将检查点类的激活函数修改为public,这样,可以在类以外调用
在这里插入图片描述
接着,我们增加一个私有函数用于高亮已经激活的检查点

private://高亮已经激活的检查点void HighlightEnabledCheckpoints(TArray<AActor*> CheckPoints) const;

函数实现,我们将获取存档,并遍历所有的检查点,如果检查点的Tag存在于已激活的数组内,我们将检查点进行高亮显示。

void ARPGGameMode::HighlightEnabledCheckpoints(TArray<AActor*> CheckPoints) const
{//获取存档ULoadScreenSaveGame* SaveGameData = RetrieveInGameSaveData();if(SaveGameData == nullptr) return;//遍历关卡内的所有的检查点,如果数组里存在,将高亮显示for(AActor* Actor : CheckPoints){if(ACheckPoint* CheckPoint = Cast<ACheckPoint>(Actor)){if(SaveGameData->ActivatedPlayerStatTags.Contains(CheckPoint->PlayerStartTag)){CheckPoint->HandleGlowEffects();}}}
}

在关卡打开后,生成角色时,我们调用此函数,进行场景关卡的检查点进行初始化高亮
在这里插入代码片
最后,还有保存已经激活的检查点,我们可以选择在保存角色的存档时候,将当前检查点的标签保存进去。
在这里插入图片描述
重点,你每个添加到关卡的检查点,要做到全局不同,也就是每个关卡的检查点都不要重名,我们可以考虑关卡+检查点的索引方式去设置检查点的tag。


http://www.ppmy.cn/embedded/138837.html

相关文章

QT<30> Qt中使鼠标变为转圈忙状态

前言&#xff1a;当我们在写软件时&#xff0c;在等待阻塞耗时操作时可以将鼠标变为忙状态&#xff0c;并在一段时间后恢复状态&#xff0c;可以用到GxtWaitCursor&#xff1a;Qt下基于RAII的鼠标等待光标类。 一、效果演示 二、详细代码 在项目中添加C文件&#xff0c;命名为…

c#加载shellcode

本地加载bin文件 SharpPELoader项目如下&#xff1a; using System; using System.IO; using System.Runtime.InteropServices;namespace TestShellCode {internal class Program{private const uint MEM_COMMIT 0x1000;private const uint PAGE_EXECUTE_READWRITE 0x40;pr…

Flink新版Source接口源码解析

目录 1. 前言 2. Source解析 2.1 Source类图 2.2 接口和方法说明 2.2.1 Source,> 3. SplitEnumerator解析 3.1 SplitEnumetator类图 3.2 类和方法说明 3.2.1 SplitEnumerator 3.2.2 SimpleVersionedSerializer 4. SourceReader解析 4.1 SourceReader类图 4.2 类…

Ekman理论回归

Scientific reportsEkman revisited: Surface currents to the left of the winds in the Northern HemisphereVagn Walfrid Ekman1905年的理论描述了地球旋转受到风的作用&#xff0c;摩擦边界层中的流场&#xff0c;北半球总是在海表风的右侧&#xff0c;南半球总是在海表风的…

Linux之进程(3)

Linux2.6内核进程调度队列 操作系统有分时操作系统&#xff0c;实时操作系统&#xff0c;实时操作系统主要应用于一些制造业工业等&#xff0c;需要快速响应&#xff0c;但是在互联网领域不会用实时操作系统 队列共有140个空间大小&#xff0c;但是我们不用考虑前面100个实时的…

HTTP 请求方式

深入理解 HTTP 请求方式 在 Web 开发中&#xff0c;HTTP 请求方式起着至关重要的作用。它们决定了客户端如何与服务器进行交互以及服务器如何响应这些请求。本文将深入探讨不同的 HTTP 请求方式及其应用场景。 一、GET 请求 GET 请求是最常见的 HTTP 请求方式之一。它用于从服…

单元测试时报错找不到@SpringBootConfiguration

找到问题出现原因&#xff1a; 错误表示 Spring Boot 在运行测试时无法找到 SpringBootConfiguration 注解。 通常&#xff0c;SpringBootTest注解用于加载 Spring Boot 应用上下文&#xff0c;但它需要找到一个带有SpringBootConfiguration&#xff08;或者Configuration&am…

RFC 2018 即《TCP Selective Acknowledgement Options》

RFC 2018 即《TCP Selective Acknowledgement Options》