총과 총알 작성부터 나에겐 굉장한 멘붕이었다. 난이도가 너무 높았다.
먼저 작성한 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언어 부분에서 랑 &랑 정말 햇갈리는 부분이다. 뭔가 애매하게 이해해서 더 햇갈리는 것 같다. 다음장은 아마 포인터와 레퍼런스 정리를 한번 해볼까 생각중이다. 아마 포인터랑 &를 함수에서, 변수 선언시 자주 사용하게 될 것 같은데 날잡고 한번 공부를 쎄게 해봐야겠다!
오늘 내용은 이틀 한건데 진짜 어렵다 최고다 최고