UE5俯视角游戏案例代码查看

2024-04-07 21:12

本文主要是介绍UE5俯视角游戏案例代码查看,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

对于初学者来说,UE的项目案例是我们入手的最佳途径,首先代码量少,思路清晰,还能给你提供一个清晰的结构。所以,我创建了一个俯视角的官方案例,来查看一下官方的代码学习一下。

首先打开引擎,启动引擎
在这里插入图片描述
然后创建一个示例,这是ue自带的案例
在这里插入图片描述
打开源代码,看到案例就几个文件
在这里插入图片描述

TopDownProject

这个项目名称我设置的是TopDownProject,TopDownProject.h和TopDownProject.cpp就是自动生成的项目主文件,它不需要我们自己创建,案例中里面这两个文件主要在里面增加了一个打印日志的宏,宏的第一个参数是定义了名称,可以不一样,但是相同的不需要定义两遍。这样方便后面查看哪里报错。

DECLARE_LOG_CATEGORY_EXTERN(LogTopDownProject, Log, All);
DEFINE_LOG_CATEGORY(LogTopDownProject)

定义完宏以后,使用,需要使用UE_LOG去打印,这里我将输入映射上下文设置为空指针,然后打印

	if(DefaultMappingContext == nullptr){UE_LOG(LogTemplateCharacter, Error, TEXT("当前操作映射上下文未设置"));}

可以看到在输出日志里面显示打印的内容
ELogVerbosity 枚举类型通常包含以下几个级别(具体级别可能因UE版本而异):

  • Verbose:最详细的日志级别,通常用于调试目的,包含大量的信息。
  • Log:常规日志级别,用于记录程序运行时的正常消息。
  • Warning:警告级别,用于记录可能导致问题的情况,但不一定是错误。
  • Error:错误级别,用于记录程序运行时遇到的严重问题或异常。
  • Display:用于显示给用户的信息,通常出现在用户界面上。
  • Fatal:致命错误级别,通常用于记录程序无法继续运行的情况。
    在这里插入图片描述

GameMode

案例重新创建了一个GameMode
在这里插入图片描述
它文件头设置了minimalapi,是为了加快编译,也无法作为蓝图父类,作为直接设置无法修改的类。

UCLASS(minimalapi)

在里面只是在构造函数内做了一些处理

public:ATopDownProjectGameMode();

因为无法在UE里面去修改内容,所以它在构造函数内,重新设置了默认Pawn类和玩家控制器类,可以通过上图看到。这里面也教给我们如何在C++里面去获取对应的蓝图的方法。你也可以看到官方开发人员也写的不标准,下面的NULL虽然也不会出错,但是推荐修改为nullptr(空指针)

ATopDownProjectGameMode::ATopDownProjectGameMode()
{// 使用我们自定义的 PlayerController classPlayerControllerClass = ATopDownProjectPlayerController::StaticClass();// 设置默认的控制Pawn为我们自定义的蓝图创建的Characterstatic ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/TopDown/Blueprints/BP_TopDownCharacter"));if (PlayerPawnBPClass.Class != nullptr){DefaultPawnClass = PlayerPawnBPClass.Class;}// 设置控制器为我们创建蓝图PlayerControllerstatic ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerBPClass(TEXT("/Game/TopDown/Blueprints/BP_TopDownPlayerController"));if(PlayerControllerBPClass.Class != NULL){PlayerControllerClass = PlayerControllerBPClass.Class;}
}

剩下两个文件刚好是一个角色类和一个玩家控制类。

Character

在自定义角色类这里,类继承至ACharacter
在类里面创建了两个私有变量,用于存储相机和弹簧臂

private:/** Top down camera */UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))class UCameraComponent* TopDownCameraComponent;/** Camera boom positioning the camera above the character */UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))class USpringArmComponent* CameraBoom;

meta 这个参数用于控制属性在引擎和编辑器之间的交互方式。它还有其他设置的内容:

  • meta = (AllowPrivateAccess = “true”) 为变量即使是私有属性,也可以在UE里编辑器和访问
  • meta=(DisplayName=“自定义名称”) 用于在UE中自定义属性的显示名称
  • meta=(ToolTip=“这是一个提示信息”) 为属性提供编辑器中的工具提示,帮助解释属性的用途或如何设置它
  • meta=(EditCondition=“bSomeCondition”) 定义一个布尔表达式,用于控制属性是否可以在UE中编辑
  • meta=(ClampMin=“0.0”, ClampMax=“100.0”) 用于限制编辑器中可编辑属性的最小值和最大值
  • meta=(Category=“MyCustomCategory”) 指定属性在编辑器细节面板中所属的类别
  • meta=(AdvancedDisplay) 用于将属性标记为高级属性,使其在编辑器中默认隐藏,但可以通过点击“显示更多”或类似按钮来显示

然后又增加了两个对属性的获取公共函数,FORCEINLINE 内联函数是在调用点直接插入函数体代码的函数,而不是进行常规的函数调用。这可以减少函数调用的开销,从而可能提高执行速度,但也可能增加生成的代码大小。

	/** Returns TopDownCameraComponent subobject **/FORCEINLINE class UCameraComponent* GetTopDownCameraComponent() const { return TopDownCameraComponent; }/** Returns CameraBoom subobject **/FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }

在TopDownProjectCharacter的cpp文件中,只是在构造函数中初始化了一些内容,首先设置角色的胶囊体,然后设置角色移动相关的内容,并创建了相机弹簧臂和相机,最后设置帧回调。

ATopDownProjectCharacter::ATopDownProjectCharacter()
{// 设置角色胶囊体的尺寸GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);// 禁止相机随角色旋转bUseControllerRotationPitch = false;bUseControllerRotationYaw = false;bUseControllerRotationRoll = false;// Configure character movementGetCharacterMovement()->bOrientRotationToMovement = true; // 当设置为true时,角色的前方将自动朝向其移动的方向GetCharacterMovement()->RotationRate = FRotator(0.f, 640.f, 0.f); //控制角色旋转的速率GetCharacterMovement()->bConstrainToPlane = true; //当设置为true时,角色的移动将被约束在一个特定的平面上,通常是地面。GetCharacterMovement()->bSnapToPlaneAtStart = true; //游戏开始时,角色被吸附到地面,防止有空中坠落或者卡在地面的问题// 创建相机弹簧臂CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom")); //创建弹簧臂CameraBoom->SetupAttachment(RootComponent); //附加到根组件上CameraBoom->SetUsingAbsoluteRotation(true); // 不跟随根组件旋转CameraBoom->TargetArmLength = 800.f; //设置弹簧臂长度CameraBoom->SetRelativeRotation(FRotator(-60.f, 0.f, 0.f)); //设置弹簧臂的角度CameraBoom->bDoCollisionTest = false; // 设置为false,弹簧臂将不会与其他碰撞体产生交互// 创建相机TopDownCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("TopDownCamera")); //创建相机组件TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); //附加到弹簧臂上面TopDownCameraComponent->bUsePawnControlRotation = false; // 设置为false,相机的旋转不受角色控制// 激活帧更新,以便每帧更新光标PrimaryActorTick.bCanEverTick = true; //设置是否帧更新,如果当前值为false,即使设置其它,也不会被更新PrimaryActorTick.bStartWithTickEnabled = true; //游戏开始时,是否立即开始帧回调,如果bCanEverTick为false,也无法帧回调
}

这就是整个Character的内容,其实都是在蓝图里面都可以设置的东西,只不过修改成了使用c++去设置。

PlayerController

在PlayerController里面,首先增加了一个宏用于打印调试,和前面引擎文件里的一样,只是名称不一样,方便区分。

DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);

然后在类内部设置了构造函数,定义了一些可编辑变量,比如点击地面时生成的箭头特效,设置一个时间阈值判断是点击事件还是长按事件,增强输入的上下文,还有点击的action(鼠标和触摸屏的)

public:ATopDownProjectPlayerController();/** 定义一个时间是长按还是点击 */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)float ShortPressThreshold;/** 点击地面生成的箭头特效 */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)UNiagaraSystem* FXCursor;/** MappingContext */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))UInputMappingContext* DefaultMappingContext;/** 鼠标点击 Input Action */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))UInputAction* SetDestinationClickAction;/** 触屏点击 Input Action */UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Input, meta=(AllowPrivateAccess = "true"))UInputAction* SetDestinationTouchAction;

设置完成,可以在UE面板里面去修改对应的配置
在这里插入图片描述

接着覆盖BeginPlay函数,这个函数在开始运行时触发

virtual void BeginPlay();

在实现这里,获取增强输入的子系统,添加自定义的输入映射上下文

void ATopDownProjectPlayerController::BeginPlay()
{// Call the base class  Super::BeginPlay();//Add Input Mapping Contextif (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer())){Subsystem->AddMappingContext(DefaultMappingContext, 0);}
}

然后覆盖SetupInputComponent,这个函数允许我们自定义输入

virtual void SetupInputComponent() override;

在函数实现这里,绑定了InputAction的按下,悬停,抬起,取消四个事件(分别绑定了鼠标和触摸,兼容移动端)

void ATopDownProjectPlayerController::SetupInputComponent()
{// set up gameplay key bindingsSuper::SetupInputComponent();// Set up action bindingsif (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(InputComponent)){// Setup mouse input eventsEnhancedInputComponent->BindAction(SetDestinationClickAction, ETriggerEvent::Started, this, &ATopDownProjectPlayerController::OnInputStarted);EnhancedInputComponent->BindAction(SetDestinationClickAction, ETriggerEvent::Triggered, this, &ATopDownProjectPlayerController::OnSetDestinationTriggered);EnhancedInputComponent->BindAction(SetDestinationClickAction, ETriggerEvent::Completed, this, &ATopDownProjectPlayerController::OnSetDestinationReleased);EnhancedInputComponent->BindAction(SetDestinationClickAction, ETriggerEvent::Canceled, this, &ATopDownProjectPlayerController::OnSetDestinationReleased);// Setup touch input eventsEnhancedInputComponent->BindAction(SetDestinationTouchAction, ETriggerEvent::Started, this, &ATopDownProjectPlayerController::OnInputStarted);EnhancedInputComponent->BindAction(SetDestinationTouchAction, ETriggerEvent::Triggered, this, &ATopDownProjectPlayerController::OnTouchTriggered);EnhancedInputComponent->BindAction(SetDestinationTouchAction, ETriggerEvent::Completed, this, &ATopDownProjectPlayerController::OnTouchReleased);EnhancedInputComponent->BindAction(SetDestinationTouchAction, ETriggerEvent::Canceled, this, &ATopDownProjectPlayerController::OnTouchReleased);}else{UE_LOG(LogTemplateCharacter, Error, TEXT("'%s' Failed to find an Enhanced Input Component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));}
}

接下来就是定义上面绑定的函数,以及对应所需的变量,就是通过这里实现的角色移动,接下来,我们看一下是如何实现移动的。

/** Input handlers for SetDestination action. */void OnInputStarted();void OnSetDestinationTriggered();void OnSetDestinationReleased();void OnTouchTriggered();void OnTouchReleased();private:FVector CachedDestination; //存储鼠标点击的位置bool bIsTouch; // 是否开启屏幕触摸float FollowTime; // 用于查看按住了多久

在鼠标按下时,会触发OnInputStarted()函数,这个函数内调用StopMovement()函数,停止角色移动。

void ATopDownProjectPlayerController::OnInputStarted()
{StopMovement();
}

鼠标按住,会触发角色跟随鼠标移动事件,在这种模式下,角色会直接朝向鼠标移动,不会自动躲避障碍物,我们看一下悬停回调函数的实现。
函数内先记录一下FollowTime 就是悬停按的时间,这个值会在鼠标抬起是使用。
然后通过鼠标位置发出射线去拾取点击的地面位置,然后通过角色位置和拾取位置计算朝向,根据朝向去移动。

void ATopDownProjectPlayerController::OnSetDestinationTriggered()
{// 长按时将帧的时间存储在变量内,用于鼠标抬起时判断是否为点击事件FollowTime += GetWorld()->GetDeltaSeconds();// 用于存储点击位置的数据信息FHitResult Hit;bool bHitSuccessful = false; //是否成功获取点击位置信息//bIsTouch是由屏幕触摸回调触发,并在触摸回调内设置其开启关闭,因为鼠标点击和触摸点击逻辑一样,只是获取点击地面位置的函数不一样。if (bIsTouch){bHitSuccessful = GetHitResultUnderFinger(ETouchIndex::Touch1, ECollisionChannel::ECC_Visibility, true, Hit);}else{bHitSuccessful = GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, true, Hit);}// 拾取到为止,从Hit内获取点击位置if (bHitSuccessful){CachedDestination = Hit.Location;}// 通过点击位置和角色位置计算出角色移动方向,并调用AddMovementInput移动。APawn* ControlledPawn = GetPawn();if (ControlledPawn != nullptr){FVector WorldDirection = (CachedDestination - ControlledPawn->GetActorLocation()).GetSafeNormal();ControlledPawn->AddMovementInput(WorldDirection, 1.0, false);}
}

AddMovementInput有三个值:

  • 方向向量(通常是一个FVector):这个参数定义了移动的方向。它可以是代表前后左右移动的二维向量,也可以是包含垂直移动的三维向量。
  • 缩放值(通常是一个浮点数float):这个参数用于调整移动的速度或幅度。通过改变这个值,你可以控制角色移动的快慢。
  • 强制标志(通常是一个布尔值bool):这个参数用于指定是否强制添加移动输入,即使某些条件不满足。它允许开发者在特定情况下覆盖正常的移动逻辑。
ControlledPawn->AddMovementInput(WorldDirection, 1.0, false);

最后我们看一下鼠标抬起事件,鼠标抬起事件首先判断鼠标悬停的时间,如果时间小于我们设置的ShortPressThreshold值,则代表当前属于点击事件,则可以触发自动寻路和播放粒子特效。如果时间大于,则代表属于鼠标悬停,角色跟随鼠标事件,不会触发自动寻路。在最后将FollowTime 设置为零。

void ATopDownProjectPlayerController::OnSetDestinationReleased()
{// 判断是否触发的点击事件,if (FollowTime <= ShortPressThreshold){// 使用函数库,将角色移动到目标位置UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, CachedDestination);//使用Niagara生成生成粒子特效UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, FXCursor, CachedDestination, FRotator::ZeroRotator, FVector(1.f, 1.f, 1.f), true, true, ENCPoolMethod::None, true);}FollowTime = 0.f;
}

还有两个是触摸屏触发的事件,它内部只是设置bIsTouch布尔值,这个值为true代表是触摸屏触发的此事件,所以可以看到在悬停时将其设置为true,然后触发悬停事件,然后在抬起时将其设置为false。这样即使你这次是触摸屏触发的事件,在下一次修改为鼠标也是没问题的。

void ATopDownProjectPlayerController::OnTouchTriggered()
{bIsTouch = true;OnSetDestinationTriggered();
}void ATopDownProjectPlayerController::OnTouchReleased()
{bIsTouch = false;OnSetDestinationReleased();
}

弊端

由于此案例是一个教学案例,所以代码很精简,只实现了简单的功能,其也只适合制作单机游戏,如果将其设置为包含服务器的客户端运行的话,会发现它的自动寻路功能将会失效,那么我们还需要使用其它的方式实现。
在这里插入图片描述
接下来,我将在UE5 RPG的文章中实现可以在服务器上运行的自动寻路功能。

这篇关于UE5俯视角游戏案例代码查看的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/883688

相关文章

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

Vue实现路由守卫的示例代码

《Vue实现路由守卫的示例代码》Vue路由守卫是控制页面导航的钩子函数,主要用于鉴权、数据预加载等场景,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、概念二、类型三、实战一、概念路由守卫(Navigation Guards)本质上就是 在路

uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)

《uni-app小程序项目中实现前端图片压缩实现方式(附详细代码)》在uni-app开发中,文件上传和图片处理是很常见的需求,但也经常会遇到各种问题,下面:本文主要介绍uni-app小程序项目中实... 目录方式一:使用<canvas>实现图片压缩(推荐,兼容性好)示例代码(小程序平台):方式二:使用uni

JAVA实现Token自动续期机制的示例代码

《JAVA实现Token自动续期机制的示例代码》本文主要介绍了JAVA实现Token自动续期机制的示例代码,通过动态调整会话生命周期平衡安全性与用户体验,解决固定有效期Token带来的风险与不便,感兴... 目录1. 固定有效期Token的内在局限性2. 自动续期机制:兼顾安全与体验的解决方案3. 总结PS

C#中通过Response.Headers设置自定义参数的代码示例

《C#中通过Response.Headers设置自定义参数的代码示例》:本文主要介绍C#中通过Response.Headers设置自定义响应头的方法,涵盖基础添加、安全校验、生产实践及调试技巧,强... 目录一、基础设置方法1. 直接添加自定义头2. 批量设置模式二、高级配置技巧1. 安全校验机制2. 类型

Python屏幕抓取和录制的详细代码示例

《Python屏幕抓取和录制的详细代码示例》随着现代计算机性能的提高和网络速度的加快,越来越多的用户需要对他们的屏幕进行录制,:本文主要介绍Python屏幕抓取和录制的相关资料,需要的朋友可以参考... 目录一、常用 python 屏幕抓取库二、pyautogui 截屏示例三、mss 高性能截图四、Pill

使用MapStruct实现Java对象映射的示例代码

《使用MapStruct实现Java对象映射的示例代码》本文主要介绍了使用MapStruct实现Java对象映射的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、什么是 MapStruct?二、实战演练:三步集成 MapStruct第一步:添加 Mave