CharacterBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "CharacterBase.generated.h"
UCLASS()
class BASIS_API ACharacterBase : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
ACharacterBase();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
virtual void Attack(); // 상속받는 자식이 다르게 구현될 수 있음 virtual
virtual void Hit(int32 Damage, AActor* ByWho);
virtual void IncreaseKillCount();
UFUNCTION(BlueprintCallable)
void Heal(int HealAmount);
public:
UPROPERTY(EditAnywhere)
int32 FullHP;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
int32 AttackCount;
UPROPERTY(EditAnywhere)
int32 Strength;
int32 CurrentHP;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 KillCount;
};
CharacterBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "CharacterBase.h"
// Sets default values
ACharacterBase::ACharacterBase()
{
// 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;
FullHP = 100;
Strength = 10;
KillCount = 0;
}
// Called when the game starts or when spawned
void ACharacterBase::BeginPlay()
{
Super::BeginPlay();
SpawnDefaultController(); // 캐릭터가 생성되었을 때 그에 맞는 컨트롤러를 자동으로 인식
CurrentHP = FullHP;
KillCount = 0;
}
void ACharacterBase::Attack()
{
AttackCount++;
}
void ACharacterBase::Hit(int32 Damage, AActor* ByWho)
{
CurrentHP -= Damage;
if (CurrentHP < 0)
{
CurrentHP = 0;
}
if (CurrentHP <= 0)
{
ACharacterBase* CB= Cast<ACharacterBase>(ByWho);
if (IsValid(CB))
{
CB->IncreaseKillCount();
}
}
}
void ACharacterBase::IncreaseKillCount()
{
KillCount++;
}
void ACharacterBase::Heal(int HealAmount)
{
CurrentHP += HealAmount;
if (CurrentHP > FullHP)
{
CurrentHP = FullHP;
}
}
PlayerBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "CharacterBase.h"
#include "InputMappingContext.h"
#include "EnhancedInputSubsystems.h"
#include "PlayerBase.generated.h"
class UInputAction;
struct FInputActionValue;
class AWeapon;
class UInputMappingContext;
UCLASS()
class BASIS_API APlayerBase : public ACharacterBase
{
GENERATED_BODY()
public:
APlayerBase();
virtual void BeginPlay() override;
virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; // input 관련 컴포넌트
virtual void Hit(int32 Damage, AActor* ByWho) override; // Hit 덮어씌우기
virtual void IncreaseKillCount() override;
virtual void Attack() override;
private:
bool Zoomed = false;
// USpringArmComponent* a; 를
// TObjectPtr<USpringArmComponent> a; 로 변경
UPROPERTY(VisibleAnywhere)
TObjectPtr<class USpringArmComponent> CameraBoom; // 스프링암 컴포넌트
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UCameraComponent> FollowCamera; // 카메라
UPROPERTY(EditAnywhere)
TSubclassOf<AWeapon> Weapon; // 액터의 클래스 부분
UPROPERTY()
TObjectPtr<AWeapon> WeaponActor; // 실제 스폰되는 액터
UPROPERTY(EditAnywhere)
TObjectPtr<UInputMappingContext> InputMappingContext;
UPROPERTY(EditAnywhere)
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditAnywhere)
TObjectPtr<UInputAction> ZoomAction;
UPROPERTY(EditAnywhere)
TObjectPtr<UInputAction> LookAction;
UPROPERTY(EditAnywhere)
TObjectPtr<UInputAction> FireAction;
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void Fire(const FInputActionValue& Value);
void Zoom(const FInputActionValue& Value);
};
PlayerBase.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "PlayerBase.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include <EnhancedInputComponent.h> //EnhancedInputComponent를 사용하려면 모듈도 필요함(Basis.Build.cs에)
#include "Weapon.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"
#include <MainGameMode.h>
APlayerBase::APlayerBase()
{
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
CameraBoom->SetupAttachment(RootComponent);
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
FollowCamera->SetupAttachment(CameraBoom);
}
void APlayerBase::BeginPlay()
{
Super::BeginPlay();
CameraBoom->TargetArmLength = 120;
CameraBoom->SocketOffset = FVector(0, 60, 60);
APlayerController* PC = Cast<APlayerController>(GetController()); // 컨트롤러 가져오기
if (IsValid(PC))
{
ULocalPlayer* Player = PC->GetLocalPlayer();
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(Player))
{
Subsystem->AddMappingContext(InputMappingContext, 0); // PlayerBase에서 설정한 InputMappingContext를 등록
}
}
WeaponActor = GetWorld()->SpawnActor<AWeapon>(Weapon); // 세계를 가져오고 AWeapon의 하위 클래스 중 Weapon을 SpawnActor
if (IsValid(WeaponActor))
{
FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true); // SnapToTarget으로 붙이기
WeaponActor->AttachToComponent(GetMesh(), TransformRules, TEXT("WeaponSocket")); // 메시를 가져오고 WeaponSocket이란 이름으로 붙임
WeaponActor->SetOwner(this); // 소유자는 자기자신 플레이어
}
}
void APlayerBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlayerBase::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlayerBase::Look);
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &APlayerBase::Fire);
EnhancedInputComponent->BindAction(ZoomAction, ETriggerEvent::Started, this, &APlayerBase::Zoom);
}
}
void APlayerBase::Hit(int32 Damage, AActor* ByWho)
{
Super::Hit(Damage, ByWho);
if (CurrentHP > 0) {
return;
}
if (IsValid(GetWorld()))
{
AMainGameMode* GameMode = Cast<AMainGameMode>(GetWorld()->GetAuthGameMode());
if (IsValid(GameMode))
{
GameMode->ChangeToEnd();
}
}
if (IsValid(WeaponActor))
{
WeaponActor->Destroy();
}
Destroy();
}
void APlayerBase::IncreaseKillCount()
{
Super::IncreaseKillCount();
}
void APlayerBase::Attack()
{
Super::Attack();
if (IsValid(WeaponActor))
{
WeaponActor->Fire();
}
}
void APlayerBase::Move(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddMovementInput(GetActorForwardVector(), MovementVector.Y);
AddMovementInput(GetActorRightVector(), MovementVector.X);
}
}
void APlayerBase::Look(const FInputActionValue& Value)
{
FVector2D LookAxisVector = Value.Get<FVector2D>();
if (Controller != nullptr)
{
AddControllerYawInput(LookAxisVector.X);
AddControllerPitchInput(-LookAxisVector.Y);
}
}
void APlayerBase::Fire(const FInputActionValue& Value)
{
UE_LOG(LogTemp, Warning, TEXT("Fire Called!"));
Attack();
}
void APlayerBase::Zoom(const FInputActionValue& Value)
{
if (!IsValid(CameraBoom)) // IsValid로 null이 아닌지와 삭제된것인지 아닌지 판단
{
return;
}
Zoomed = !Zoomed;
if (Zoomed) // 카메라 붐이 있고 Zoomed값이 참이면 카메라붐을 가까이
{
CameraBoom->TargetArmLength = 40;
CameraBoom->SocketOffset = FVector(0, 40, 60);
}
else
{
CameraBoom->TargetArmLength = 120;
CameraBoom->SocketOffset = FVector(0, 60, 60);
}
}
Weapon.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Weapon.generated.h"
UCLASS()
class BASIS_API AWeapon : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AWeapon();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
void Fire();
UPROPERTY(VisibleAnywhere)
TObjectPtr<class USkeletalMeshComponent > Mesh; // 총의 스켈레탈 메시 외형
UPROPERTY(VisibleAnywhere)
TObjectPtr<USceneComponent> MuzzleOffset; // 총알의 발사 위치
UPROPERTY(EditAnywhere)
TObjectPtr<class UAnimMontage> FireMontage; // 총이 발사되는 애니메이션 몽타주
UPROPERTY(EditAnywhere)
TSubclassOf<class ABullet> Bullet; // 총알
};
Weapon.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Weapon.h"
#include "Bullet.h"
#include <PlayerBase.h>
#include "Kismet/KismetMathLibrary.h"
// Sets default values
AWeapon::AWeapon()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Mesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Mesh")); // CreateDefaultSubobject
RootComponent = Mesh; // RootComponent는 이 액터의 최상위 항목으로 지정
MuzzleOffset = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleOffset"));
MuzzleOffset->SetupAttachment(Mesh); // MuzzleOffset(총알의 발사 위치)를 메시의 하위항목으로 설정
}
// Called when the game starts or when spawned
void AWeapon::BeginPlay()
{
Super::BeginPlay();
}
void AWeapon::Fire()
{
UAnimInstance* AnimInstance = Mesh->GetAnimInstance(); // 메시의 애니메이션을 가져오는 GetAnimInstance
if (IsValid(AnimInstance) && IsValid(FireMontage))
{
AnimInstance->Montage_Play(FireMontage); // AnimInstance와 파이어몽타주가 존재하면 파이어몽타주 재생
}
if (IsValid(Bullet))
{
FRotator SpawnRotation = MuzzleOffset->GetComponentRotation(); // MuzzleOffset(총알 발사 위치)의 각도 가져오기
FVector SpawnLocation = MuzzleOffset->GetComponentLocation(); // MuzzleOffset(총알 발사 위치)의 위치 가져오기
FActorSpawnParameters SpawnParams; // Spawn할때(총알)
SpawnParams.Owner = this; // 총알의 Owner는 this(총)으로 설정
APlayerBase* PB = Cast<APlayerBase>(GetOwner()); // GetOwner() 즉 총의 주인인 플레이어를 APlayerBase로 캐스팅, PB에 대입
if (!IsValid(PB))
{
GetWorld()->SpawnActor<ABullet>(Bullet, SpawnLocation, SpawnRotation, SpawnParams); // 총의 주인이 없으면 총알만 생성
return;
}
APlayerController* PC = Cast<APlayerController>(PB->GetController()); // GetController로 플레이어의 컨트롤러를 가져옴
int32 x, y;
if (!IsValid(PC))
{
GetWorld()->SpawnActor<ABullet>(Bullet, SpawnLocation, SpawnRotation, SpawnParams);
return;
}
PC->GetViewportSize(x, y); // 플레이어 컨트롤러의 뷰포트 사이즈
FVector WorldCenter;
FVector WorldFront;
PC->DeprojectScreenPositionToWorld(x / 2.0f, y / 2.0f, WorldCenter, WorldFront); // 각각 2로 나눠 화면의 정중앙
WorldCenter += WorldFront * 10000;
SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SpawnLocation, WorldCenter); // 총구 위치에서 조준점 방향으로의 회전값 계산
GetWorld()->SpawnActor<ABullet>(Bullet, SpawnLocation, SpawnRotation, SpawnParams); // 총알을 SpawnLocation의 위치에서 SpawnRotation의 각도로 spawn
}
}
Bullet.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Bullet.generated.h"
UCLASS()
class BASIS_API ABullet : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ABullet();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit);
private:
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UStaticMeshComponent> StaticMeshComponent; // 모양을 나타내는 스태틱메시 컴포넌트
UPROPERTY(VisibleAnywhere)
TObjectPtr<class UProjectileMovementComponent> ProjectileMovementComponent; // 발사체의 움직임을 담당하는 컴포넌트
};
Bullet.cpp
//Fill out your copyright notice in the Description page of Project Settings.
#include "Bullet.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/ProjectileMovementComponent.h"
#include <CharacterBase.h>
// Sets default values
ABullet::ABullet()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
RootComponent = StaticMeshComponent;
ProjectileMovementComponent = CreateDefaultSubobject<UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
ProjectileMovementComponent->InitialSpeed = 20000.0f; // 총알 초기 속도
ProjectileMovementComponent->MaxSpeed = 20000.0f; // 총알 최대 속도
}
// Called when the game starts or when spawned
void ABullet::BeginPlay()
{
Super::BeginPlay();
StaticMeshComponent->OnComponentHit.AddDynamic(this, &ABullet::OnHit);
}
// Called every frame
void ABullet::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void ABullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
UE_LOG(LogTemp, Warning, TEXT("OnHit Called")); // 로그
ACharacterBase* CB = Cast<ACharacterBase>(OtherActor); // OnHit된 OtherActor가 ACharacterBase로 Cast되고
AActor* Actor = GetOwner(); // 총알의 주인인 총
if (!IsValid(Actor)) // 총이 없으면 리턴
{
return;
}
ACharacterBase* OwnerCB = Cast <ACharacterBase>(Actor->GetOwner()); // 총의 주인인 액터를 Owner에 대입
if (!IsValid(OwnerCB))
{
return;
}
if (IsValid(CB)) // 맞은 대상인 CB가
{
CB->Hit(OwnerCB->Strength, OwnerCB); // CB에게 Owner가 Owner의 Strength 만큼 Hit
}
Destroy();
}
CharacterAnimInstance.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "CharacterAnimInstance.generated.h"
UCLASS()
class BASIS_API UCharacterAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
virtual void NativeInitializeAnimation() override; // 애니메이션이 초기화 될때 실행
virtual void NativeUpdateAnimation(float DeltaSeconds) override; // 애니메이션이 업데이트 될때마다 실행(DeltaSeconds는 전 업데이트부터 지금까지 걸린시간)
UFUNCTION()
void AnimNotify_Attack();
UPROPERTY(BlueprintReadOnly)
TObjectPtr<class ACharacterBase> Character;
UPROPERTY(BlueprintReadOnly)
TObjectPtr<class UCharacterMovementComponent> MovementComponent; // 캐릭터의 움직임 저장
UPROPERTY(BlueprintReadOnly)
FRotator ControlRot;
UPROPERTY(BlueprintReadOnly)
FRotator ActorRot;
UPROPERTY(BlueprintReadOnly)
float Speed;
UPROPERTY(BlueprintReadOnly)
int32 AttackCount;
};
CharacterAnimInstance.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "CharacterAnimInstance.h"
#include "CharacterBase.h"
#include "GameFramework/CharacterMovementComponent.h"
void UCharacterAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
Character = Cast<ACharacterBase>(TryGetPawnOwner()); // 애니메이션을 가진 주인을 가져움
if (IsValid(Character))
{
MovementComponent = Character->GetCharacterMovement(); // 캐릭터의 움직임을 MovementComponent에 대입
}
}
void UCharacterAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (IsValid(MovementComponent))
{
Speed = MovementComponent->Velocity.Size();
AttackCount = Character->AttackCount;
}
if (IsValid(Character))
{
ActorRot = Character->GetActorRotation();
ControlRot = Character->GetControlRotation();
}
}
void UCharacterAnimInstance::AnimNotify_Attack()
{
Character->AttackCount = 0;
AttackCount = 0;
}
C++로 Character와 Weapon, Bullet을 구현하고 C++ 기반으로 블루프린트 클래스 생성 후 메시 또는 스켈레탈 메시와 애니메이션을 할당한다.
Input Action과 InputMappingContext도 설정하게 되면 캐릭터를 움직이고 좌클릭으로 총 발사, 우클릭으로 zoom까지 할 수 있다.
