[3일차] 직접 TPS 캐릭터 만들어보기-2

칼든개구리·2024년 12월 4일
0

[언리얼TO리얼]

목록 보기
7/42

총과 총알 작성부터 나에겐 굉장한 멘붕이었다. 난이도가 너무 높았다.

먼저 작성한 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 "MyCharacter.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<class UProjectileMovementComponent>(TEXT("ProjectileMovementComponent"));
	ProjectileMovementComponent->InitialSpeed = 20000.0f;
	ProjectileMovementComponent->MaxSpeed = 20000.0f;

}

// Called when the game starts or when spawned
void ABullet::BeginPlay()
{
	Super::BeginPlay();
	
}

// 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)
{
	AMyCharacter* CB = Cast<AMyCharacter>(OtherActor);
	AActor* Actor = GetOwner(); //총을 쏜 주인
	if (!IsValid(Actor)) //총이 없으면 리턴
	{
		return;
	}

	AMyCharacter* Owner = Cast<AMyCharacter>(Actor->GetOwner());

	if (!IsValid(Owner))
	{
		return;
	}

	if (IsValid(CB))//존재하면
	{
		CB->Hit(Owner->Strength, Owner);//맞은 대상이 존대하면 Hit
	}

}

다음은 총의 주인인 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 "PlayerBase.h"
#include "Bullet.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"));
	RootComponent = Mesh; //엑터의 최상위 항목을 이것으로 둔다

	MuzzleOffset = CreateDefaultSubobject<USceneComponent>(TEXT("MuzzleOffset"));
	MuzzleOffset->SetupAttachment(Mesh); //매쉬에 붙이는 하위항목
}

// Called when the game starts or when spawned
void AWeapon::BeginPlay()
{
	Super::BeginPlay();
	
}

void AWeapon::Fire()
{
	UAnimInstance* AnimInstance = Mesh->GetAnimInstance(); //GetAnimInstance: mesh에서 애니메이션 담당하는 부분 
	if (IsValid(AnimInstance) && IsValid(FireMontage)) //애니메이션과 FireMontage라는 애니메이션 존재 시 
	{
		AnimInstance->Montage_Play(FireMontage);
	}

	if (IsValid(Bullet))
	{
		FRotator SpawnRotation = MuzzleOffset->GetComponentRotation(); //총구 위치의 각도
		FVector SpawnLocation = MuzzleOffset->GetComponentLocation(); //총구 위치

		FActorSpawnParameters SpawnParams;
		SpawnParams.Owner = this; //총알의 소유주는 총으로 해야함

		APlayerBase* PB = Cast<APlayerBase>(GetOwner());

		if (!IsValid(PB)) //존재하지 않으면
		{
			GetWorld()->SpawnActor<ABullet>(Bullet, SpawnLocation, SpawnRotation, SpawnParams); //총알만 생성
			return;
		}

		APlayerController* PC = Cast<APlayerController>(PB->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); //가운데 부분을 실제 게임의 가운데, 실제 게임의 앞방향 출력
		WorldCenter += WorldFront * 10000; //실제 방향을 가운데서 100미터가량 앞으로 
		SpawnRotation = UKismetMathLibrary::FindLookAtRotation(SpawnLocation, WorldCenter); //각도 변환(시작 위치, 끝 위치)
		GetWorld()->SpawnActor<ABullet>(Bullet, SpawnLocation, SpawnRotation, SpawnParams); //액터를 어떤 각도로 소환 주체는? 어떤 것을? 소환할 것인지

	}
}

여기는 뭐 벡터도 나오고 getworld() 진짜 처음보는건데 등장해서 띠용 했다. 유니티에서도 그렇고 나는 애니메이션 영역에 매우 약한데 너무 어렵다 엉엉

다음으로 playerbase부분에도 총과 총알 부분을 추가해준다

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

#pragma once

#include "CoreMinimal.h"
#include "MyCharacter.h"
#include "Weapon.h"
#include "PlayerBase.generated.h"

class AWeapon;
class UInputAction;
struct FInputActionValue;


UCLASS()
class BASIS_API APlayerBase : public AMyCharacter
{
	GENERATED_BODY()

public:
	APlayerBase();
	virtual void BeginPlay() override;
	virtual void Hit(int32 Damage, AActor* Bywho) override;
	virtual void IncreaseKillCount() override;
	virtual void Attack() override;
	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
private:
	UPROPERTY(VisibleAnywhere)
	TObjectPtr<class USpringArmComponent> CameraBoom;//USpringArmComponent의 전방선언, TObjectPtr은 언리얼이 USpringArmConponent * a 를 TObjectPtr<USpringArmComponent>로 쓰기를 권하고 있음

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<class UCameraComponent> FollowCamera;

	UPROPERTY(EditAnywhere)
	TSubclassOf<AWeapon> Weapon; //dp

	UPROPERTY()
	TObjectPtr<AWeapon> WeaponActor; //실제 스폰되는 엑터

	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);
};

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"
#include "Weapon.h"

APlayerBase::APlayerBase()
{
	//컴포넌트들의 배치
	CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
	CameraBoom->SetupAttachment(RootComponent);

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
	FollowCamera->SetupAttachment(CameraBoom);

}

void APlayerBase::BeginPlay()
{
	Super::BeginPlay(); //상속받는 것이니 super로 처리해줘야함
	WeaponActor = GetWorld()->SpawnActor<AWeapon>(Weapon);
	if (Weapon)
	{
		FAttachmentTransformRules TransformRules(EAttachmentRule::SnapToTarget, true);
		WeaponActor->AttachToComponent(GetMesh(), TransformRules, TEXT("WeaponSocekt"));
		WeaponActor->SetOwner(this);
		WeaponActor->SetInstigator(this);
	}

}

void APlayerBase::Hit(int32 Damage, AActor* Bywho)
{
	Super::Hit(Damage, Bywho);
	if (CurrentHP > 0)
	{
		return;
	}
	Destroy();
}

void APlayerBase::IncreaseKillCount()
{
	Super::IncreaseKillCount();
}

void APlayerBase::Attack()

{
	Super::Attack();
	if (IsValid(WeaponActor))
	{
		WeaponActor->Fire();
	}
}

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::Triggered, this, &APlayerBase::Zoom);
	}
}

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);
		AddControllerYawInput(LookAxisVector.Y);

	}
}

void APlayerBase::Fire(const FInputActionValue& Value)
{
	//무기 완성 후 작성
	Attack();
}

void APlayerBase::Zoom(const FInputActionValue& Value)
{
	if (!IsValid(CameraBoom)) //IsValid: 널 포인터인지 삭제될 건지 진짜 존재하는지 체크하는 용도
	{
		return;
	}
	if (Value.Get<bool>())
	{
		CameraBoom->TargetArmLength = 40;
		CameraBoom->SocketOffset = FVector(0, 40, 60);

	}
	else
	{
		CameraBoom->TargetArmLength = 120;
		CameraBoom->SocketOffset = FVector(0, 60, 60);
	}
}

오늘의 어리둥절 타임은 컴공으로서 그러면 안되는것지만 포인터에서 1시간정도를 고군분투하였다

void ABullet::OnHit(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
	AMyCharacter* CB = Cast<AMyCharacter>(OtherActor);
	AActor* Actor = GetOwner(); //총을 쏜 주인
	if (!IsValid(Actor)) //총이 없으면 리턴
	{
		return;
	}

	AMyCharacter* Owner = Cast<AMyCharacter>(Actor->GetOwner());

	if (!IsValid(Owner))
	{
		return;
	}

	if (IsValid(CB))//존재하면
	{
		CB->Hit(Owner->Strength, Owner);//맞은 대상이 존대하면 Hit
	}

}

저기서 맨밑에 Hit함수 호출할 때 Owner 부분에 빨간줄이 생겨 이유를 보니
void AMyCharacter::Hit(int32 Damage, AActor& Bywho) 이렇게 적어둬서 흠 AActor을 받아야 하는데 AMycharacter을 줘서 그런가하고 해결을 못하고 있었는데
정답은 void AMyCharacter::Hit(int32 Damage, AActor Bywho)이다.
뒤에 AActor 부분을 포인터로 바꿔주면 되는 것이다. 아직 이해 안되서 회장동기 한테 물어보니 Owner가
선언이라 로 받아야 하는거 아니야? 물어봐서 일단 이해는 했다.]
진짜로 c언어 부분에서
랑 &랑 정말 햇갈리는 부분이다. 뭔가 애매하게 이해해서 더 햇갈리는 것 같다. 다음장은 아마 포인터와 레퍼런스 정리를 한번 해볼까 생각중이다. 아마 포인터랑 &를 함수에서, 변수 선언시 자주 사용하게 될 것 같은데 날잡고 한번 공부를 쎄게 해봐야겠다!
오늘 내용은 이틀 한건데 진짜 어렵다 최고다 최고

profile
메타쏭이

0개의 댓글