Unreal5 무기 Collision C++
- 프로그래밍/언리얼
- 2023. 5. 10.
Unreal5 무기 Collision C++
이번 글에서는 무기 Collision 설정으로 공격 가능한 상태일 때만 Collision이 활성화되도록 하겠습니다.
[프로그래밍/언리얼] - Unreal5 맞은 방향 알아내기 CrossProduct(외적) c++
- Notify추가.
- 코드수정.
- 결과.
Notify 추가.
캐릭터 공격 montage에 Notify를 추가해서 무기의 Collision을 공격중일때 활성, 공격중이 아닐 때 비활성으로 만들어주겠습니다.
애니메이션의 공격 모션이 때리는 순간일 때 Collision을 활성화 할 수 있도록 EnableBoxCollision이라는 Notify를 배치하고 때리는 모션이 끝나면 DisableBoxCollision Notify를 배치했습니다.
코드 수정.
Weapon.h 에서 때린 액터를 또 때리지 않도록 IgnoreActors를 추가하고, 외부에서 WeaponBox에 접근할 수 있도록 함수를 추가했습니다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Item/Item.h"
#include "Weapon.generated.h"
/**
*
*/
class UBoxComponent;
UCLASS()
class SLAYER_API AWeapon : public AItem
{
GENERATED_BODY()
public:
AWeapon();//생성자.
void Equip(USceneComponent* InParent,const FName& InSocketName);
void AttachMeshToSocket(USceneComponent* InParent, const FName& InSocketName);
TArray<AActor*> IgnoreActors;
protected:
virtual void BeginPlay() override;
UFUNCTION()
void OnBoxOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
private:
/*Attack*/
UPROPERTY(VisibleAnywhere, Category = "Weapon Properties")
UBoxComponent* WeaponBox;
UPROPERTY(VisibleAnywhere)
USceneComponent* BoxTraceStart;
UPROPERTY(VisibleAnywhere)
USceneComponent* BoxTraceEnd;
public:
FORCEINLINE UBoxComponent* GetWeaponBox() const { return WeaponBox; }
};
Weapon.cpp에서는 OnBoxOverlap이벤트에 때린것을 또 때리지 않도록 for문을 통해 이전에 Hit가 되었던 것들을 ActorToIgnore에 넣어주었습니다.
void AWeapon::OnBoxOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
const FVector Start = BoxTraceStart->GetComponentLocation();
const FVector End = BoxTraceEnd->GetComponentLocation();
TArray<AActor*> ActorsToIgnore;
ActorsToIgnore.Add(this);
//#include "Kismet/KismetSystemLibrary.h"
FHitResult BoxHit;
//이전 충돌했던 것 빼기.
for (AActor* a : IgnoreActors) {
ActorsToIgnore.AddUnique(a);
}
UKismetSystemLibrary::BoxTraceSingle(this,Start,End, FVector(5.f, 5.f, 5.f),BoxTraceStart->GetComponentRotation(), ETraceTypeQuery::TraceTypeQuery1,
false,ActorsToIgnore,EDrawDebugTrace::None, BoxHit,true);
IgnoreActors.AddUnique(BoxHit.GetActor());
if (BoxHit.GetActor()) {
/*Actor를 때렸을 때*/
IHitInterface* HitInterface = Cast<IHitInterface>(BoxHit.GetActor());
if (HitInterface) {
HitInterface->GetHit(BoxHit.ImpactPoint);
}
}
}
SlayerCharacter.h 에서는 Enable, Disable어쩌고 Notify가 들어왔을 때 Collision을 설정할 함수를 선언하고, 저번에 실수로 빼먹은 것으로 생각되는 Weapon변수를 선언합니다.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "CharacterTypes.h"
#include "SlayerCharacter.generated.h"
/*input*/
class UInputMappingContext;
class UInputAction;
/*camera, spring arm*/
class USpringArmComponent;
class UCameraComponent;
/*Equip*/
class AItem;
class AWeapon;
/*Attack*/
class UAnimMontage;
UCLASS()
class SLAYER_API ASlayerCharacter : public ACharacter
{
GENERATED_BODY()
public:
ASlayerCharacter();
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//UFUNCTION(BlueprintCallable)
void SetWeaponCollision(ECollisionEnabled::Type CollisionEnabled);
protected:
virtual void BeginPlay() override;
/*Enhanced input*/
//#include "InputActionValue.h"
UPROPERTY(EditAnywhere, Category=Input)
UInputMappingContext* SlayerContext;
UPROPERTY(EditAnywhere, Category = Input)
UInputAction* MovementAction;
UPROPERTY(EditAnywhere, Category = Input)
UInputAction* JumpAction;
UPROPERTY(EditAnywhere, Category = Input)
UInputAction* LookAction;
void Look(const FInputActionValue& value);
void Move(const FInputActionValue& value);
void Jump() override;
//Equip
UPROPERTY(VisibleAnywhere, Category = Weapon)
AWeapon* EquippedWeapon;
UPROPERTY(EditAnywhere, Category = Input)
UInputAction* EkeyAction;
void EkeyPress();
//Attack
UPROPERTY(EditAnywhere, Category = Input)
UInputAction* AttackAction;
void Attack();
void PlayAttackMontage();
private:
/*camera,spring arm*/
UPROPERTY(VisibleAnywhere)
USpringArmComponent* CameraBoom;
UPROPERTY(VisibleAnywhere)
UCameraComponent* ViewCamera;
//Equip
UPROPERTY(VisibleInstanceOnly)
AItem* OverlappingItem;
//Attack
/*Animation montages*/
UPROPERTY(EditDefaultsOnly, Category = Montages)
UAnimMontage* AttackMontage;
//Attack State
//#include "CharacterTypes.h"
ECharacterState CharacterState = ECharacterState::ECS_Unequipped;
//UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = "true"))
EActionState ActionState = EActionState::EAS_Unoccupied;
public:
FORCEINLINE void SetOverlappingItem(AItem* Item) { OverlappingItem = Item; }
FORCEINLINE EActionState SetAttackEndState(){ return ActionState = EActionState::EAS_Unoccupied; }
};
SlayerCharacter.cpp 의 EKeyPress에서 오른손에 장착한 Weapon도 EquippedWeapon에 넣어주고, SetWeaponCollision함수도 정의합니다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/SlayerCharacter.h"
/*input*/
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
/*Rotate*/
#include "GameFramework/CharacterMovementComponent.h"
/*camera, spring arm*/
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
/*Equip*/
#include "Item/Weapons/Weapon.h"
#include "Components/BoxComponent.h"
/*Attack*/
//#include "Animation/AnimMontage.h"
#include "Animation/AnimInstance.h"
// Sets default values
ASlayerCharacter::ASlayerCharacter()
{
// 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;
/*spring arm*/
//#include "GameFramework/SpringArmComponent.h"필요
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->TargetArmLength = 300.f;
CameraBoom->bUsePawnControlRotation = true;
/*CharacterRotate*/
bUseControllerRotationPitch = false;
bUseControllerRotationRoll = false;
bUseControllerRotationYaw = false;
//#include "GameFramework/CharacterMovementComponent.h" 추가
GetCharacterMovement()->bOrientRotationToMovement = true;
/*Camera*/
ViewCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("ViewCamera"));
ViewCamera->SetupAttachment(CameraBoom);
}
// Called when the game starts or when spawned
void ASlayerCharacter::BeginPlay()
{
Super::BeginPlay();
//#include "EnhancedInputSubsystems.h" 추가 필요.
if (APlayerController* PlayerController = Cast<APlayerController>(GetController())) {
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) {
Subsystem->AddMappingContext(SlayerContext, 0);
}
}
}
void ASlayerCharacter::Look(const FInputActionValue& value)
{
const FVector2D LookVector = value.Get<FVector2D>();
AddControllerYawInput(LookVector.X);//도리도리
AddControllerPitchInput(-LookVector.Y);//끄덕끄덕
}
void ASlayerCharacter::Move(const FInputActionValue& value)
{
if (ActionState != EActionState::EAS_Unoccupied) return;
const FVector2D MovementVector = value.Get<FVector2D>();
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0.f, Rotation.Yaw, 0.f);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
AddMovementInput(ForwardDirection, MovementVector.Y);//이동
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(RightDirection, MovementVector.X);//이동
}
void ASlayerCharacter::Jump()
{
Super::Jump(); //Character.h에 있는 Jump사용.
}
void ASlayerCharacter::SetWeaponCollision(ECollisionEnabled::Type CollisionEnabled)
{
if (EquippedWeapon && EquippedWeapon->GetWeaponBox()) {
EquippedWeapon->GetWeaponBox()->SetCollisionEnabled(CollisionEnabled);
UE_LOG(LogTemp, Log, TEXT("SetWeaponCollision"));
EquippedWeapon->IgnoreActors.Empty();
}
}
void ASlayerCharacter::EkeyPress()
{
/*Equip*/
//#include "Item/Weapons/Weapon.h"
if (AWeapon* Weapon = Cast<AWeapon>(OverlappingItem)) {
Weapon->Equip(GetMesh(),FName("RightHandSocket"));
OverlappingItem = nullptr;
//무기장착.
CharacterState = ECharacterState::ECS_EquippedOneHandedWeapon;
EquippedWeapon = Weapon;
}
}
/*Attack*/
void ASlayerCharacter::Attack()
{
//공격중이 아니고, 무기 장착중.
if (ActionState == EActionState::EAS_Unoccupied && CharacterState != ECharacterState::ECS_Unequipped) {
PlayAttackMontage();
ActionState = EActionState::EAS_Attacking;
}
}
void ASlayerCharacter::PlayAttackMontage()
{
//#include "Animation/AnimInstance.h"
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage) {
AnimInstance->Montage_Play(AttackMontage);
const int32 Selection = FMath::RandRange(0,AttackMontage->GetNumSections()-1);
GEngine->AddOnScreenDebugMessage(1, 2.f, FColor::Cyan, FString::Printf(TEXT("GetSection: %d"), Selection));
FName SectionName = FName();
switch (Selection) {
case 0:
SectionName = FName("Attack1");
break;
case 1:
SectionName = FName("Attack2");
break;
case 2:
SectionName = FName("Attack3");
break;
case 3:
SectionName = FName("Attack4");
break;
case 4:
SectionName = FName("Attack5");
break;
case 5:
SectionName = FName("Attack6");
break;
case 6:
SectionName = FName("Attack7");
break;
case 7:
SectionName = FName("Attack8");
break;
//case 8:
// SectionName = FName("Attack9");
// break;
default:
break;
}
//GEngine->AddOnScreenDebugMessage(2, 2.f, FColor::Red, SectionName.ToString());
AnimInstance->Montage_JumpToSection(SectionName, AttackMontage);
}
}
// Called every frame
void ASlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void ASlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//#include "EnhancedInputComponent.h"추가.
if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent)) {
EnhancedInputComponent->BindAction(MovementAction, ETriggerEvent::Triggered, this, &ASlayerCharacter::Move);
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ASlayerCharacter::Jump);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ASlayerCharacter::Look);
EnhancedInputComponent->BindAction(EkeyAction, ETriggerEvent::Triggered, this, &ASlayerCharacter::EkeyPress);
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &ASlayerCharacter::Attack);
}
}
SlayerAnimInstance.h에서 Notify이벤트를 선언합니다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "SlayerAnimInstance.generated.h"
/**
*
*/
UCLASS()
class SLAYER_API USlayerAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
virtual void NativeInitializeAnimation() override;
virtual void NativeUpdateAnimation(float DeltaTime) override;
UPROPERTY(BlueprintReadOnly)
class ASlayerCharacter* SlayerCharacter;
UPROPERTY(BlueprintReadOnly, Category = Movement)
class UCharacterMovementComponent* SlayerCharacterMovement;
UPROPERTY(BlueprintReadOnly, Category = Movement)
float GroundSpeed;
UPROPERTY(BlueprintReadOnly, Category = Movement)
bool isFalling;
UFUNCTION()
void AnimNotify_AttackEnd();
UFUNCTION()
void AnimNotify_EnableBoxCollision();
UFUNCTION()
void AnimNotify_DisableBoxCollision();
};
SlayerAnimInstace.cpp에서 EnableBoxCollision Notify가 발생하면 Collision이 QueryOnly가 되도록 하고, 공격이 끝나면(DisalbeBoxCollision) Collision을 NoCollision이 되도록 정의하였습니다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/SlayerAnimInstance.h"
#include "Character/SlayerCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
void USlayerAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
SlayerCharacter = Cast<ASlayerCharacter>(TryGetPawnOwner());
if(SlayerCharacter){
//#include "GameFramework/CharacterMovementComponent.h" 필요.
SlayerCharacterMovement = SlayerCharacter->GetCharacterMovement();
}
}
void USlayerAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
Super::NativeUpdateAnimation(DeltaTime);
if (SlayerCharacterMovement) {
//#include "Kismet/KismetMathLibrary.h"
GroundSpeed = UKismetMathLibrary::VSizeXY(SlayerCharacterMovement->Velocity);
isFalling = SlayerCharacterMovement->IsFalling();
}
}
void USlayerAnimInstance::AnimNotify_AttackEnd()
{
SlayerCharacter->SetAttackEndState();
}
void USlayerAnimInstance::AnimNotify_EnableBoxCollision()
{
SlayerCharacter->SetWeaponCollision(ECollisionEnabled::QueryOnly);
}
void USlayerAnimInstance::AnimNotify_DisableBoxCollision()
{
SlayerCharacter->SetWeaponCollision(ECollisionEnabled::NoCollision);
}
결과.
이렇게 수정하니 한번 공격에 여러 번 맞는 문제나 공격도 안 했는데 Hit Reaction애니메이션이 나가는 문제는 사라졌습니다.
근데 이거 계속 작성하면 코드 수정이랑 가독성이 좀 아쉽기도 하고, 어느 정도 힌트는 될 수 있다고 생각하지만 코드 자체는 저만 알아볼 수 있는 코드가 되어버려서...
블로그에 계속 이런 식으로 쓰는 것은 생각 좀 다시 해봐야 될 것 같아요.