目录
DECLARE_DELEGATE 委托
委托可以存储对 具有任意类的特定签名的方法 的引用,并在必要时调用此方法。委托在蓝图中叫EventDispatchers
(1)DELARE_DELEGATE:是最简单的版本,带有一个参数DelegateName 是我们可以分配给委托的名称,只能在C++中使用
(2)DECLARE_DYNAMIC_DELEGATE:这个委托的不同之处在于,可以做蓝图中使用
(3)DECLARE_DYNAMIC_MULTICAST_DELEGATE:多播,分别用蓝图的形式编写,可以编写多个客户端,也可以使用C++
声明两个委托:
//动态委托:以蓝图提供,具有两个参数 //UE中所有代表队名称用 F 开头 //给这个委托命名为FOnColoredChanged,下一个参数是 参数的类型(比如说参数是color,那么类型就是FLinearColor),下一个参数就是 第一个参数的名称 Color,下一个是 第二个参数的类型(比如说要输出 Actor的名称,那么类型就是FString),后面以此类推 DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnColoredChanged,const FLinearColor& ,Color,const FString&, Name); //非动态委托:以C++提供 ,具有一个参数 //命名为FOnTimerFinished,参数类型是指向参与者的指针,就是返回当前actor的指针 DECLARE_MULTICAST_DELEGATE_OneParam(FOnTimerFinished,AActor*);
创建两个委托变量
//动态委托需要提供给蓝图 UPROPERTY(BlueprintAssignable) FOnColoredChanged OnColorChanged; FOnTimerFinished OnTimerFinished;
执行这两个多播委托:Broadcast 单播用ExecuteIfBound
//使用Broadcast 调用委托 OnColorChanged.Broadcast(NewColor, GetName()); OnTimerFinished.Broadcast(this);
绑定委托:动态多播用AddDynamic,非动态多播用AddUobject,单播用BindUObject
//创建两个函数,用于订阅委托 //参数类型要和委托类型一样 //绑定动态委托必须要用UFUNCTION标记 UFUNCTION() void OnColorChanged1(const FLinearColor& Color, const FString& Name); void OnTimerFinished1(AActor* Actor);
//绑定委托:第一个参数是指向对象的指针,第二个是 改对象函数的引用 // 动态委托绑定:AddDynamic: //当委托被触发时,会调用OnColorChanged1函数 Geometry->OnColorChanged.AddDynamic(this, &ABaseGeometryHubActor::OnColorChanged1); //非动态委托绑定:AddUObject Geometry->OnTimerFinished.AddUObject(this, &ABaseGeometryHubActor::OnTimerFinished1);
在蓝图中的使用
GameMode创建基础类
StaticClass
DefaultPawnClass = ADefaultPawn::StaticClass(); PlayerControllerClass = APlayerController::StaticClass(); PlayerStateClass = APlayerState::StaticClass(); GameStateClass = AGameStateBase::StaticClass(); HUDClass = AHUD::StaticClass(); GameSessionClass = AGameSession::StaticClass(); SpectatorClass = ASpectatorPawn::StaticClass(); ReplaySpectatorPlayerControllerClass = APlayerController::StaticClass(); ServerStatReplicatorClass = AServerStatReplicator::StaticClass();
添加基础移动逻辑
SceneComponent组件
Static组件,Camera组件
FVector初始为0可以用FVector::ZeroVector
UPROPERTY(VisibleAnywhere) USceneComponent* SceneComponent; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* StaticMeshComponent; UPROPERTY(VisibleAnywhere) UCameraComponent* CameraComponent; UPROPERTY(EditAnyWhere) float Velocity = 300.0f; FVector VelocityVector = FVector::ZeroVector; void MoveForward(float Amount); void MoveRight(float Amount);
初始化组件:CreateDefaultSubobject
将组件附加到别的组件中:SetupAttachment()
获取根组件:GetRootComponent()
SceneComponent = CreateDefaultSubobject<USceneComponent>("SceneComponent"); SetRootComponent(SceneComponent); StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMeshComponent"); CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); //使用SetupAttachment将组件附加到根组件中,可以使用GetRootComponent()获取根组件 StaticMeshComponent->SetupAttachment(SceneComponent); CameraComponent->SetupAttachment(GetRootComponent());
控制器有一个指向它所控制的pawn的指针,可以通过控制器将输入传递给pawn
绑定按键输入的函数:PlayerInputComponent->BindAxis()
void ASandboxPawn::Tick(float DeltaTime) { Super::Tick(DeltaTime); if (!VelocityVector.IsZero()) { const FVector NewLocation = GetActorLocation() + Velocity * DeltaTime * VelocityVector; SetActorLocation(NewLocation); VelocityVector = FVector::ZeroVector; } } // Called to bind functionality to input void ASandboxPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); if (PlayerInputComponent) { PlayerInputComponent->BindAxis("MoveForward", this, &ASandboxPawn::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASandboxPawn::MoveRight); } } void ASandboxPawn::MoveForward(float Amount) { UE_LOG(LogTemp, Display, TEXT("MoveForwart: %f"), Amount); VelocityVector.X = Amount; } void ASandboxPawn::MoveRight(float Amount) { UE_LOG(LogTemp, Display, TEXT("MoveRIght: %f"), Amount); VelocityVector.Y = Amount; }
PlayerController切换pawn
在PlayerController中添加按键输入,有个SetupInputComponent()
virtual void SetupInputComponent() override;
使用BindAction或者BindAxis绑定按键
void ASandboxPlayerController::SetupInputComponent() { Super::SetupInputComponent(); //BindAction:绑定按键::蓝图映射名称、EInputEvent(负责控件发生的事件类型) if (InputComponent) { InputComponent->BindAction("ChangePawn", IE_Pressed, this, &ASandboxPlayerController::ChangePawn); } }
Possess:用于切换Pawn的控制权
UGameplayStatic类:包含各种静态函数,该类的所有功能都可以做蓝图中使用
UPROPERTY() TArray<AActor*>Pawns; int32 CurrentPawnIndex = 0;
void ASandboxPlayerController::BeginPlay() { Super::BeginPlay(); //UGameplayStatics类:包含各种静态函数,该类的所有功能都可以做蓝图中使用, UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASandboxPawn::StaticClass(), Pawns); } void ASandboxPlayerController::ChangePawn() { if (Pawns.Num() <= 1) return; ASandboxPawn* CurrentPawn = Cast<ASandboxPawn>(Pawns[CurrentPawnIndex]); CurrentPawnIndex = (CurrentPawnIndex + 1) % Pawns.Num(); if (!CurrentPawn) return; UE_LOG(LogTemp, Display, TEXT("ChangePawn")); //切换pawn :Possess Possess(CurrentPawn); }
PossessedBy,UnPossessed:当Pawn被切换控制权时调用
//切换控制权 virtual void PossessedBy(AController* NewController) override; virtual void UnPossessed() override;
新项目设置版权
创建文件在新的文件夹里面
需要在构建文件Build.cs里面加上路径 :
UE的映射
UE的映射分为动作映射和轴映射,动作映射是一种离散映射,当一次收到一个事件:按下或者释放一个按钮。轴映射来自控件的值不断变化。
轴映射:添加移动,在charactor的SetupPlayerInputComponent函数中,绑定轴映射,AddMovementInput函数在每个Charactor中都有,实际上就是获取移动组件。
#include "Components/InputComponent.h" void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); PlayerInputComponent->BindAxis("MoveForward", this, &ASTUBaseCharacter::MoveForward); PlayerInputComponent->BindAxis("MoveRight", this, &ASTUBaseCharacter::MoveRight); } void ASTUBaseCharacter::MoveForward(float Amount) { AddMovementInput(GetActorForwardVector(), Amount); } void ASTUBaseCharacter::MoveRight(float Amount) { AddMovementInput(GetActorRightVector(), Amount); }
动作映射:添加跳跃
void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); //Jump函数是Character自带的 PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ASTUBaseCharacter::Jump); }
角色摄像机控制
添加相机组件和弹簧笔组件:UCameraComponent
//前向声明 class UCameraComponent; UPROPERTY(VisibleAnyWhere, BlueprintReadWrite, Category = "Components") UCameraComponent* CameraComponent;
初始化组件:
#include "Components/InputComponent.h" // Sets default values ASTUBaseCharacter::ASTUBaseCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); CameraComponent->SetupAttachment(GetRootComponent()); }
添加旋转:
#include "Components/InputComponent.h" // Called to bind functionality to input void ASTUBaseCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); //相机旋转 PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::LookUp); PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::TurnAround); //如果是使用controller的旋转,可以直接用接口,这个和上面是一样的,都能够实现逻辑 //PlayerInputComponent->BindAxis("LookUp", this, &ASTUBaseCharacter::AddControllerPitchInput); //PlayerInputComponent->BindAxis("TurnAround", this, &ASTUBaseCharacter::AddControllerYawInput); } void ASTUBaseCharacter::LookUp(float Amount) { AddControllerPitchInput(Amount); } void ASTUBaseCharacter::TurnAround(float Amount) { AddControllerYawInput(Amount); }
但是这样设置只有水平的旋转,如果要上下旋转的话还需要使用pawn控制旋转
AddControllerPitchInput实际上是使用的PlayerController的旋转,而它的旋转速度在下面设置
如果要实现围绕角色当前所在点进行旋转的话,需要使用弹簧臂。
添加弹簧臂
//前向声明 class USpringArmComponent; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Components") USpringArmComponent* SpringArmComponent;
初始化:
#include "Components/InputComponent.h" #include "GameFramework/SpringArmComponent.h" // Sets default values ASTUBaseCharacter::ASTUBaseCharacter() { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>("SpringArmComponent"); SpringArmComponent->SetupAttachment(GetRootComponent()); //刚刚的设置也可以在代码中使用pawn的旋转 SpringArmComponent->bUsePawnControlRotation = true; //将相机的父组件改为弹簧臂 CameraComponent = CreateDefaultSubobject<UCameraComponent>("CameraComponent"); CameraComponent->SetupAttachment(SpringArmComponent); }
获取玩家速度
GetVelocity(); //判断速度是否为0 GetVelocity().IsZero();
更改移动组件的父类
创建一个CharacterMovement类,在Character类中使用带有参数的构造函数,
UCLASS() class SHOOTTHEMUP_API ASTUBaseCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties //一种带有参数的特殊构造函数,将初始化对象传递到改构造函数中,改对象的类型为FObjectInitializer ASTUBaseCharacter(const FObjectInitializer& ObjInit);
在CPP文件中调用父类的构造,传递要初始化的对象
ObjInit.SetDefaultSubobjectClass<要继承的新类>(要修改的组件名字)
//必须要调用父类的构造函数,然后传递我们的初始化对象 //调用模板函数SetDefaultSubobjectClass,以要替换的子对象的类作为模板参数 ASTUBaseCharacter::ASTUBaseCharacter(const FObjectInitializer& ObjInit) : Super(ObjInit.SetDefaultSubobjectClass<USTUCharacterMovementComponent>(ACharacter::CharacterMovementComponentName)) { // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; }
返回给定组件的所有者
GetPawnOwner()
//创建一个指向角色的指针,GetPawnOwner()返回指向给定组件所有者的指针,然后将指针转换为ASTUBaseCharacter const ASTUBaseCharacter* Player = Cast<ASTUBaseCharacter>(GetPawnOwner());
计算角色的移动
归一化:GetSafeNormal()
点乘:FVector::DotProduct()
余弦:FMath::Acos()
交叉乘积:FVector::CrossProduct()
反余弦:FMath::RadiansToDegrees()
符号化:FMath::Sign()
float ASTUBaseCharacter::GetMovementDirection() const { if (GetVelocity().IsZero()) return 0; const FVector VelocityNormal = GetVelocity().GetSafeNormal(); const float AngleBetween = FMath::Acos(FVector::DotProduct(GetActorForwardVector(), VelocityNormal)); const FVector CrossProduct = FVector::CrossProduct(GetActorForwardVector(), VelocityNormal); return FMath::RadiansToDegrees(AngleBetween) * FMath::Sign(CrossProduct.Z); }
判断变量是否有效
Check:如果无效的话,运行时会跳转到报错位置
check(HealthComponent); check(HealthTextComponent);
伤害函数TakeDamage
//ue自带的伤害函数,是蓝图的ApplyDamage的底层实现函数 TakeDamage(0.1,FDamageEvent{}, Controller, this);
分为三种类型OnTakeAnyDamage,OnTakePointDamage,OnTakeRadialDamage
是通过委托来实现的
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( FTakeAnyDamageSignature, AActor, OnTakeAnyDamage, AActor*, DamagedActor, float, Damage, const class UDamageType*, DamageType, class AController*, InstigatedBy, AActor*, DamageCauser );
实现逻辑:
创建一个函数用于绑定委托 参数要和委托一样
UFUNCTION() void OnTakeAnyDamageComponent(AActor* DamagedActor, float Damage, const class UDamageType* DamageType, class AController* InstigatedBy, AActor* DamageCauser);
动态绑定委托:这里使用的是第一种伤害类型
OnTakeAnyDamage.AddDynamic()
//获取owner AActor* ComponentOwner = GetOwner(); if (ComponentOwner) { //OnTakeAnyDamage,OnTakePointDamage,OnTakeRadialDamage有三种类型,这里使用第一种 ComponentOwner->OnTakeAnyDamage.AddDynamic(this, &USTUHealthComponent::OnTakeAnyDamageComponent); }
ApplyDamage
ApplyDamage:需要用到GameplayStatics类
DrawDebugSphere:绘制调试球形
TSubclassOf:
//TSubclassOf:生成一个DamgeType类型的变量,在蓝图外部赋值 UPROPERTY(EditAnywhere, BlueprintReadWrite) TSubclassOf<UDamageType> DamageType;
#include "DrawDebugHelpers.h" //从world中获得所以的actor时 #include "Kismet/GameplayStatics.h" //绘制球形检测:指向世界的指针,球的中心点,半径,球段数,球颜色 DrawDebugSphere(GetWorld(), GetActorLocation(), Radius, 24, SphereColor); //DoFullDamage:为false时 边缘到中心伤害是增加的,true时,伤害是一样的 UGameplayStatics::ApplyRadialDamage(GetWorld(), Damage, GetActorLocation(), Radius, DamageType, {}, this, nullptr, DoFullDamage);
绑定委托:
AddDynamic:绑定动态委托
IsA<>():IsA<>():强制转换,将DamageType强制转换为USTUFirDamageType或者USTUIceDamageType类型
// Called when the game starts void USTUHealthComponent::BeginPlay() { Super::BeginPlay(); Health = MaxHealth; //获取owner AActor* ComponentOwner = GetOwner(); if (ComponentOwner) { //OnTakeAnyDamage,OnTakePointDamage,OnTakeRadialDamage有三种类型,这里使用第一种 ComponentOwner->OnTakeAnyDamage.AddDynamic(this, &USTUHealthComponent::OnTakeAnyDamageComponent); } } void USTUHealthComponent::OnTakeAnyDamageComponent(AActor* DamagedActor, float Damage, const UDamageType* DamageType, AController* InstigatedBy, AActor* DamageCauser) { Health -= Damage; UE_LOG(HealthComponentLog, Display, TEXT("Damage:%f"), Damage); if (DamageType) { //IsA<>():强制转换,将DamageType强制转换为USTUFirDamageType或者USTUIceDamageType类型 if (DamageType->IsA<USTUFirDamageType>()) { UE_LOG(HealthComponentLog, Display, TEXT("Fire")); } else if (DamageType->IsA<USTUIceDamageType>()) { UE_LOG(HealthComponentLog, Display, TEXT("Ice")); } } }