게임 개발/언리얼 5

언리얼5 - 언리얼 엔진 게임 제작 기초

싹난 감자 2024. 11. 23. 05:27

게임 콘텐츠의 구조

게임 제작을 위해 언리얼 엔진은 자체적으로 설계한 프레임워크를 제공함.

이를 게임플레이 프레임워크(Gameplay Framework)라고 부름.

언리얼 게임 프레임워크의 각 구성 요소를 파악하고, 이를 확장하면서 게임을 제작하는 것을 권장.

예제의 Step1 맵은 상자 3개를 얻으면 승리, 맵 밖으로 나가면 사망하는 구조.

 

사용된 게임 프레임워크 기능

월드(World)

  • 게임 콘텐츠를 담기 위해 제공되는 가상의 공간
  • 월드는 시간, 트랜스폼, 틱을 서비스로 제공.
  • 월드 세팅이라는 콘텐츠 제작을 위한 기본 환경 설정을 제공.
  • 월드의 기본 단위는 액터(Actor)로 정의되며, 액터 클래스는 언제나 접두사 A를 사용.

월드의 역할

  • 게임 콘텐츠를 담는 공간 (Transform)
  • 게임 콘텐츠의 진행 (Tick)
  • 게임 흐름을 위한 시간 (Time)
  • 기본 환경 설정 (World Setting)
  • 월드의 단위 구성 요소 (Actor)

게임 모드(Game Mode)

  • 게임 규칙을 지정하고 게임을 판정하는 최고 관리자 액터. 형태가 없음.
  • 언리얼 엔진에서 하나의 게임에는 반드시 하나의 게임 모드만 존재.
  • 게임 모드에서 입장할 사용자의 규격을 지정할 수 있음.
  • 멀티 플레이어 게임에서 판정을 처리하는 절대적 권위의 심판.

게임 모드의 역할

  • 플레이어 입장 관리
  • 게임의 최종 승패 관리
  • 게임의 규칙 관리
  • 게임 데이터 검증 (멀티 플레이 게임)

기믹(Gimmick)

  • 게임 진행을 위한 이벤트를 발생시키는 사물 액터
  • 주로 이벤트 발생을 위한 충돌 영역을 설정하는데, 이를 트리거(Trigger)라고 함.
  • 트리거를 통해 캐릭터와 상호 작용하고, 월드에 액터를 스폰해 콘텐츠를 전개함.

기믹의 역할

  • 월드 내 사물과 상호 작용을 수행
  • 지정된 조건을 만족하면 이벤트를 발생
  • 월드에 물체를 스폰(Spawn)시킬 수 있음

플레이어(Player)

  • 게임에 입장한 사용자 액터. 형태가 없음
  • 게임 모드의 로그인을 통해 사용자가 게임 월드에 입장하면 플레이어가 생성됨.
  • 싱글 플레이 게임에는 0번 플레이어가 설정됨
  • 사용자와 최종 커뮤니케이션을 담장. (예) 입력 장치의 해석, 화면 장치로의 출력)

플레이어의 역할

  • 게임에 입장한 사용자와 1:1 대응
  • 사용자의 현재 상태 및 데이터 관리
  • 사용자 입력의 1차 처리 (필요 시)
  • 사용자 화면에 대응하는 카메라 관리
  • 사용자 화면에 나타나는 HUD UI 관리

폰(Pawn)

  • 무형의 액터인 플레이어가 빙의해 조종하는 액터
  • 길찾기를 사용할 수 있으며, 기믹 및 다른 폰과 상호작용함
  • 폰 중에서 인간형 폰을 별도로 캐릭터라고 지칭.

폰의 역할

  • 빙의(Possess)를 통해 플레이어와 연결
  • 사용자 입력의 실제 처리
  • 사용자 화면에 대응되는 카메라 설정
  • 기믹과의 상호작용
  • 현재 상태에 적합한 애니메이션 재생

최종 예제 콘텐츠의 구성

최종 프로젝트에서는 가급적 블루프린트를 사용하지 않고 C++로만 제작. 이후에 블루프린트로 확장 가능.

1단계

월드 설정과 게임 모드 생성

               ↓

2단계

플레이어 폰의 입출력 및 모션 설정

               ↓

3단계

캐릭터에 반응하는 기믹 설계

               ↓

4단계

게임 데이터와 NPC 인공지능

               ↓

5단계

UI 연동 및 게임 제작 마무리

 

최종 예제에서 확장할 내용

  • 기본 템플릿을 사용하지 않고 직접 제작
  • 두 가지의 카메라 모드를 제공 (솔더뷰, 탑뷰)
  • 액션 입력을 통한 콤보 공격의 구현
  • 아이템 애셋을 활용한 무기 상자의 구현
  • NPC 레벨 정보를 스프레드 시트로 관리
  • 플레이어와 전투하는 NPC 인공지능의 구현
  • UI 연동과 게임의 마무리

프로젝트

C++ Blank 템플릿

프로젝트 이름은 ArenaBattle.

  • 주 게임 모듈 이름은 자동으로 ArenaBattle로 지정됨

프로젝트 소스코드 폴더 규칙

게임 플레이를 구성하는 요소별로 폴더를 제작해 관리

  • Game: 게임 모드와 게임 설정에 관련된 클래스 [1강에서 생성]
  • Gimmick: 기믹 배경 물체에 관련된 클래스
  • Player: 플레이어에 관련된 클래스 [1강에서 생성]
  • Input: 플레이어 입력에 관련된 클래스
  • Character: 기본 캐릭터와 PC 및 NPC에 관련된 클래스 [1강에서 생성]
  • Action: 캐릭터 액션에 관련된 클래스
  • Item: 캐릭터 아이템에 관련된 클래스
  • Stat: 캐릭터 스탯에 관련된 클래스
  • AI: NPC 인공지능에 관련된 클래스
  • UI: UI에 관련된 클래스
  • Physics: 물리 충돌체 설정에 관련된 전처리기
  • Interface: 인터페이스 클래스

클래스 생성 규칙

  • 모든 클래스는 AB(ArenaBattle)의 접두사로 시작
  • 폴더마다 다른 폴더와의 의존성을 최소화
  • 다른 폴더의 클래스에 접근할 때는 가급적 인터페이스를 통해 접근
  • 헤더 경로를 찾기 쉽도록 모듈.Build.cs 파일에 모듈의 폴더를 Include 경로에 설정
    • PublicIncludePaths.AddRange( new string[] {”모듈 이름”});

한 폴더에서 다른 폴더의 헤더를 직접 참조하지 않도록 구성하는 것이 바람직함.

최초 생성할 클래스

게임을 구성하는 기본 골격의 설정

  • 캐릭터는 PC와 NPC를 구분해 구현
  • 게임 모드에서는 입장할 플레이어의 규격을 지정
  • (AABPlayerController, AABCharacterPlayer)


생성한 프로젝트의 Content 폴더에 예제 프로젝트 ArenaBattle을 복사.

Content Browser에 뜨게 되는데 안뜨면 에디터 껐다켜기.

Basic으로 새 레벨 제작

 

바닥을 지우고 0,0,0 위치에 메쉬 배치

 

Place Actors에서 Player Start 끌어 배치.

Window / Place Actors를 활성화하면 창이 뜸

 

Player와 Player가 빙의한 Pawn이 Player Start에서 생성됨.

오브젝트를 선택하고 end 키를 누르면 바닥에 딱 붙일 수 있음.

 

File / Save Current Level As...로 현재 레벨 저장

언리얼은 보통 Maps라는 폴더를 만들어서 작업한 레벨과 월드를 저장함.

Project Setting에서 에디터가 실행될 때 바로 로딩할 레벨을 설정 

 

 Game Mode Base를 상속받아 C++ 클래스 생성

 

Game 폴더를 만들어 그 아래에 생성

 

경로가 맞지 않아 빌드 실패함.

 

ArenaBattle.Build.cs에 PublicIncludePaths.AddRange(new string[] { "모듈 이름" }); 추가해준 뒤 다시 빌드 (Ctrl+Alt+F11)

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class ArenaBattle : ModuleRules
{
	public ArenaBattle(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicIncludePaths.AddRange(new string[] { "ArenaBattle" });
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

ArenaBattle.Build.cs

빌드가 완료되면 Player Controller를 상속받은 ABPlayerController와 Character를 상속받은 ABCharacterBase 클래스도 각 파일에 생성.

 

ABCharacterBase를 상속받는 ABCharacterPlayer 클래스도 생성

 

Content Browser에 Add에서 Add Freature or Content Pack 클릭

Third Person 템플릿 추가

 

레벨에서 3인칭 템플릿을 제공하고 있는 게임 모드를 지정하게 되면 그 게임 모드를 사용해서 캐릭터가 로그인하고 등장하게 됨.

 

Outliner에서 레벨에 우클릭 - World Setting을 열고 GameMode Override에서 BP_ThirdPersonGameMode 선택 후

게임 플레이를 누르면 마네킹 캐릭터가 등장함.

 

이후 직접 만든 캐릭터로 변경 예정.

 

World Setting의 Selected GameMode 아래

 

Default Pawn Class: 등장시킬, 조종할 캐릭터 사물에 대한 클래스를 지정하는 것

Player Controller Class: 언리얼에서 기본으로 제공하는 플레이어 컨트롤러 사용 중.

 

GameMode를 아까 생성했던 ABGameMode로 변경하면 아직 아무값이 들어있지 않아 언리얼에서 제공하는 기본 클래스를 사용해서 플레이어를 스폰하도록 설계되어있음.

 

GameMode 설정

 

PlayerControllerClass 설정

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ABGameMode.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABGameMode : public AGameModeBase
{
	GENERATED_BODY()
	
public:
	AABGameMode();
	
};

ABGameMode.h

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


#include "Game/ABGameMode.h"
#include "ABGameMode.h"
#include "Player/ABPlayerController.h"
// PlayerController 클래스 정보 가져오기

AABGameMode::AABGameMode()
{
    // 멤버 변수 값 설정
    // DefaultPawnClass

    // PlayerControllerClass에 AABPlayerController 클래스 설정    
    PlayerControllerClass = AABPlayerController::StaticClass();
}

ABGameMode.cpp

AABPlayerController는 C++ 클래스이기 때문에 AABGameMode의 생성자에서 PlayerControllerClass 값을 AABPlayerController로 지정해준 뒤 컴파일 해보면 World Setting의 값이 변경된 것을 확인할 수 있다.

 

Default Pawn Class 설정

Default Pawn Class은 BP_ThirdPersonCharacter로 변경할텐데, C++ 클래스가 아니기 때문에 애셋으로부터 클래스 정보를 얻어와야함. 이것도 엄연히 Character를 상속받은 언리얼 체계에서의 클래스 구조를 따르기 때문에 클래스 정보를 얻어올 수 있음.

BP_ThirdPersonCharacter 애셋에 우클릭 후 Copy Reference로 애셋 주소 복사

 

경로 /Script/Engine.Blueprint'/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter' 에서 앞부분과 따옴표는 지우고 애셋 경로만 사용하며,

클래스 정보를 가져올 것이기 때문에 BP_ThirdPersonCharacter 애셋 이름 뒤에 _C를 붙여줌

/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter_C

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


#include "Game/ABGameMode.h"
#include "ABGameMode.h"
#include "Player/ABPlayerController.h"
// PlayerController 클래스 정보 가져오기

AABGameMode::AABGameMode()
{
    // 멤버 변수 값 설정
    // DefaultPawnClass 지정
    // 애셋에 담긴 클래스 정보를 얻어와서 설정해줌
    // 생성자 코드이기 때문에 ConstructorHelpers 함수를 사용해서 애셋 정보를 가져올 수 있음.
    // Pawn 클래스로 상속을 받았기 때문에 APawn으로 찾음
    static ConstructorHelpers::FClassFinder<APawn> ThirdPersonClassRef(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter_C"));
    if (ThirdPersonClassRef.Class) 
    {
        // 해당 레퍼런트 안에 있는 클래스 정보가 null이 아니라면
        // DefaultPawnClass 값을 ThirdPersonClassRef.Class로 지정
        DefaultPawnClass = ThirdPersonClassRef.Class;
    }

    // PlayerControllerClass에 AABPlayerController 클래스 설정    
    PlayerControllerClass = AABPlayerController::StaticClass();
}

ABGameMode.cpp

 

다시 빌드하면 World Setting의 값이 변경되어 있다. (라이브 코딩으로 반영되지 않을 때는 에디터를 껐다 켜기. 생성자 구문을 변경했을 때에는 에디터를 껐다 켜주는 것이 확실함)

 

게임이 시작되면 자동으로 마우스가 뷰 포트에 들어가도록 설정.

게임이 시작할 때 마우스 입력 (Mouse Input)을 뷰포트로 옮겨주는 코드를 작성해야함.

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ABPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class ARENABATTLE_API AABPlayerController : public APlayerController
{
	GENERATED_BODY()
	
protected:
	// 게임 시작 시 실행되는 함수
	virtual void BeginPlay() override;
	
};

ABPlayerController.h

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


#include "Player/ABPlayerController.h"

void AABPlayerController::BeginPlay()
{
    // 상속 받았기 때문에 Super로 한번 실행
    Super::BeginPlay();

    // FInputModeGameOnly는 구조체,
    // SetInputMode 함수에 FInputModeGameOnly 구조체를 넘겨주면
    // 시작하자마자 포커스가 뷰 포트 안으로 들어감
    FInputModeGameOnly gameOnlyInputMode;
    SetInputMode(gameOnlyInputMode);
}

ABPlayerController.cpp

컴파일 후 게임을 시작하면 자동으로 포커스가 뷰포트에 들어감. 마우스를 꺼내고 싶을 때는 Shift+F1이나 esc.

 

 

게임모드에서 헤더로 include하지 않고 ABPlayerController 클래스를 설정하는 방법.

우리가 만든 C++ 객체들은 계층구조, 폴더 구조로 되어 있는데 독립적인 파일로 구성되지 않지만 고유한 경로를 가짐.

ABPlayerController의 경우에도 고유 주소값을 가지고 있음.

BP_ThirdPersonCharacter를 추가했던 것처럼 주소를 사용하면 헤더를 include하지 않고 클래스 정보를 얻어올 수 있음.

 

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


#include "Game/ABGameMode.h"
#include "ABGameMode.h"
//#include "Player/ABPlayerController.h"
// PlayerController 클래스 정보 가져오기
// 애셋으로부터 직접 참조를 받으면 헤더를 추가하지 않아도 됨

AABGameMode::AABGameMode()
{
    // 멤버 변수 값 설정
    
    // DefaultPawnClass 지정
    static ConstructorHelpers::FClassFinder<APawn> ThirdPersonClassRef(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.BP_ThirdPersonCharacter_C"));
    if (ThirdPersonClassRef.Class) 
    {
        // 해당 레퍼런트 안에 있는 클래스 정보가 null이 아니라면
        // DefaultPawnClass 값을 ThirdPersonClassRef.Class로 지정
        DefaultPawnClass = ThirdPersonClassRef.Class;
    }

    // PlayerControllerClass에 AABPlayerController 클래스 설정
    // AABPlayerControllerClassRef는 클래스 정보가 바로 복사된 것이기 때문에 _C를 붙이지 않음
    static ConstructorHelpers::FClassFinder<APlayerController> PlayerControllerClassRef(TEXT("/Script/ArenaBattle.ABPlayerController"));
    if (PlayerControllerClassRef.Class)
    {
        PlayerControllerClass = PlayerControllerClassRef.Class;
    }
    //PlayerControllerClass = AABPlayerController::StaticClass();
}

ABGameMode.cpp