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

相关文章

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Java中调用数据库存储过程的示例代码

《Java中调用数据库存储过程的示例代码》本文介绍Java通过JDBC调用数据库存储过程的方法,涵盖参数类型、执行步骤及数据库差异,需注意异常处理与资源管理,以优化性能并实现复杂业务逻辑,感兴趣的朋友... 目录一、存储过程概述二、Java调用存储过程的基本javascript步骤三、Java调用存储过程示

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

Java实现自定义table宽高的示例代码

《Java实现自定义table宽高的示例代码》在桌面应用、管理系统乃至报表工具中,表格(JTable)作为最常用的数据展示组件,不仅承载对数据的增删改查,还需要配合布局与视觉需求,而JavaSwing... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Go语言代码格式化的技巧分享

《Go语言代码格式化的技巧分享》在Go语言的开发过程中,代码格式化是一个看似细微却至关重要的环节,良好的代码格式化不仅能提升代码的可读性,还能促进团队协作,减少因代码风格差异引发的问题,Go在代码格式... 目录一、Go 语言代码格式化的重要性二、Go 语言代码格式化工具:gofmt 与 go fmt(一)