UE5.1.1 C++从0开始(18.游戏标签)

教程的链接:https://www.bilibili.com/video/BV1nU4y1X7iQ

这一章刚开始听可能有点晕,就记得最后做的几个功能其它全忘记了,我在这里做一个总结:

  1. 我们并没有从头开始像构建GAS一样构建一个游戏标签,我们使用的是官方给的数据类型,我们在代码内初始化的是标签容器,我们在容器内写明这个类带着什么标签,而标签的定义在蓝图编辑器内操作。我们所检查的也是对标签容器进行检查,具体行为是对容器内的标签进行比对,而不是直接检查我们的标签容器
  2. 我们给SAction类上了个互斥锁,也就是在CanStart函数内增加了一个bIsRunning的检查和一个BlockedTags标签的检查,如果正在运行或正在运行的行为的GrantTags内包含BlockedTags内的任意一个标签的话,那么下一个命令将不会被执行。我们在SActionComponent类内使用了这个互斥锁。
  3. 我们在蓝图内演示了游戏标签不但能够充当一个互斥锁的效果,还能够当一个钥匙与门锁的一个效果。教程内演示了只有当拿到固定的卡的时候才能够打开对应的箱子。
  4. 我们做了个反弹子弹的效果(有点类似于OW内源氏的e,不过这个e并不是按照我们的视角进行移动的,而是原路返回)
  5. 我们给我们的ai角色增加了受击后显示受伤的伤害显示以及更改了血条位置

我认为游戏标签应该不止这么一点点的功能,肯定还有很多教程内没有提到的功能。例如我们的不同的子弹上能够加上不同的标签,一些子弹是附带火焰伤害的,一些子弹会附带别的buff或debuff;再或者我们与NPC的交互中也可以使用标签,比如我们的装备可能会有标签赋予给我们,然后NPC可以对不同的标签作出不同的反应(有点像塞尔达那种)

废话不多说,上代码:

SAction.h

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

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "GameplayTagContainer.h"
#include "SAction.generated.h"

/**
 * 
 */
UCLASS(Blueprintable)
class ACTIONROGUELIKE_API USAction : public UObject
{
	GENERATED_BODY()

protected:

	UFUNCTION(BlueprintCallable,Category="Action")
	USActionComponent* GetOwningComponent() const;

	//在事件激活的时候增加到Actor上,事件结束后就移除
	UPROPERTY(EditDefaultsOnly,Category="Tags")
	FGameplayTagContainer GrantsTags;

	//用于检查我们是否能够运行该事件
	UPROPERTY(EditDefaultsOnly, Category = "Tags")
	FGameplayTagContainer BlockedTags;

	bool bIsRunning;

public:
    //互斥锁
	UFUNCTION(BlueprintCallable,Category = "ACtion")
	bool IsRunning() const;

    //新增检测函数,用于充当互斥锁
	UFUNCTION(BlueprintNativeEvent, Category = "Action")
	bool CanStart(AActor* Instigator);

	
	UPROPERTY(EditDefaultsOnly,Category="Action")
	FName ActionName;
	
	UFUNCTION(BlueprintNativeEvent,Category="Action")
	void StartAction(AActor* Instigator);

	UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Action")
	void StopAction(AActor* Instigator);

	class UWorld* GetWorld() const override;
};

SAction.cpp

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


#include "SAction.h"
#include "Logging/LogMacros.h"
#include "SActionComponent.h"

USActionComponent* USAction::GetOwningComponent() const
{
	return Cast<USActionComponent>(GetOuter());
}

bool USAction::IsRunning() const
{
	return bIsRunning;
}

bool USAction::CanStart_Implementation(AActor* Instigator)
{
    //首先检测是否有相同的Action被短时间内快速的多次激活,如果有而且已经有一个Action正在执行,那么我们就直接返回,不执行新的Action
	if (IsRunning())
	{
		return false;
	}
	
    //第二步,检测我们的组件对象是否正在执行一个与我们最新输入的命令所执行的Action类冲突,也就是说Action内的BlockedTags是否与正在执行的Action所赋予组件的tags有任何的重叠,如果有重叠,那么最新输入的Acion不会被执行
	USActionComponent* Comp = GetOwningComponent();

	if (Comp->ActiveGameplayTags.HasAny(BlockedTags))
	{
		return false;
	}

	return true;
}

void USAction::StartAction_Implementation(AActor* Instigator)
{
	UE_LOG(LogTemp, Log, TEXT("Running: % s"), *GetNameSafe(this));

	USActionComponent* Comp = GetOwningComponent();
	Comp->ActiveGameplayTags.AppendTags(GrantsTags);

	bIsRunning = true;

}

void USAction::StopAction_Implementation(AActor* Instigator)
{
	UE_LOG(LogTemp, Log, TEXT("Stopped: % s"), *GetNameSafe(this));

	ensureAlways(bIsRunning);

	USActionComponent* Comp = GetOwningComponent();
	Comp->ActiveGameplayTags.RemoveTags(GrantsTags);

	bIsRunning = false;
}

class UWorld* USAction::GetWorld() const
{
	UActorComponent* Comp = Cast<UActorComponent>(GetOuter());
	if (Comp)
	{
		return Comp->GetWorld();
	}
	return nullptr;
}

SActionComponent.h

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "SActionComponent.generated.h"


class USAction;

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class ACTIONROGUELIKE_API USActionComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Tags")
	FGameplayTagContainer ActiveGameplayTags;

protected:
	UPROPERTY()
	TArray<USAction*> Actions;

	UPROPERTY(EditAnywhere, Category = "Actions")
	TArray<TSubclassOf<USAction>> DefaultActions;

public:
	UFUNCTION(BlueprintCallable,Category="Actions")
	void AddAction(TSubclassOf<USAction>ActionClass);

	UFUNCTION(BlueprintCallable, Category = "Actions")
	bool StartActionByName(AActor* Instigator, FName ActionName);

	UFUNCTION(BlueprintCallable, Category = "Actions")
	bool StopActionByName(AActor* Instigator, FName ActionName);

public:	
	// Sets default values for this component's properties
	USActionComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

		
};

SActionComponent.cpp

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


#include "SActionComponent.h"
#include "SAction.h"

void USActionComponent::AddAction(TSubclassOf<USAction>ActionClass)
{
	if (!ensure(ActionClass))
	{
		return;
	}
	USAction* NewAction =  NewObject<USAction>(this,ActionClass);
	if (NewAction)
	{
		Actions.Add(NewAction);
	}
}

bool USActionComponent::StartActionByName(AActor* Instigator, FName ActionName)
{
	for (USAction *Action : Actions)
	{
		if (Action&&Action->ActionName==ActionName)
		{
            //此处使用了互斥锁
			if (!Action->CanStart(Instigator))
			{
				continue;
			}

			Action->StartAction(Instigator);
			return true;
		}
	}
	return false;
}

bool USActionComponent::StopActionByName(AActor* Instigator, FName ActionName)
{
	for (USAction* Action : Actions)
	{
		if (Action && Action->ActionName == ActionName)
		{

			if (Action->IsRunning())
			{
				Action->StopAction(Instigator);
				return true;
			}

		}
	}
	return false;
}

USActionComponent::USActionComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}


// Called when the game starts
void USActionComponent::BeginPlay()
{
	Super::BeginPlay();

	for (TSubclassOf<USAction> ActionClass : DefaultActions)
	{
		AddAction(ActionClass);
	}

	// ...
	
}


// Called every frame
void USActionComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ...
	FString DebugMessage = GetNameSafe(GetOwner()) + " : " + ActiveGameplayTags.ToStringSimple();
	GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::White, DebugMessage);
}


蓝图演示我们不做过多的赘述,接下来是增加一个子弹原路返回的功能。

子弹原路返回的功能我没有写在普通魔法子弹的类里面,而是写在魔法子弹的基类里面,不过在子类和基类里面写其实大差不差,只不过在基类里面写,所有的子类子弹都能被反弹罢了。

主要更改函数是下面这个:

void ASFatherMagicProjectile::OnComponentOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{

	if (OtherActor&&OtherActor!=GetInstigator())
	{

		//由于AI和PlayerCharacter两个类都有ActionComp的组件,我们可以直接无脑抓取OtherActor的USActionComponent
		USActionComponent* ActionComp = Cast<USActionComponent>(OtherActor->GetComponentByClass(USActionComponent::StaticClass()));
        //对组件进行存在与否的判断以及组件的标签容器内是否有魔法子弹所自带的标签进行判断
		if (ActionComp&&ActionComp->ActiveGameplayTags.HasTag(ParryTag))
		{
			//调转方向
			MovementComp->Velocity = -MovementComp->Velocity;
			//将Instigator设置为反弹的那个人
			SetInstigator(Cast<APawn>(OtherActor));

			return;
		}

		if (USGameplayFunctionLibrary::ApplyDirectionalDamage(GetInstigator(), OtherActor, Damage, SweepResult))
		{
			Explode();
		}

	}
}

我们在基类的.h文件内增加了这个参数

	UPROPERTY(EditDefaultsOnly, Category = "Damage")
	FGameplayTag ParryTag;

然后在蓝图内进行标签赋值,再回到上面的函数代码,各位应该能看懂大概是个啥意思了。

最后的一点时间教程在说给我们的AI增加一个伤害显示和血条位置的调整,我觉得这个东西应该不需要再复述了,这个东西在老早之前的教程内就教过了,就是我们的几个黄色的正方体,做受到伤害就闪一下的那一节课。只需要用相同的方法在AI的蓝图内写一遍就可以了。

最后,这一块的知识运用的点可能相对前面会比较散,所以大家如果有什么不太明白的地方一定记得问我。只要我会我都会尽全力给大家用大家能理解的方式讲解,如果正好遇上了我也不会的问题,我们就一起寻找答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

楚江_wog1st

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值