no image
[ C++ ] 프로그래머스 카드 뭉치
https://school.programmers.co.kr/learn/courses/30/lessons/159994 프로그래머스SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프programmers.co.kr 문제  나의 풀이- goal에 있는 단어를 cards1과 cards2의 첫번째 글자와 각각 비교한다.- 비교했을 때 둘 다 없으면 No를 바로 반환해버린다.- 있으면 있는 쪽의 0번째 단어를 삭제한다.이렇게 비교하면 순차적으로 비교할 수 있으면서 깔끔하게 코드를 작성할 수 있을 것 같아 이렇게 로직을 설계하였다. 내 코드#include #include #include #include using namespace std;string soluti..
2025.02.10
no image
[ UE, C++ ] 아이템 만들기
C++ 코드를 사용해서 아이템을 구현해보았다.스코어를 쌓을 수 있는 코인, 체력을 회복하는 회복포션, 범위 안에 들어가면 터지는 지뢰가 있다.아이템들은 플레이어와 닿았을 때의 충돌처리가 가장 중요한 것 같다.  언리얼 충돌 옵션들Collsion 카테고리의 여러 옵션들을 보면 충돌과 물리에 대한 옵션들이 많이 있는데, 상황에 맞게 어떻게 쓸 수 있는지 정리해보았다. Collsion PresetNoCollision : 충돌 자체를 처리하지 않을 때BlockAll : 모든 것들을 막을 때(벽이나, 바닥같은)OverlapAll : 모든 객체 통과하는지 감지(주로 트리거 장치에 주로 사용)BlockAllDynamic : 움직이는 객체만 막음(플레이어만 상호작용하고 싶을 때)OverlapAllDynamic : 움직..
2025.02.07
no image
[ UE ] StateMachine을 활용해 캐릭터에 생기 불어넣기
오늘은 저번에 만들었던 Chacter Class를 다시 가져와서 생기를 불어넣어 주려고 한다.애니메이션을 만들어줄건데, 리소스는 언리얼에서 제공해주는거 그대로 사용하려고 한다. Animaition Blueprint 만들기애니메이션 블루프린트를 먼저 만들어 준다.아 네이밍은 Animation Blueprint를 줄여서 ABP_땡땡땡 으로 해주면 가독성이 더 좋을 것 같다. 들어가보면 Event 그래프와 Anim 그래프가 있다. Event Graph- 애니메이션 로직과 관련된 계산이나 변수 업데이트 등을 수행- 캐릭터의 이동 속도, 방향, 상태(isFalling, isMoving과 같은?) 정보를 기반으로 애니메이션 전환 조건 계산 또는 중요한 이벤트를 처리 Anim Graph- 실제로 애니메이션 데이터를 ..
2025.02.06
no image
[ UE, C++ ] Pawn 클래스로 액터 움직이기
오늘은 저번에 Character 클래스로 간단하게 구현했던 부분들을 Pawn 클래스에서 구현해보려고 한다.언리얼에서 기본적으로 제공해주는 내장기능을 이용하지 않고 직접 구현하는 방식으로...   전체적인 로직의 흐름이나 라이프 사이클은 저번에 구현했던 Character 클래스와 유사하게 가지만,나의 개발길을 편하게 해주는.. 천사같은 언리얼 내장 함수는 .. 이번엔.. 과감..하게 버린채로 순수 구현을 해보려고한다.. void AMyCharacter::Move(const FInputActionValue& value){ FVector2D MoveInput = value.Get(); FVector Forward = GetActorForwardVector(); FVector Right = GetActorRig..
2025.02.05
[ UE ] 개발하면서 알아두면 좋은 내용들
오늘은 언리얼 엔진으로 개발할 때 꼭 알아야하는 내용들을 정리해보았다. 좌표계 축x 축 : Rolly 축 : Pitchz 축 : Yaw  블루프린트와 C++의 차이점블루프린트는 즉긱적인 테스트 가능  리플렉션 시스템#include "Item.generated.h”리플렉션 시스템을 지원하는 헤더 파일, 항상 선언 가장 마지막에 위치해줘야함UCLASS()클래스를 리플렉션 시스템에 등록(등록되었기 때문에 블루프린트에서 접근 가능)UCLASS() == UCLASS(Blueprintable, BlueprintType)Blueprintable : 클래스 상속이 블루프린트에서 가능NotBlueprintable : 블루프린트에서 상속 불가능BlueprintType : 블루프린트에서 해당 클래스를 변수로 선언하거나 참조..
2025.02.04
no image
[UE, C++] 캐릭터 움직여보기
드디어 언리얼 에디터 상에서 캐릭터를 움직여보려고 한다오늘은 애니메이션 적용까지는 하지않고 간단한 이동까지만 진행했다. 먼저 캐릭터를 만들기 전에 알아야하고, 캐릭터 이동에 쓰이는 이론들이 있기 때문에 내가 이해한대로 쉽게 정리를 해보았다. 월드 상의 무언가를 움직일 수 있게 하는 클래스월드 상에서 움직임을 구현하는 클래스 중에서는 Pawn Class가 있고, Character Class가 있다.이렇게 클래스를 검색했을 때 Pawn이 Character보다 더 상위 클래스인 것을 알 수 있다.이것만 보고 예상할 수 있는것은 Character 클래스는 Pawn 클래스보다 제공하는 함수가 더 많으려나?하고 예상을 해보았다. Pawn Class플레이어가 소유할 수 있는 가장 부모의 클래스하나하나 손수 로직을 짜..
2025.02.03
C++
no image
[ UE ] Texture Coordinates 노드 활용 - 타일링
점프맵을 제작하면서 Ground의 크기를 변경하게 되었는데, Ground가 커질 수록 적용했던 Material의 Texture도 같이 커지는 현상을 발견했다 아무래도.. 눈에 바로 보이는 부분인지라.. 어떻게 해결하면 좋을지 검색하다가 타일링이라는 것을 발견했다. 타일링(Tiling)타일링(Tiling)은 주로 게임 환경을 효율적으로 구성하고, 리소스를 절약하며, 일관된 디자인을 유지하기 위해 반복 가능한 패턴이나 요소를 사용하는 기법이다.타일링은 다양한 방식으로 적용될 수 있으며, 주로 텍스처, 메시(mesh), 레벨 디자인 등 여러 분야에서 활용된다. 그래서 나도 Ground의 텍스처를 일관되도록 변경하고 싶어서 사용하게 되었다.  TextureCoordinates 노드먼저 타일링을 적용하고 싶은 m..
2025.01.31
no image
[ UE, C++ ] Actor를 다양하게 회전시켜보자
오늘은 퍼즐맵을 만들면서 회전하는 발판을 구현했다.그런데 구현하는 과정에서 새로 배운 것이 있어 이렇게 메모하기 위해 글을 작성하게 되었다. 회전 구현 코드먼저 내가 구현했던 회전하는 로직의 예제 코드는 다음과 같다.void ARotationActor::Tick(float DeltaTime){ Super::Tick(DeltaTime); if (!FMath::IsNearlyZero(RotationSpeed)) AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));Tick 함수에서 DeltaTime을 사용해 프레임별 일정하게 회전하도록 설정했고, 프레임 마다 값이 있을 때만 안전하게 계산하고자 IsNearlyZero를 통해 값체크를 ..
2025.01.27
no image
[ UE ] 퍼즐스테이지 설계 및 구상
C++ 코드를 활용한 퍼즐 스테이지를 만들어야한다. 먼저 기능을 어떤 것을 추가해야하고 무엇을 만들어야하는지 먼저 구상해보았다.   프로젝트 설계그리고 발판의 기믹은 여러 개로 하고 싶어서 고민하다가.. 밟으면 바로 떨어지는? 그런 기믹도 괜찮을 것 같고..안보였다가 닿이면 보이는 그런 발판도 괜찮을 것 같다.(슈퍼마리오의 투명블럭 느낌?)아직 더 좋은 아이디어가 나지 않아서 유튜버들이 다른 플랫폼 게임을 하는 영상을 찾아봐야겠다(합법적 휴식) 일단 스테이지 컨셉부터 잡아야 발판 배치나 기믹에 대한 아이디어가 떠오를 것 같아서(사실 그래야 눈에 보여서 재미있기 때문..ㅎ)Fab을 돌아다녔다. 에셋 찾는게 가장 시간이 많이 걸리는데.. 결국 원하는 테마를 찾지도 못했다.. 발판 기믹을 구성- 회전하는 발판..
2025.01.24

https://school.programmers.co.kr/learn/courses/30/lessons/159994

 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

문제

 

 

나의 풀이
- goal에 있는 단어를 cards1과 cards2의 첫번째 글자와 각각 비교한다.
- 비교했을 때 둘 다 없으면 No를 바로 반환해버린다.
- 있으면 있는 쪽의 0번째 단어를 삭제한다.

이렇게 비교하면 순차적으로 비교할 수 있으면서 깔끔하게 코드를 작성할 수 있을 것 같아 이렇게 로직을 설계하였다.

 

내 코드
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

string solution(vector<string> cards1, vector<string> cards2, vector<string> goal) {
    for (int i = 0; i < goal.size(); i++)
    {
        if (cards1.size() > 0 && goal[i] == cards1[0])
            cards1.erase(remove(cards1.begin(), cards1.end(), goal[i]));
        else if (cards2.size() > 0 && goal[i] == cards2[0])
            cards2.erase(remove(cards2.begin(), cards2.end(), goal[i]));
        else
            return "No";
    }
    
    return "Yes";
}

 

 

remove와 erase를 같이 쓴 이유

remove와 erase를 다룬 게시물 첨부 예정

C++ 코드를 사용해서 아이템을 구현해보았다.

스코어를 쌓을 수 있는 코인, 체력을 회복하는 회복포션, 범위 안에 들어가면 터지는 지뢰가 있다.

아이템들은 플레이어와 닿았을 때의 충돌처리가 가장 중요한 것 같다.

 

 

언리얼 충돌 옵션들

Collsion 카테고리의 여러 옵션들을 보면 충돌과 물리에 대한 옵션들이 많이 있는데, 상황에 맞게 어떻게 쓸 수 있는지 정리해보았다.

 

Collsion Preset

  • NoCollision : 충돌 자체를 처리하지 않을 때
  • BlockAll : 모든 것들을 막을 때(벽이나, 바닥같은)
  • OverlapAll : 모든 객체 통과하는지 감지(주로 트리거 장치에 주로 사용)
  • BlockAllDynamic : 움직이는 객체만 막음(플레이어만 상호작용하고 싶을 때)
  • OverlapAllDynamic : 움직이는 객체만 처리함(센서용으로 많이 사용)
  • Pawn : Pawn 타입 객체들 전용으로 충돌처리

 

Collsion Enabled

  • Collision Enable(Query and Physics) : 충돌, 물리적 감지 모두 활성화 ← 보통의 상황에 주로 사용
  • Query Only(No Physics Collsion) : 충돌만 감지(overlap, hit) 물리적 감지 반응 x
  • Physics Only(Noe Query Collsion) : 물리적 감지 x

 

이렇게 다양한 옵션들이 있으니 상호작용하는 물체마다 적절한 옵션을 넣어주면 될 것 같다.

 

아이템 클래스 설계

아이템들을 구현하기 위해 설계를 어떻게 하면 좋을지 고민해보았다.

 

이렇게 공통 특성을 묶어서 설계하면 코드의 유지보수가 엄청 편해진다는 것을 이해했다.

 

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

#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "ItemInterface.generated.h"

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UItemInterface : public UInterface
{
	GENERATED_BODY()
};

class PRACTICE_API IItemInterface
{
	GENERATED_BODY()

public:
	UFUNCTION()
	virtual void OnItemOverlap(
		UPrimitiveComponent* OverlappedComp,  // SphereComponent
		AActor* OtherActor,  // SphereComponent에 부딪힌 다른 Actor
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult
	) = 0;
	UFUNCTION()
	virtual void OnItemEndOverlap(
		UPrimitiveComponent* OverlappedComp,  // SphereComponent
		AActor* OtherActor,  // SphereComponent에 부딪힌 다른 Actor
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex
	) = 0;
		
	virtual void ActivateItem(AActor* Activator) = 0;
	virtual FName GetItemType() const = 0;
};

인터페이스는 순수가상함수로만 이뤄진 클래스를 말한다.

모두 순수가상함수로 정의해서 상속받은 클래스에서 무조건 해당 함수를 정의하게 만들어줬고, 오버랩 이벤트를 사용해주었다.

 

참고로 인터페이스 클래스라고해도 cpp 파일은 삭제하지 않는 편이 좋다고 한다. 엔진에서 컴파일할 때 존재를 알려주는 역할? 로 작용한다고 한다.

 

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ItemInterface.h"
#include "BaseItem.generated.h"

class USphereComponent;

UCLASS()
class PRACTICE_API ABaseItem : public AActor, public IItemInterface
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ABaseItem();

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Item")
	FName ItemType;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	USceneComponent* Scene;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	USphereComponent* Collision;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Item|Component")
	UStaticMeshComponent* StaticMesh;

	virtual void OnItemOverlap(
		UPrimitiveComponent* OverlappedComp,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex,
		bool bFromSweep,
		const FHitResult& SweepResult
	) override;
	virtual void OnItemEndOverlap(
		UPrimitiveComponent* OverlappedComp,
		AActor* OtherActor,
		UPrimitiveComponent* OtherComp,
		int32 OtherBodyIndex
	) override;
	virtual void ActivateItem(AActor* Activator) override;
	virtual FName GetItemType() const override;

	virtual void DestroyItem();


	
};

BaseItem에서는 아이템들이 가지게 될 루트 컴포넌트와 충돌 컴포넌트, 메쉬를 정의해주었다.

블루프린트에서도 접근할 수 있도록 매크로도 추가해주었다.

 

+ 스크린상에서 로그 띄우는 방법

GEngine->AddOnScreenDebugMessage(id, 몇초동안 띄울지, 폰트색상, 출력하고싶은 문구);

이렇게 해주면 된다.

GEngine->AddOnScreenDebugMessage(-1, 2.0f, FColor::Blue, FString::Printf(TEXT("Overlap!!")));

나는 이렇게 사용해줬다. id 의 -1은 id가 로그출력될 때마다 바로바로 생성된다는 의미로 이해하면 될 것 같다.

 

 

결과

 

이렇게 구현한대로, 플레이어와 아이템이 닿으면 아이템이 사라지고 로그가 스크린에 표출되는 것을 볼 수 있다.

하지만 지뢰에서 SphereCollision의 반지름을 300.0f 나 크게 설정해줬는데 로그를 보면 ExplosionCollision이 아니라 Mine 자체의 Collision에서 로그가 나오는 것을 볼 수 있다.

왜그런지는 내일 다시 코드를 살펴보고 수정해야 할 사항이다.

 

 

마지막 말

Overlap과 Physics의 Collision은 어떤 차이점이 있을까? 물리를 추가하면 중력이나 여러 부가적인 기능 때문에 무거워지는 것 같지만 어떤 상황에서 Overlap과 물리의 Collision을 사용하는 건지 궁금해졌다.

어떤 상황이 두 기능의 특징들을 잘 살릴 수 있는지 테스트를 해보려고 한다.

 

오늘은 저번에 만들었던 Chacter Class를 다시 가져와서 생기를 불어넣어 주려고 한다.

애니메이션을 만들어줄건데, 리소스는 언리얼에서 제공해주는거 그대로 사용하려고 한다.

 

Animaition Blueprint 만들기

애니메이션 블루프린트를 먼저 만들어 준다.

아 네이밍은 Animation Blueprint를 줄여서 ABP_땡땡땡 으로 해주면 가독성이 더 좋을 것 같다.

 

들어가보면 Event 그래프와 Anim 그래프가 있다.

 

Event Graph

- 애니메이션 로직과 관련된 계산이나 변수 업데이트 등을 수행

- 캐릭터의 이동 속도, 방향, 상태(isFalling, isMoving과 같은?) 정보를 기반으로 애니메이션 전환 조건 계산 또는 중요한 이벤트를 처리

 

Anim Graph

- 실제로 애니메이션 데이터를 가져와서 캐릭터에 입히는 역할

- State Machine을 통해 애니메이션 간 전환을 원활하게 연결

 

 

결과

 

 

마지막 말

아직은 캐릭터의 움직임이 디테일하지는 못한 것 같다.

뒤로가는 부분에서 뒤로 이동은 하지만 캐릭터의 발이 앞으로 가는 모션이라 조금 어색하다.

그리고 회전하는 부분에서도 너무 경직된 느낌?

애니메이션을 어떻게 조화롭게 넣어줘야할지 조금 더 고민해봐야겠다.

 

그리고 공부하면서 신기했던 것은 애니메이션 블루프린트의 Event BlueprintUpdateAnimation이라는 노드가 Tick 함수처럼 매 프레임마다 호출하는 역할을 하는 것이 신기했다.

오늘은 저번에 Character 클래스로 간단하게 구현했던 부분들을 Pawn 클래스에서 구현해보려고 한다.

언리얼에서 기본적으로 제공해주는 내장기능을 이용하지 않고 직접 구현하는 방식으로...

 

 

 

전체적인 로직의 흐름이나 라이프 사이클은 저번에 구현했던 Character 클래스와 유사하게 가지만,

나의 개발길을 편하게 해주는.. 천사같은 언리얼 내장 함수는 .. 이번엔.. 과감..하게 버린채로 순수 구현을 해보려고한다..

 

void AMyCharacter::Move(const FInputActionValue& value)
{
	FVector2D MoveInput = value.Get<FVector2D>();

	FVector Forward = GetActorForwardVector();
	FVector Right = GetActorRightVector();
	FVector MoveDirection = (Forward * MoveInput.Y) + (Right * MoveInput.X);
	MoveDirection.Z = 0.0f;

	const float DeltaTime = GetWorld()->GetDeltaSeconds();
	FVector DeltaLocation = MoveDirection * MoveSpeed * DeltaTime;
	AddActorLocalOffset(DeltaLocation, true);
}

 

Move에서는 앞 뒤 이동을 GetActorForwardVector로 가져와줬고, 좌우도 RightVector를 가져와서 처리해주었다.

그리고 Tick 함수는 프레임마다 돌아서 부담이 많이 된다고 하길래 Move 함수 안에서 DeltaTime을 가져와 구현해보았다.

 

void AMyCharacter::Look(const FInputActionValue& value)
{
	FVector2D LookInput = value.Get<FVector2D>();

	float YawDelta = LookInput.X * YawSensitivity;
	AddActorLocalRotation(FRotator(0.0f, YawDelta, 0.0f));

	FRotator CurrentRotation = SpringArmComponent->GetRelativeRotation();
	float NewPitch = FMath::Clamp(CurrentRotation.Pitch + LookInput.Y * PitchSensitivity, MinPitch, MaxPitch);
	SpringArmComponent->SetRelativeRotation(FRotator(NewPitch, CurrentRotation.Yaw, CurrentRotation.Roll));
}

그리고 카메라 회전부분인데.. 여기서 좀 많이 어려워서.. 채찍피티의 도움을 살짝(아주많이.).. 받았다

 

 

짜잔.. 캐릭터 내가 원한건 카메라의 시점이 회전하는 걸 원했는데 캐릭터 자체가 회전해버린다.

뭐가 문제인지 내일 찬찬히 뜯어봐야겠다.

 

아.. 그리고 슬프게도 점프는 중력을 구현하려다 실패해서 이 아이는 아직 점프를 배우지 못한.. 슬픈..아이이다..

오늘은 언리얼 엔진으로 개발할 때 꼭 알아야하는 내용들을 정리해보았다.

 

좌표계 축

x 축 : Roll

y 축 : Pitch

z 축 : Yaw

 

 

블루프린트와 C++의 차이점
  • 블루프린트는 즉긱적인 테스트 가능

 

 

리플렉션 시스템
  • #include "Item.generated.h”
    • 리플렉션 시스템을 지원하는 헤더 파일, 항상 선언 가장 마지막에 위치해줘야함
  • UCLASS()
    • 클래스를 리플렉션 시스템에 등록(등록되었기 때문에 블루프린트에서 접근 가능)
    • UCLASS() == UCLASS(Blueprintable, BlueprintType)
    • Blueprintable : 클래스 상속이 블루프린트에서 가능
    • NotBlueprintable : 블루프린트에서 상속 불가능
    • BlueprintType : 블루프린트에서 해당 클래스를 변수로 선언하거나 참조할 수 있게 함
  • GENERATED_BODY()
    • 리플렉션 데이터들을 자동으로 생성하기 위해서 사용하는 매크로

[UE, C++] 캐릭터 움직여보기

닿메_dahme
|2025. 2. 3. 21:24

드디어 언리얼 에디터 상에서 캐릭터를 움직여보려고 한다

오늘은 애니메이션 적용까지는 하지않고 간단한 이동까지만 진행했다.

 

먼저 캐릭터를 만들기 전에 알아야하고, 캐릭터 이동에 쓰이는 이론들이 있기 때문에 내가 이해한대로 쉽게 정리를 해보았다.

 

월드 상의 무언가를 움직일 수 있게 하는 클래스

월드 상에서 움직임을 구현하는 클래스 중에서는 Pawn Class가 있고, Character Class가 있다.

이렇게 클래스를 검색했을 때 Pawn이 Character보다 더 상위 클래스인 것을 알 수 있다.

이것만 보고 예상할 수 있는것은 Character 클래스는 Pawn 클래스보다 제공하는 함수가 더 많으려나?하고 예상을 해보았다.

 

Pawn Class
  • 플레이어가 소유할 수 있는 가장 부모의 클래스
  • 하나하나 손수 로직을 짜줘야함

 

Character Class
  • 캐릭터를 움직이기 위한 여러 컴포넌트 붙어져 있음
  • 이족보행의 인간형 캐릭터만 적용할 수 있음
  • 장점
    • Jump, StopJump, Sprint 같은 캐릭터 이동을 구현할 때 필수적으로 구현해야하는 함수들이 내장되어있어 빠르고 간편하게 구현 가능함

 

PlayerComtroller Class
  • 만들어둔 캐릭터에 PlayerController가 빙의하는 것
    • 캐릭터가 3개가 있다고 할 때, PlayerController에 따라 어떤 캐릭터를 조작하는지 결정됨
  • 주요기능
    • Enhanced Input Sysytem - 입력처리(언리얼 엔진 5)
      • Input Mapping System (IMC)
        • IA들을 총괄해서 관리하는 역할
      • Input Action (IA) - 특정 동작 추상화(특정 동작 1개에 대해서만 전문적으로 처리) ← 한 동작 하나하나를 따로따로 저장하는 방식
        • 점프 → IA_Jump → Jump 함수
        • 마우스 회전 → IA_Look → Look 함수
        • 이동 → IA_Move → Move 함수
      • Enhanced Input Sysytem 장점
        1. IA로 동작별로 독립적으로 구현해놓고, IMC로 총괄하면 나중에 키 입력을 A → F로 변경하고 싶을 때 IA의 내부로직을 건드리지 않고도 IMC에서 간편하게 변경(관리) 가능
        2. 캐릭터가 자동차를 탔을 때 자동차의 입력으로 변경되어야하는 상황에서 IMC만 갈아끼울 수 있음(간단하게)
          • 사람 전용 IMC
          • 자동차 전용 IMC
          • 이렇게 있는데 if 문으로 조건에 맞게 활성화/비활성화 하는 방식
    • 카메라 제어 로직
    • UI 상호작용
      • 플레이어가 버튼을 누르거나 드래그 앤 드랍 이벤트가 발생했거나 하는 상황(어떤 상황인지 잘 이해안됨)
    • Possess / Unpossess - 빙의
      • 해당 캐릭터나 폰을 움직일 수 있게 해줌

 

 

예제 코드
#include "MyCharacter.h"
#include "MyPlayerController.h"
#include "EnhancedInputComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Camera/CameraComponent.h"

AMyCharacter::AMyCharacter()
{
	PrimaryActorTick.bCanEverTick = false;

	SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArmComp->SetupAttachment(RootComponent);
	SpringArmComp->TargetArmLength = 300.0f;
	SpringArmComp->bUsePawnControlRotation = true;
	
	CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
	CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);  // 소켓네임이 스프링암의 끝부분을 나타냄
	CameraComp->bUsePawnControlRotation = false;

	NormalSpeed = 600.0f;
	SprintSpeedMultiplier = 1.7f;
	SprintSpeed = NormalSpeed * SprintSpeedMultiplier;

	GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;


}

void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
}

void AMyCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		if (AMyPlayerController* PlayerController = Cast<AMyPlayerController>(GetController()))
		{
			if (PlayerController->MoveAction)
			{
				EnhancedInput->BindAction(PlayerController->MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
			}
			if (PlayerController->JumpAction)
			{
				EnhancedInput->BindAction(PlayerController->JumpAction, ETriggerEvent::Triggered, this, &AMyCharacter::StartJump);
				EnhancedInput->BindAction(PlayerController->JumpAction, ETriggerEvent::Completed, this, &AMyCharacter::StopJump);
			}
			if (PlayerController->LookAction)
			{
				EnhancedInput->BindAction(PlayerController->LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
			}
			if (PlayerController->SprintAction)
			{
				EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Triggered, this, &AMyCharacter::StartSprint);
				EnhancedInput->BindAction(PlayerController->SprintAction, ETriggerEvent::Completed, this, &AMyCharacter::StopSprint);
			}
		}
	}

}

void AMyCharacter::Move(const FInputActionValue& value)
{
	if (!Controller) return;

	const FVector2D MoveInput = value.Get<FVector2D>();

	if (!FMath::IsNearlyZero(MoveInput.X))
	{
		AddMovementInput(GetActorForwardVector(), MoveInput.X);
	}
	if (!FMath::IsNearlyZero(MoveInput.Y))
	{
		AddMovementInput(GetActorRightVector(), MoveInput.Y);
	}
}

void AMyCharacter::StartJump(const FInputActionValue& value)
{
	if (value.Get<bool>())
		Jump();
}

void AMyCharacter::StopJump(const FInputActionValue& value)
{
	if (!value.Get<bool>())
		StopJumping();
}

void AMyCharacter::Look(const FInputActionValue& value)
{
	FVector2D LookInput = value.Get<FVector2D>();

	AddControllerYawInput(LookInput.X);
	AddControllerPitchInput(LookInput.Y);  // ia 만들 때 y값은 반전시켜놓은 상태
}

void AMyCharacter::StartSprint(const FInputActionValue& value)
{
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
	}
}

void AMyCharacter::StopSprint(const FInputActionValue& Value)
{
	if (GetCharacterMovement())
	{
		GetCharacterMovement()->MaxWalkSpeed = NormalSpeed;
	}
}
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

class USpringArmComponent;
class UCameraComponent;
struct FInputActionValue;

UCLASS()
class PRACTICE_API AMyCharacter : public ACharacter
{
	GENERATED_BODY()

public:
	AMyCharacter();

protected:	
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;

	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	USpringArmComponent* SpringArmComp;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	UCameraComponent* CameraComp;

	UFUNCTION()
	void Move(const FInputActionValue& value);  // 참조로 가져오는 이유 : 구조체는 엄청 큰 사이즈이기 때문에 성능, 복사 비용이 크다. 그래서 참조로 가져옴
	UFUNCTION()
	void StartJump(const FInputActionValue& value);  // Trigger로 받는 애들은 시작과 끝을 나눠주는 것이 좋음(EnhancedInputSystem의 취지와도 맞음)
	UFUNCTION()
	void StopJump(const FInputActionValue& value);
	UFUNCTION()
	void Look(const FInputActionValue& value);
	UFUNCTION()
	void StartSprint(const FInputActionValue& value);
	UFUNCTION()
	void StopSprint(const FInputActionValue& value);

private:
	float NormalSpeed;
	float SprintSpeedMultiplier;  // 몇 배 곱해줄건지
	float SprintSpeed;


};

 

이렇게 구현을 해보았다. 지금은 Character 클래스를 상속받아 구현하는 중이다.

 

 

결과

 

자세가 좀 경직되어있지만 잘 움직이는 것을 볼 수 있다.

 

 

마지막 말

Character 클래스가 Jump나 여러 함수를 제공해줘서 여러모로 편했다.

Pawn 클래스를 상속받아 하나하나 구현해보면 더욱 실력이 늘 것 같다.

또한, 언리얼의 장점인 오픈소스답게 Character 클래스에서 제공하는 Jump 함수는 어떤 로직을 사용하는지, 엔진 내부 코드들을 하나하나 뜯어볼 수 있어서 재밌었던 것 같다.

 

 

점프맵을 제작하면서 Ground의 크기를 변경하게 되었는데, Ground가 커질 수록 적용했던 Material의 Texture도 같이 커지는 현상을 발견했다

 

아무래도.. 눈에 바로 보이는 부분인지라.. 어떻게 해결하면 좋을지 검색하다가 타일링이라는 것을 발견했다.

 

타일링(Tiling)

타일링(Tiling)은 주로 게임 환경을 효율적으로 구성하고, 리소스를 절약하며, 일관된 디자인을 유지하기 위해 반복 가능한 패턴이나 요소를 사용하는 기법이다.

타일링은 다양한 방식으로 적용될 수 있으며, 주로 텍스처, 메시(mesh), 레벨 디자인 등 여러 분야에서 활용된다.

 

그래서 나도 Ground의 텍스처를 일관되도록 변경하고 싶어서 사용하게 되었다.

 

 

TextureCoordinates 노드

먼저 타일링을 적용하고 싶은 material을 더블 클릭한 후 블루프린트로 들어간다.

들어가서 TextureCoordinates 노드를 추가하고, 왼쪽 Detail 패널에서 Coordinate Index와 UTiling, VTilng을 적절하게 만져주면 끝!

 

 

타일링 적용 전

적용하기 전 Ground이다.

보이는가..? 발판보다 잔디가 더 큰... 세상에 이런 일이..

 

자세히 보자

이렇게 군데군데 텍스처가 깨져있는 모습도 볼 수 있다.

 

타일링 적용 후

이제 타일링을 적절하게 만져준 후 적용한 모습이다.

짜잔 균일하게 적용된 모습을 볼 수 있다.

잘 안보이는가??

이렇게 원래의 잔디를 되찾은 모습을 볼 수 있다!

 

 

마지막 말

사실 모델링이나 디자인 쪽은 지식은 잘 몰랐지만 액터의 크기를 바꾸거나 서로 다른(모양, 크기 등) 액터에서 동일한 Material을 넣어줄 때가 많은데 타일링 기법 하나만 알아두면 여러모로 유용하게 사용할 수 있을 것 같다.

 

 

참고링크

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/coordinates-material-expressions-in-unreal-engine

 

 

 

 

오늘은 퍼즐맵을 만들면서 회전하는 발판을 구현했다.

그런데 구현하는 과정에서 새로 배운 것이 있어 이렇게 메모하기 위해 글을 작성하게 되었다.

 

회전 구현 코드

먼저 내가 구현했던 회전하는 로직의 예제 코드는 다음과 같다.

void ARotationActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (!FMath::IsNearlyZero(RotationSpeed))
		AddActorLocalRotation(FRotator(0.0f, RotationSpeed * DeltaTime, 0.0f));

Tick 함수에서 DeltaTime을 사용해 프레임별 일정하게 회전하도록 설정했고, 프레임 마다 값이 있을 때만 안전하게 계산하고자 IsNearlyZero를 통해 값체크를 한 번 더 해주었다.

 

+ 아! 그리고 여기에는 나와있지 않지만 BeginPlay에서 값을 set해줄 때 회전은 순서가 좀 신기했다.

FRotator NewRotation(0.0f, 0.0f, 0.0f);  // pitch, yaw, roll 순

 

이렇게 X, Y, Z 순이 아니라 Y, Z, X 순으로 초기화를 해줘야한다!

 

분명 같은 코드인데 다른 아웃풋이?!

상황 1. 액터 그 자체로 회전하는 상황

 

 

상황 2.  한 축을 중심으로 회전하는 상황

 

이렇게 코드는 동일하게 사용했지만, 아웃풋이 다르게 나타나는 상황이 발생했다.

 

내가 코드상 바꾼 것은 메시를 가져오는 부분인데.. 그래서 다시 살펴보았다.

 

메시별 기즈모 위치

1번 상황에서 사용한 메시의 기즈모는 액터의 센터에 있었다.

 

 

2번 상황에서 사용한 메시는 기즈모가 액터의 left top에 있었다.

 

그래서 같은 로직이라도 메시의 중점이 어디에 있느냐에 따라 아웃풋이 달라진다는 것을 알게 되었다..!

 

기즈모 중점 통일화

신기하긴 하지만 어쨋든.. 동일한 결과가 나와야하기 때문에 얘네들을 어떻게 교정을 해주면 좋을까?

 

이건 메시를 모델링하시는 모델러 분들에 따라 기즈모의 중점이 달라진다고 한다.

그래서 원래는 개발자가 아닌 모델러분께 요청드리면 되는 내용이지만 지금은 혼자서 개발하기 때문에(ㅜ.,ㅜ)

피벗을 센터로 적용해보려 한다.

 

 

피벗 교정

피벗을 변경하는 방법은 아래의 블로그에서 자세하게 나와있어 많은 도움이 되었다.

추가로 한 것 없이 따라하기만 해서 참고 링크를 올려두겠다.

(그리고 나는 회전을 좀 다양하게 주고 싶어서 피벗 교정하는 방법을 따라만 해보고 결론은 그대로 둘 예정이다)

 

언리얼 5 - 액터의 피벗 위치 변경하기 (How to Change Actor Pivots)

A형 필수 알고리즘을 체계적으로 배우고 싶다면? (인프런 바로가기) Unreal 5 전체 링크 언리얼에서는 액터(게임 오브젝트)의 Pivot을 게임 엔진 안에서 직접 바꿀 수 있다.피벗 위치 임시 변경하기

bloodstrawberry.tistory.com

 

 

오늘 배운 내용 및 마지막 말

의도치 않게 하나의 인풋으로 다양한 아웃풋이 나오는 상황을 경험했다.

이 경험을 통해 다양한 상황에서 로직을 유연하게 적용하는 방법을 또 하나 알아가는 것 같아서 뿌듯하다.

코드를 바꾸지 않고 회전 아웃풋을 변경하고 싶다면 간단하게 기즈모를 변경하는 방법으로 회전을 적용하는 것도 좋을 것 같다.

맵 제작에도 일단 회전방식이 둘 다 마음에 들어서 두 발판 모두 사용할 예정이다!   \( ̄︶ ̄*\))

 

C++ 코드를 활용한 퍼즐 스테이지를 만들어야한다.

 

먼저 기능을 어떤 것을 추가해야하고 무엇을 만들어야하는지 먼저 구상해보았다.

 

 

 

프로젝트 설계

그리고 발판의 기믹은 여러 개로 하고 싶어서 고민하다가.. 밟으면 바로 떨어지는? 그런 기믹도 괜찮을 것 같고..

안보였다가 닿이면 보이는 그런 발판도 괜찮을 것 같다.(슈퍼마리오의 투명블럭 느낌?)

아직 더 좋은 아이디어가 나지 않아서 유튜버들이 다른 플랫폼 게임을 하는 영상을 찾아봐야겠다(합법적 휴식)

 

일단 스테이지 컨셉부터 잡아야 발판 배치나 기믹에 대한 아이디어가 떠오를 것 같아서(사실 그래야 눈에 보여서 재미있기 때문..ㅎ)

Fab을 돌아다녔다. 에셋 찾는게 가장 시간이 많이 걸리는데.. 결국 원하는 테마를 찾지도 못했다..

 

발판 기믹을 구성

- 회전하는 발판
- 이동하는 발판
- 닿으면 사라지는 발판
- 닿으면 뚝! 떨어지는 발판

 

설계 단계에서 클래스를 어떻게 분리할 것인지

근데 이제 클래스 설계를.. 어떻게 해야할 지 고민중이다.

기능별로 클래스를 각각 만들 것인지, 아니면 발판 기믹별로 클래스를 만들 것인지..

기능별로 하면,, 필요한 기능이 있을 때마다 컴포넌트를 붙여서 쓸 수 있나?

아니면 기믹별로 클래스를 설계하면 똑같은 기믹이 있을 때 그냥 갖다 붙이면 편하고..

 

내가 하고 싶은건 기능별로 쪼개서 하나의 액터에 기능들이 필요할 때마다 갖다붙이는 방식으로 하고싶은데.. 이게 될까?

 

+ 궁금증이 해결되었다.

액터 클래스에서는 기능별로 갖다붙이는게 안된다고 한다. 이유는 내일 찾아보려고 한다.

그래서 이렇게 하려면 액터 컴포넌트? 를 사용하는 방식으로 해야한다고 하는데 그게 무엇인지는 내일 기능 구현하면서 같이 공부하려고 한다.