UEC++ 虚幻5 “类魂”游戏笔记(七)

UEC++ 虚幻5 “类魂”游戏笔记(七)本文详细介绍了如何在游戏开发中制作 AI 敌人的 UI 血条联动 包括添加公共接口获取血量信息 定义冲刺攻击逻辑 配置防御机制以及战斗结束后 UI 的显示与管理

UI反馈制作与人物敌人战斗逻辑补充(二)

制作AI敌人与UI血条联动

  • BP_UI_FightMianUI蓝图中制作敌人的UI血条
    在这里插入图片描述
  • BaseEnemy类中添加两个公共接口,返回敌人的血量与最大血量
	//公共接口
	UFUNCTION(BlueprintCallable,BlueprintPure)
	inline float GetCurHP() {
    return HP; };
	UFUNCTION(BlueprintCallable,BlueprintPure)
	inline float GetMaxHP() {
    return MaxHP; };
  • 在UI蓝图中绑定Progress控件
    在这里插入图片描述

定义冲刺攻击逻辑

  • 现在来不起冲刺攻击的逻辑,在BaseEnemy类中新建一个是否可以冲刺攻击的函数方法和冲刺攻击的目标位置向量变量与获取冲刺目标的位置函数方法
	//冲刺攻击的目标位置
	FVector RushAttackPos;
	
RushAttackPos = FVector(0, 0, 0);

	//是否可以冲刺攻击
	bool CanRushAttack();

bool ABaseEnemy::CanRushAttack()
{
   
	return bEquip;
}
	//获取冲刺目标的位置
	FVector GetShiftingTargetPos();
  • SoulBaseCharacter类中新建一个箭头的组件
	//玩家位置箭头
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	UArrowComponent* PlayerPosArrow;
	
PlayerPosArrow = CreateDefaultSubobject<UArrowComponent>(TEXT("PlayerPosArrow"));
PlayerPosArrow->SetupAttachment(GetRootComponent());
  • BaseEnemy类中的GetShiftingTargetPos函数逻辑
FVector ABaseEnemy::GetShiftingTargetPos()
{
   
	FVector PlayerLocation = TargetPlayer->PlayerPosArrow->GetComponentLocation();
	return  PlayerLocation;
}
  • 然后在定义一个平滑过渡冲刺的函数RushAttackShifting
	//平滑过渡冲刺
	UFUNCTION(BlueprintCallable)
	void RushAttackShifting(float Lerp);

void ABaseEnemy::RushAttackShifting(float Lerp)
{
   
	FVector LerpLocation = UKismetMathLibrary::VLerp(GetActorLocation(), RushAttackPos, Lerp);
	SetActorLocation(FVector(LerpLocation.X, LerpLocation.Y, GetActorLocation().Z));
}
  • 补齐之前的冲刺攻击函数逻辑
void ABaseEnemy::RushAttack()
{
   
	if (CanRushAttack())
	{
   
		UAnimInstance* CurAnimInstance = GetMesh()->GetAnimInstance();
		if(CurAnimInstance)
		{
   
			bAttack = true;
			EnemyAIController->GetBlackboardComponent()->SetValueAsBool("IsRunning", false);
			//获取冲刺目标位置
			RushAttackPos = GetShiftingTargetPos();

			//奔跑速度变为行走速度
			RunningMovement(false);

			CurAnimInstance->Montage_Play(RushAttackAnim[0]);
			//双重保险,防止敌人冲击没有播放出来,就没重置Attack状态
			bAttack = false;
		}
	}
}
  • 配置AI敌人冲刺攻击的资产,因为上面的逻辑就是在设置冲刺位置,所以不需要Root动画
    在这里插入图片描述
  • 在敌人蓝图里面创建一个事件,调用时间轴与平滑冲刺攻击函数,这个事件在冲刺攻击的蒙太奇去通知调用
    在这里插入图片描述
    在这里插入图片描述
  • 在敌人的冲刺动画创建蒙太奇然后其通知状态设置
    在这里插入图片描述
  • 调用冲刺事件
    在这里插入图片描述
  • 最后将这个冲刺蒙太奇添加到敌人蓝图上
    在这里插入图片描述
  • 在主角蓝图中将箭头往角色前面移一点,不要太贴脸了
    在这里插入图片描述

人物防御受击逻辑定义

  • SoulBaseCharacter类中新建一个持剑防御减少体力的变量
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Attribute")
	float SwordDefenceSubStamina;//持剑防御消耗体力值

SwordDefenceSubStamina = 15.f;
  • PalyerCharacter类中新建一个破防函数与持剑防御受击的蒙太奇数组,和持剑破防蒙太奇
	//持剑防御受击蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	TArray<UAnimMontage*> SwordDefenceInjuryAnim;
	//持剑破防蒙太奇
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "AnimMontage", meta = (AllowPrivateAccess = "true"))
	UAnimMontage* SwordDestroyDefenceAnim;
	
	//破防函数
	void DestroyDefence();
  • 破防函数逻辑
void APlayerCharacter::DestroyDefence()
{
   
	PlayerBehavior = EPlayerBehavior::EPB_INJURY;
	UAnimInstance* CurAniminstance = GetMesh()->GetAnimInstance();
	if (CurAniminstance)
	{
   
		CurAniminstance->Montage_Play(SwordDestroyDefenceAnim);
	}
	//减去体力
	Stamina -= SwordDefenceSubStamina;
	if (Stamina < 0)
	{
   
		Stamina = 0;
	}
}
  • 持剑受击函数逻辑添加
void APlayerCharacter::SwordInjury()
{
   	
	RandomMontage(SwordInjuryAnim);
	CameraShakeFeedBack(false);//色散效果
	if (PlayerBehavior == EPlayerBehavior::EPB_DEFENCE)
	{
   
		if (Stamina < SwordDefenceSubStamina)
		{
   
			//破防
			DestroyDefence();
		}
		else
		{
   		
			//随机播放受击防御蒙太奇和减去相应的体力值
			RandomMontage(SwordDefenceInjuryAnim);
			Stamina -= SwordAttackSubStamina;
		}
	}
	else
	{
   
		PlayerBehavior = EPlayerBehavior::EPB_INJURY;
		HP -= 10;
		if (HP <= 10)
		{
   
			HP -= 10;
			HP = 0;
			Die();
		}
		else
		{
   
			HP -= 10;
		}
	}	
}
  • 添加CanSwordDefence逻辑,防御状态不能奔跑,体力值得>0
bool APlayerCharacter::CanSwordDefence()
{
   
	if (PlayerBehavior == EPlayerBehavior::EPB_IDLE && WeaponType == EWeaponType::EWT_SWORD && !bIsRun && Stamina > 0)
	{
   
		return true;
	}
	return false;
}

  • 角色持剑受击破防资源重定向
    在这里插入图片描述
    在这里插入图片描述
  • 创建蒙太奇,然后在破防蒙太奇中添加通知受击状态重置的通知
    在这里插入图片描述
  • 然后将这三个蒙太奇添加到角色蓝图上
    在这里插入图片描述

定义战斗UI显示与隐藏逻辑

  • 将之前的DarkSoulHUD类中的BeginPlay开始就绑定UI到视口的逻辑给注释掉,新建一个函数专门来绑定UI到视口逻辑,然后在DarkSoulPlayerController类中来打开这个主UI,新建一个函数方法来调用刚才新建那个绑定UI到视口的函数

DarkSoulHUD.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "DarkSoulHUD.generated.h"

/** * */
UCLASS()
class DARKSOULSGAME_API ADarkSoulHUD : public AHUD
{
   
	GENERATED_BODY()
	
public:
	ADarkSoulHUD();
	//存储主界面UI的模版
	TSubclassOf<class UUI_FightMain> FightMainUIClass;
	//主界面UI的类指针
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MainUI")
	class UUI_FightMain* FightMainUI;
protected:
	virtual void BeginPlay() override;

public:
	inline UUI_FightMain* GetFightMainUI() {
    return FightMainUI; };

	//创建主战斗UI
	void CreateFightMainUI();
};

DarkSoulHUD.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "DarkSoulHUD.h"
#include "UI/UI_FightMain.h"
ADarkSoulHUD::ADarkSoulHUD()
{
   
	static ConstructorHelpers::FClassFinder<UUI_FightMain> UI_FightMain(TEXT("/Game/_Game/BP/UI/BP_UI_FightMian"));
	FightMainUIClass = UI_FightMain.Class;
}

void ADarkSoulHUD::BeginPlay()
{
   
	创建UI界面
	//FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
	//if (FightMainUI)
	//{
   
	// //添加到视口
	// FightMainUI->AddToViewport(0);
	//}
}

void ADarkSoulHUD::CreateFightMainUI()
{
   
	//创建UI界面
	FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
	if (FightMainUI)
	{
   
		//添加到视口
		FightMainUI->AddToViewport(0);
	}
}

DarkSoulPlayerController.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "DarkSoulPlayerController.generated.h"

/** * */
UCLASS()
class DARKSOULSGAME_API ADarkSoulPlayerController : public APlayerController
{
   
	GENERATED_BODY()
public:
	// Called to bind functionality to input

	//调用战斗主UI
	UFUNCTION(BlueprintCallable)
	void OpenFightUI();
};

DarkSoulPlayerController.cpp

// Fill out your copyright notice in the Description page of Project Settings.
#include "DarkSoulPlayerController.h"
#include "DarkSoulHUD.h"
void ADarkSoulPlayerController::OpenFightUI()
{
   
	ADarkSoulHUD* HUD = Cast<ADarkSoulHUD>(GetHUD());
	if (HUD)
	{
   
		//创建主UI
		HUD->CreateFightMainUI();
	}
}

最后在蓝图上的逻辑

  • 最后在开始战斗的触发盒子上调用这个函数
    在这里插入图片描述

配置敌人高亮Tip

  • 创建一个高两点材质,材质域换成用户界面
    在这里插入图片描述
  • RadialGradientExponential节点径向梯度指数。常用于制作从圆心到四周产生渐变的效果
    • UV:纹理坐标,默认是没有经过任何变换的纹理坐标
    • CenterPosition:圆心位置,默认(0.5,0.5)
    • Radius:半径占纹理百分比
    • Density:密度,密度越高,产生渐变的区间越小
    • InvertDensity:是否反转计算公式,默认是false
      在这里插入图片描述
  • 然后建立继承与DarkSoulUserWidgetRule类的UI蓝图,制作UI
    在这里插入图片描述
    在这里插入图片描述
  • 然后在敌人蓝图中添加一个WidgetUI组件,来添加这个新建的UI
    在这里插入图片描述

战斗结束反馈结果UI制作

定义需求

  • 新建一个地图方便测试切换关卡
    在这里插入图片描述
  • 新建一个继承DarkSoulUserWidgetRule类的UI类
    在这里插入图片描述
  • UI_FightResult类中重写BeginPlayTick,然后新建一个计时的变量与一个辅助延迟一秒的变量,一个返回计时变量的函数与在蓝图中实现的设置显示倒计时的文本函数与设置胜负的函数方法

UI_FightResult.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "DarkSoulUserWidgetRule.h"
#include "UI_FightResult.generated.h"

/** * */
UCLASS()
class DARKSOULSGAME_API UUI_FightResult : public UDarkSoulUserWidgetRule
{
   
	GENERATED_BODY()
public:

	UUI_FightResult();

	//相当于BeginPlay
	virtual void NativeConstruct();
	//Tick
	virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime);
	
	//倒计时
	int32 CountDownTime;
	//辅助变量
	float WaitTime;

	//返回倒计时
	UFUNCTION(BlueprintCallable,BlueprintPure)
	int32 GetCountDownTime();

	//设置显示倒计时
	UFUNCTION(BlueprintImplementableEvent)
	void SetCountDownText();

	//设置胜负
	UFUNCTION(BlueprintImplementableEvent)
	void SetResultText(bool IsWin);
};

UI_FightResult.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "UI_FightResult.h"
#include "Kismet/GameplayStatics.h"
UUI_FightResult::UUI_FightResult()
{
   

}

void UUI_FightResult::NativeConstruct()
{
   
	Super::NativeConstruct();

	CountDownTime = 5;
	WaitTime = 0;

	//设置开始倒计时
	SetCountDownText();
}

void UUI_FightResult::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
{
   
	Super::NativeTick(MyGeometry, InDeltaTime);
	if (CountDownTime > 0)
	{
   
		WaitTime += InDeltaTime;
		if (WaitTime >= 1)
		{
   
			CountDownTime--;
			//倒计时
			SetCountDownText();
			WaitTime = 0;
		}
		//说明五秒过去了,切换地图
		if (CountDownTime == 0)
		{
   
			UGameplayStatics::OpenLevel(GetWorld(), "TestMainMap");
		}
	}
}

int32 UUI_FightResult::GetCountDownTime()
{
   
	return CountDownTime;
}

创建UI实例蓝图

在这里插入图片描述

配置战斗结束UI控件并重写蓝图逻辑

  • 配置好UI控件蓝图然后进行编写逻辑
    在这里插入图片描述
    在这里插入图片描述

定义战斗结果反馈UI创建方法

DarkSoulHUD.h

  • DarkSoulHUD中创建绑定UI方法
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "DarkSoulHUD.generated.h"

/** * */
UCLASS()
class DARKSOULSGAME_API ADarkSoulHUD : public AHUD
{
   
	GENERATED_BODY()
public:
	ADarkSoulHUD();
	//存储主界面UI的模版
	TSubclassOf<class UUI_FightMain> FightMainUIClass;

	//存储胜负界面UI的模版
	TSubclassOf<class UUI_FightResult> FightResultUIClass;

	//主界面UI的类指针
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MainUI")
	class UUI_FightMain* FightMainUI;

	//胜负界面UI的类指针
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "MainUI")
	class UUI_FightResult* FightResultUI;
protected:
	virtual void BeginPlay() override;

public:
	inline UUI_FightMain* GetFightMainUI() {
    return FightMainUI; };

	inline UUI_FightResult* GetFightResultUI() {
    return FightResultUI; };

	//创建主战斗UI
	void CreateFightMainUI();

	//创建胜负UI
	void CreateFightResult(bool IsWin);
};

DarkSoulHUD.cpp

// Fill out your copyright notice in the Description page of Project Settings.
#include "DarkSoulHUD.h"
#include "UI/UI_FightMain.h"
#include "UI/UI_FightResult.h"
ADarkSoulHUD::ADarkSoulHUD()
{
   
	static ConstructorHelpers::FClassFinder<UUI_FightMain> UI_FightMain(TEXT("/Game/_Game/BP/UI/BP_UI_FightMian"));
	static ConstructorHelpers::FClassFinder<UUI_FightResult> UI_FightResult(TEXT("/Game/_Game/BP/UI/BP_UI__FightResult"));
	FightMainUIClass = UI_FightMain.Class;
	FightResultUIClass = UI_FightResult.Class;
}


void ADarkSoulHUD::BeginPlay()
{
   
	创建UI界面
	//FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
	//if (FightMainUI)
	//{
   
	// //添加到视口
	// FightMainUI->AddToViewport(0);
	//}
}

void ADarkSoulHUD::CreateFightMainUI()
{
   
	//创建UI界面
	FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
	if (FightMainUI)
	{
   
		//添加到视口
		FightMainUI->AddToViewport(0);
	}
}

void ADarkSoulHUD::CreateFightResult(bool IsWin)
{
   
	//创建UI界面
	FightResultUI = CreateWidget<UUI_FightResult>(GetWorld(), FightResultUIClass);
	if (FightResultUI)
	{
   
		//调用显示胜负UI的函数
		FightResultUI->SetResultText(IsWin);
		//添加到视口
		FightResultUI->AddToViewport(0);
	}
}

在主角基类里添加一个死亡UI事件

  • SoulBaseCharacter类中创建一个死亡后的UI事件函数,单独封装一个函数用来执行死亡后的UI
	//死亡UI事件
	void DeathUI();
  • 在死亡函数中调用这个DeathUI事件
void ASoulBaseCharacter::Die()
{
   
	bIsDie = true;
	//传递IsAnyoneDie到黑板
	EnemyTarget->EnemyAIController->GetBlackboardComponent()->SetValueAsBool("IsAnyoneDie", true);

	//按键解绑
	RemovePlayerInput();

	//关闭胶囊体碰撞
	if (GetCapsuleComponent())
	{
   
		GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	}
	if (GetMesh())
	{
   
		GetMesh()->SetCollisionObjectType(ECC_PhysicsBody);
		GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		GetMesh()->SetCollisionResponseToAllChannels(ECR_Block);
		GetMesh()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
		GetMesh()->SetAllBodiesBelowSimulatePhysics("pelvis", true);
	}
	//调用死亡后的UI事件
	DeathUI();
}

定义委托管理器单例

  • 建立一个专门管理的委托的管理类,可以继承自UObject
    在这里插入图片描述
  • 设计一个单例设计模式架构
  • DarkSoulEventManager.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DarkSoulEventManager.generated.h"

/** * */
UCLASS(BlueprintType)
class DARKSOULSGAME_API UDarkSoulEventManager : public UObject
{
   
	GENERATED_BODY()
public:

	UFUNCTION(BlueprintCallable,Category="DarkSoulEventManager")
	static UDarkSoulEventManager* GetInstancePtr();

private:
	//类中定义一个静态指针,指向唯一对象
	static UDarkSoulEventManager* S_Instance;
};

  • DarkSoulEventManager.cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "DarkSoulEventManager.h"

//懒汉式单例设计:无创建单例对象,没有在外面创建对象
UDarkSoulEventManager* UDarkSoulEventManager::S_Instance = nullptr;

UDarkSoulEventManager* UDarkSoulEventManager::GetInstancePtr()
{
   
	//保证外部只有唯一的对象方式
	if (S_Instance == nullptr)
	{
   
		S_Instance = NewObject<UDarkSoulEventManager>();
		//不被UE系统垃圾回收
		S_Instance->AddToRoot();
	}
	return S_Instance;
}

定义单播创建战斗结果反馈UI逻辑

  • 定义一个单播,然后到DarkSoulHUD中去绑定这个单播,然后就可以在主角类或者敌人类中调用绑定单播的胜负UI函数了
  • DarkSoulEventManager类中创建单播
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "DarkSoulEventManager.generated.h"

/** * */

//创建单播
DECLARE_DELEGATE_OneParam(FOpenFightReasultUI, bool);

UCLASS(BlueprintType)
class DARKSOULSGAME_API UDarkSoulEventManager : public UObject
{
   
	GENERATED_BODY()
public:

	UFUNCTION(BlueprintCallable,Category="DarkSoulEventManager")
	static UDarkSoulEventManager* GetInstancePtr();

private:
	//类中定义一个静态指针,指向唯一对象
	static UDarkSoulEventManager* S_Instance;

public:
	//委托名字
	FOpenFightReasultUI OpenFightResultUI;
};
  • DarkSoulHUD中去绑定这个单播,也就是单播与要执行的事件进行联动
void ADarkSoulHUD::BeginPlay()
{
   
	创建UI界面
	//FightMainUI = CreateWidget<UUI_FightMain>(GetWorld(), FightMainUIClass);
	//if (FightMainUI)
	//{
   
	// //添加到视口
	// FightMainUI->AddToViewport(0);
	//}

	Super::BeginPlay();
	//单播绑定
	UDarkSoulEventManager::GetInstancePtr()->OpenFightResultUI.BindUObject(this, &ADarkSoulHUD::CreateFightResult);
}
  • SoulBaseCharacter类中的DeathUI函数就去调用这个单播委托
void ASoulBaseCharacter::DeathUI()
{
   
	//调用单播委托使用胜负UI函数方法
	UDarkSoulEventManager::GetInstancePtr()->OpenFightResultUI.ExecuteIfBound(false);//false主角死亡失败了
}
  • 在敌人类BaseEnemy中也一样,单独创建一个DeathUI函数在Die函数中调用这个单播委托
	//敌人死亡函数
	void Die();

	//死亡UI事件
	void DeathUI();
  • 函数逻辑
void ABaseEnemy::Die()
{
   
	bIsDie = true;
	//传递IsAnyoneDie到黑板
	EnemyAIController->GetBlackboardComponent()->SetValueAsBool("IsAnyoneDie", true);

	//关闭胶囊体碰撞
	if (GetCapsuleComponent())
	{
   
		GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	}
	if (GetMesh())
	{
   
		GetMesh()->SetCollisionObjectType(ECC_PhysicsBody);
		GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		GetMesh()->SetCollisionResponseToAllChannels(ECR_Block);
		GetMesh()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);
		GetMesh()->SetAllBodiesBelowSimulatePhysics("pelvis", true);
	}
	DeathUI();
}

void ABaseEnemy::DeathUI()
{
   
	UDarkSoulEventManager::GetInstancePtr()->OpenFightResultUI.ExecuteIfBound(true);//敌人死亡胜
}
  • 最后在新建那个关卡中,把游戏Mode添加上,然后添加一个玩家出生点
    在这里插入图片描述
编程小号
上一篇 2025-01-11 08:06
下一篇 2025-01-11 07:57

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/108306.html