언리얼 오브젝트 관리 - 직렬화

Unreal 이론|2024. 9. 12. 17:24

[이득우의 언리얼 프로그래밍 수업의 정리]

 

직렬화(Serializaion)란?

  • 오브젝트나 연결된 오브젝트의 묶음(오브젝트 그래프)을 바이트 스트림으로 변환하는 과정.
    • 복잡한 데이터를 일렬로 세우기 때문에 직렬화
  • 거꾸로 복구시키는 과정도 포함
    • 시리얼라이제이션: 오브젝트 그래프에서 바이트 스트림으로
    • 디시리얼라이제이션: 바이트스트림에서 오브젝트 그래프로
  • 장점
    • 현재 프로그램의 상태를 저장하고 필요한 때 복원(게임의 저장)
    • 현재 객체의 정보를 클립보드에 복사해서 다른 프로그램에 전송
    • 네트워크를 통해 현재 프로그램의 상태를 다른 컴퓨터에 복원(멀티플레이어 게임)
    • 데이터 압축, 암호화 통해 데이터를 효율적이고 안전하게 보관

직렬화 구현시 고려할 점

  • 직접 구현할경우 다양한 상황 고려 필요
    • 데이터 레이아웃: 오브젝트가 소유한 다양한 데이터를 변환할것인가?
    • 이식성: 서로 다른 시스템에 전송해도 이식될 수 있는가? (예시: 운영체제마다 다른 빅엔디안, 리틀엔디안 차이)
    • 버전 관리: 새로운 기능이 추가될 때 이를 어떻게 확장하고 처리할것인가?
    • 성능: 네트웍 비용을 줄이기 위해 어떤 데이터 형식 사용?
    • 보안: 데이터 어떻게 안전하게 보호?
    • 에러 처리: 전송과정에서 문제 발생시 이를 어떻게 인식하고 처리할것인가?
  • 이런 상황 모두 감안해 직렬화 모델 만드는것 쉽지않다.

언리얼 엔진의 직렬화 시스템

  • UE엔진은 이런 상황을 모두 고려한 직렬화 시스템 자체 제공
  • FArchive 클래스와 연산자 제공
    • 아카이브 클래스 - FArchive
    • shift(<<) operator
  • 다양한 아카이브 클래스 제공
    • 메모리 아카이브(FMemoryReader, FMemoryWriter)
    • 파일 아카이브(FArchiveFileReaderGeneric, FArchiveFileWriterGeneric)
    • 기타 언리얼 오브젝트와 관련된 아카이브 클래스(FArchiveUObject)
  • Json 직렬화 기능: 별도 라이브러리 통해 제공
  • 장점
    • 텍스트지만 데이터 크기 가벼움
    • 읽기 편해서 데이터 보고 이해 가능
    • 사실상 웹 통신의 표준급으로 널리 사용됨
  • 단점
    • 지원하는 타입 몇개안됨
    • 텍스트 형식으로만 사용 가능
  • 언리얼 엔진의 Json -> JsonUtilities 라이브러리 활용

언리얼 스마트 포인터 라이브러리 개요

  • 일반 C++ 오브젝트의 포인터 문제 해결해주는 언리얼 엔진의 라이브러리
  • TUniquePtr(유니크 포인터)
    • 지정한 곳에서만 메모리 관리하는 포인터
    • 특정 오브젝트에게 명확하게 포인터 해지 권한 주고싶은 경우
    • delete 구문 없이 함수 실행 후 자동으로 소멸시키고 싶을 때
  • TSharedPtr(공유 포인터)
    • 더 이상 사용되지 않으면 자동으로 메모리 해지하는 포인터
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용하는 경우
    • 다른 함수로부터 할당된 오브젝트를 Out으로 받는 경우
    • Null 일 수 있음
  • TSharedRef(공유 레퍼런스)
    • 공유포인터와 동일하지만 유효한 객체 항상 보장받는 레퍼런스
    • 여러 로직에서 할당된 오브젝트가 공유해서 사용하는 경우
    • Not Null 보장받으며 오브젝트 편리하게 사용하고 싶은 경우

댓글()

언리얼 엔진의 메모리 관리

Unreal 이론|2024. 9. 5. 22:06

[이득우의 언리얼 프로그래밍 수업의 정리]

 

C++은 저수준으로 메모리 주소에 직접 접근하는 포인터를 사용해 오브젝트를 관리.

-> 프로그래머가 직접 할당과 해지 짝맞추기를 해줘야함.

예시)

메모리 누수(Leak)(힙에 메모리 그대로 남아있음)

허상(Dangling) 포인터(다른곳에서 이미 해제해 무효화된 오브젝트의 주소 가리키는 포인터)

와일드 포인터(값이 초기화되지 않아 엉뚱한 주소 가리키는 포인터)

-> C++ 이후의 언어는 가비지컬렉션을 채택함

 

가비지 컬렉션?

-> 프로ㅓ그램에서 더 이상 사용하지 않는 오브젝트 자동으로 감지해 메모리 회수하는 시스템

-> 동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소 사용, 사용되지 않는 메모리 추적

 

마크 스윕 방식의 가비지 컬렉션

-> 저장소에서 최초 검색 시작하는 루트 오브젝트 표시

-> 루트 오브젝트가 참조하는 객체를 찾아 마크

-> 마크된 객체로부터 다시 참조하는 객체 찾아 마크하고 이를 반복

-> 저장소는 그럼 마크된 객체랑 안된 객체 두 그룹으로 나뉨

-> 가비지 컬렉터가 마크되지 않은 객체-가비지들의 메모리 회수(Sweap)

 

언리얼은 마크-스윕 방식의 가비지 컬렉션 시스템을 자체적으로 구축

지정된 주기마다 몰아서 없애도록 설정되있음 (GCCycle, 기본값 60초)

성능향상을 위해 병렬처리, 클러스터링과 같은 기능 탑재

 

가비지 컬렉션을 위한 객체 저장소

관리되는 모든 언리얼 오브젝트의 정보 저장하는 전역변수: GUObjectArray

-> 각 요소에 플래그가 설정되있음.

가비지 컬렉터가 참고하는 주요 플래그

-> Garbage 플래그: 다른 언리얼 오브젝트로부터 참조가 없어 회수 예정인 오브젝트. 수동으로 설정하는게 아니고 시스템에서 알아서 설정함.

-> Rootset 플래그: 참조가 없어도 회수하지 않는 특별한 오브젝

가비지 컬렉터는 이런 플래그를 확인해 빠르게 파악, 메모리에서 제거

한번 생성된 언리얼 오브젝트는 바로 삭제가 불가능함.

-> 레퍼런스 정보를 없애 가비지 컬렉터가 자동으로 메모리 제거하도록 설정 (delete 같은 키워드로 없애는게 아님)

-> AddToRoot 함수 호출해 메모리 회수로부터 보호, RemoveFromRoot 함수 호출해 루트셋 플래그 제거

-> 컨텐츠 만들때 권장하는 방식은 아님

 

[장점]

1. 메모리 누수 해결

C++ 오브젝트는 직접 신경써라. (스마트 포인터 라이브러리 쓰던가)

2. 댕글링 포인터 문제 해결

언리얼 오브젝트는 이를 탐지하기위한 함수 제공(IsValid() 같은거)

C++ 오브젝트는 직접 신경써라

3. 와일드 포인터 문제 해결 (스마트 포인터 라이브러리 쓰던가)

UPROPERTY 속성 지정하면 자동으로 nullptr로 초기화 해준다.

C++ 오브젝트 포인터는 직접 초기화 (스마트 포인터 라이브러리 쓰던가)

 

회수되지 않는 언리얼 오브젝트?

-> 언리얼 엔진 방식으로 참조 설정한 언리얼 오브젝트

UPROPERTY로 참조된 언리얼 오브젝트

-> FGCObject 클래스 상속받아 AddReferencedObject 함수 잘 쓰진 않지만 이걸로도 가능

-> Rootset으로 지정된 언리얼 오브젝트. (이것도 많이 안씀)

 

오브젝트 선언의 기본 원칙

-> 오브젝트 포인터는 가급적 UPROPERTY로 설정하고, 관리는 가비지 컬렉터가!

 

[언리얼 오브젝트 관리 원칙]

생성된 언리얼 오브젝트 유지하기 위해 레퍼런스 참조 방법 설계

-> 언리얼 오브젝트 내의 언리얼 오브젝트 : UPROPERTY 사용

-> 일반 C++ 오브젝트 내의 언리얼 오브젝트 : FGCObject 상속 후 구현

생성된 언리얼 오브젝트 강제로 지우지 마라.

-> 참조를 끊는다는 생각으로 설계.

-> 가비지 컬렉터에게 나머지는 맡겨라. ForceGarbageCollection 함수로 회수 재촉은 가능

-> 콘텐츠 제작에서 Destroy 함수 사용할 순 있지만, 내부 동작은 동일(가비지 컬렉터에게 짬때린다.)

 

 

댓글()

4. 서버 구축을 위한 삽질

개발 일지|2024. 9. 2. 23:01

서버구축.. 쉽지않다.

 

원래는 호스트를 맡은 클라이언트에게 리슨 서버를 짬때려서 데디케이트 서버를 클라우드나 내 로컬에 띄워놓는 비용을 줄이고 싶었다.

그래서 방법을 알아봤는데, 호스트 유저의 고정 IP 사용, 포트포워딩, NAT우회 같은 이슈가 발생했다.

내가 기술적으로 추후 사용할 유저가 이런 문제에 대해 아예 신경쓰지 않고 플레이 하도록 코드를 작성해놓고 배포하는건 쉽지 않을것 같았다.

 

그래서 배포계획도 없고 만약 배포한다 해도 몇명이나 쓸까 싶은 내 프로젝트에는 비용을 아끼기위한 꼼수보다 이미 많은 게임들이 사용하고 있는 방식으로 연습하는 셈 치고 구현하는게 맞는것 같아서 데디케이트 서버를 구축해서 개발하기로 결정했다.

 

하지만 데디케이트 서버를 만들기 위해 서버용 UE5 에디터 환경을 만드는것도 쉽지않았다.

 

토목공사를 삽질로만 만들어준 고마운 분

https://velog.io/@singery00/UE5-Dedicate-%EC%84%9C%EB%B2%84-%EA%B5%AC%EC%B6%95

 

[UE5] 🌐 Dedicated 서버 구축

🌐 Dedicated 서버를 구축하고 멀티 플레이 게임을 만들어보자.

velog.io

 

 

 

우선 깃헙의 에픽게임즈 -> 언리얼엔진으로 들어가 계정을 연결한다.

https://www.unrealengine.com/ko/ue-on-github

공식 페이지에 잘 정리되어있다.

 

그리고 release 페이지를 확인하는데, 버전이 정확하게 일치해야한다.

나같은경우 5.3.2를 써서 개발하는 중인데, 5.3도 되겠지? 하는 안일한 마인드로 처음부터 다시 했다.

굉장히 오래 걸리는 과정이기 때문에 버전 잘못되서 기존 프로젝트 못열면 기분이 상당히 안좋다..

왼쪽 상단 릴리즈 대신 우측의 릴리즈 페이지로 들어가서 정확한 버전을 체크하자.

 

zip파일을 다운받고 압축 해제 -> setup.bat 파일 실행 (좀 오래걸림. 씻고 저녁 만들어서 먹고 설거지 하고 오면 대충 다 된다.) -> generateprojectfiles.bat 실행 (이건 금방함) 까지 하면 UE5.sln이라고 프로젝트 파일이 생긴다. 이거 열자.

솔루션 Development Editor, Win64 맞는지 확인

 

UE5를 빌드 해보자. (진짜진짜 오래걸림)

나는 거의 8시간 걸렸다.

 

빌드가 다되면 UE5를 시작프로젝트로 설정하고 ctrl+f5로 실행해준다.

 

셰이더 컴파일도 오래걸린다. 체감상 한 30분?

에디터가 잘 열리면 이제부터 이걸로 개발해야한다.

 

아 에픽게임즈 런처에서 다운받은 언리얼 엔진으로는 데디케이트 서버 구축이 안된다고 한다.

깃헙에서 다운받은 엔진으로만 한다고 한다.

에픽게임즈 런처에서 다운받은 언리얼 엔진과 깃허브에서 다운받은 언리얼 엔진은 버전 숫자는 같아도 내부 API같은게 조금씩 다를수 있어 서로 호환이 안될수도 있다고 한다.

어이가 없네...

그래서 런처에서 다운받은 엔진으로 개발했던 프로젝트가 깃헙에서 다운받은 엔진으로는 안열렸던것.

 

요새 날이 좀 시원해졌는데 머리가 뜨거워진다.

 

대충 해결한것 같다.

에픽게임즈 런처에서 다운받았던 에디터로 개발한 프로젝트는 살린것 같다.

먼저 지형을 그려주는 브러시를 플러그인으로 다운받아 썻었는데 그 플러그인을 지우고 그 플러그인으로 생성했던 더미맵도 지웠다.

그리고 프로젝트 폴더의 Binary 폴더와 immediate 폴더를 지웠다.

 

그다음 일반적인 데디케이트 서버 구축의 과정을 진행했다.

1. source 폴더의 Target.cs 파일을 복붙해 프로젝트명Server.Target.cs로 고쳐주고 vs를 킨다.

2. 프로젝트에 복붙한 파일 추가해주고 내부 프로젝트 이름으로 된 클래스 이름 같은것들을 프로젝트ServerTarget으로 수정해준다.

요런느낌

3. 저장해주고 uproject 우클릭해서 언리얼 엔진 버전을 깃헙에서 받은 엔진으로 설정해준다.

4. uprojecct 파일 우클릭해서 generate visual studio project files 실행해준다.

5. Development Editor, Win64 설정으로 프로젝트 빌드 한번 해준다.

6. Development Server, Win64 설정으로 프로젝트 빌드 한번 해준다.

 

이렇게 하니 정상적으로 열린다.

이제 멀티플레이를 위해 세션 생성, 세션 참가 같은걸 만들어 보자.

'개발 일지' 카테고리의 다른 글

6. UMG 디자인  (0) 2024.11.09
5. 멀티플레이 개발  (0) 2024.11.08
3. 플레이어 랜덤 스폰 구현  (0) 2024.08.31
2. 움직이는 플레이어 캐릭터 만들기  (0) 2024.08.26
1. 메인메뉴 구성  (0) 2024.08.26

댓글()

언리얼 네트워킹 개요

Unreal 이론|2024. 9. 2. 01:38

멀티플레이 게임에 필요한 서버 환경 구축에 대해 아는게 없기 때문에 언리얼 공식문서를 읽어보면서 어떻게 구현하면 될지 공부해보자.

 

1. 멀티플레이어 기능을 절대로 추가하지 않는다는 확신이 있는게 아닌 이상, 처음부터 멀티플레이어용으로 게임을 개발해라

  • 프로젝트에 멀티플레이어 기능이 필요하게 될 수 있다면, 프로젝트 시작부터 멀티플레이어를 염두에 두고 모든 플레이를 빌드해라.
  • 생각은 했지만, 크게 중요하게 생각하지 않았던 부분을 시작부터 지적당한 기분이다. 당연하게도 싱글 환경에서 구현한 기능들을 멀티플레이어로 확장하려면 훨씬 많은 시간과 비용이 발생한다. 아직 많은 기능을 구현한 상태가 아니므로 다행이라 생각하고 지금이라도 고려하면서 개발해보자.

2. 클라이언트-서버 모델

  • 네트워크 멀티플레이어 게임의 경우 언리얼 엔진은 클라이언트-서버 모델을 사용한다.
  • 네트워크에 존재하는 하나의 컴퓨터가 서버가 되어 멀티플레이어 게임 세션을 호스팅하고, 서버에 접속한 다른 모든 플레이어의 컴퓨터는 클라이언트가 된다.
  • 서버는 각 클라이언트와 게임 스테이트 정보를 공유하고 서로 통신하는 수단을 제공한다.
  • 서버는 하나의 진정한 Authority 게임 스테이트를 보유한다.
  • 클라이언트는 각각 서버가 소유한 Pawn을 원격 제어하며, 게임 내 액션을 수행하도록 Pawn에 프로시저 호출을 전송한다.
  • 서버는 클라이언트 모니터에 직접 영상을 스트리밍 하지 않으며, 대신 게임 스테이트에 대한 정보를 각 클라이언트에 Replicate하여 존재하는 액터와 액터의 행동방식, 보유해야하는 다양한 변수값을 알려준다.
  • 클라이언트는 이 정보를 사용해 서버에서 플레이되는 게임을 매우 비슷하게 시뮬레이션 한다.

3. 클라이언트-서버 모델 예시

로컬 게임플레이 네트워크 게임플레이
플레이어 1이 입력을 눌러 무기를 발사한다.
  • 플레이어 1의 폰이 여기에 반응해 현재 무기를 발사
  • 플레이어 1의 무기가 발사체를 스폰하고 그에 따른 사운드와  비주얼 이펙트를 재생한다.
플레이어 1이 로컬 머신에서 입력을 눌러 무기를 발사한다.
  • 플레이어 1의 로컬 폰이 대응되는 서버의 폰에 무기 발사 명령 전달
  • 서버에 있는 플레이어 1의 무기가 발사체 스폰
  • 서버가 연결된 각 클라이언트에 플레이어 1의 발사체 사본을 생성하라고 지시
  • 서버에 있는 플레이어 1의 무기가 각 클라이언트에 무기 발사와 관련된 사운드외 비주얼 이펙트를 재생하라고 지시
플레이어 1의 발사체가 무기에서 앞으로 이동한다 서버에 있는 플레이어 1의 발사체가 무기에서 앞으로 이동한다
  • 서버가 각 클라이언트에 플레이어 1의 발사체가 이동하면 그 무브먼트를 Replicate 하라고 지시하고, 그에 따라 각 클라이언트 버전의 플레이어 1 발사체도 이동한다
플레이어 1의 발사체가 플레이어 2의 폰과 충돌한다
  • 이 콜리전이 플레이어 1의 발사체를 소멸하는 함수를 트리거하여 플레이어 2의 폰에 데미지를 주고 그에 따른 사운드와 비주얼 이펙트를 재생한다.
  • 플레이어 2가 받은 데미지에 대한 반응으로 화면 이펙트 재생한다.
서버에 있는 플레이어 1의 발사체가 플레이어 2의 폰과 충돌한다.
  • 이 콜리전이 서버에 있는 플레이어 1의 발사체를 소멸하는 함수를 트리거한다.
  • 서버가 자동으로 각 클라이언트에 플레이어 1의 발사체 사본을 소멸하라고 지시
  • 이 콜리전이 모든 클라이언트에 콜리전에 따른 사운드와 비주얼 이펙트 재생하라고 지시하는 함수 트리거
  • 서버에 있는 플레이어 2의 폰이 발사체 콜리전으로부터 데미지를 받는다
  • 서버에 있는 플레이어 2의 폰이 플레이어 2의 클라이언트에 받은 데미지에 대한 반응으로 화면 이펙트 재생하라고 지시

플레이어 1의 폰이 플레이어 2의 폰에 무기를 발사하여 데미지를 입히는 단순한 과정이 독립형 게임에서는 컴퓨터의 같은 월드에서 벌어지지만, 네트워크 게임에서는 상당히 복잡해보이는 절차가 발생한다.

 

좀 정리해보자.

  • 서버에 존재하는 월드, 각 플레이어에게 존재하는 월드가 따로 있고 상호작용은 여러개의 서로 다른 월드에서 일어난다.
  • 실제 게임은 서버에서 플레이되지만, 서버는 클라이언트의 월드에서 똑같은 이벤트가 발생하도록 보이기 위해 클라이언트에 선별적으로 정보를 전송하여 서버 월드와 동기화 한다.
  • 이 동기화를 위해 Replicate - 이벤트 복제를 서버가 실행하고, 어떤 정보를 replicate할지 선별하여 유저에게 일관된 경험을 제공하는 동시에 replicate하는 정보량을 최소화하여 가능한 네트워크 대역폭을 적게 사용해야한다.

학교에서 진행했던 모든 프로젝트에서 서버는 클라이언트와 통신해서 데이터를 뿌려주는 느낌이 강했었는데, 게임에서의 서버는 확실히 역할이 확대된? 느낌이다.

 

4. 네트워크 모드 및 서버타입

네트워크 모드 설명
Standalone(독립형) 게임이 원격 클라이언트 접속 허용하지 않는 서버로 돌아감
서버측과 클라이언트측 모두 로컬플레이어에 맞게 실행됨.
Client(클라이언트) 게임이 네트워크 멀티플레이어 세션으로 서버에 접속된 클라이언트로 실행된다.
어떠한 서버측 로직도 실행하지 않는다.
Listen Server(리슨 서버) 게임이 네트워크 멀티플레이어 세션을 호스팅하는 서버로 실행된다.
원격 클라이언트의 접속을 허용.
서버에 바로 로컬 플레이어가 있다.
가벼운 협동 및 경쟁 멀티플레이어에 자주 사용된다.
Dedicated Server(데디케이티드 서버) 게임이 네트워크 멀티플레이어 세션을 호스팅하는 서버로 실행됨.
원격 클라이언트 접속을 허용하지만, 로컬 플레이어는 없다.
더 효율적인 운영을 위해 그래픽, 사운드, 입력 및 기타 플레이어 중심 기능 제외
높은 지속성이나 보안, 대규모 멀티플레이어가 필요한 게임에 자주 사용됨.

 

우선 Standalone, Client는 고려하지 않고 Listen Server와 Dedicated Server 중에 하나를 선택해야 할 것 같다.

 

Listen Server

  • 게임 사본이 있는 어떤 유저라도 리슨 서버를 시작하고 같은 컴퓨터에서 플레이 할 수 있으므로 사용자가 자발적으로 쉽게 구성할 수 있다.
  • 서버를 시작하거나 참여할 서버를 검색하기 위한 게임 내 UI 제공 필요
  • 리슨 서버를 호스팅하는 플레이어가 서버에서 직접 플레이하기 때문에 네트워크 접속을 통해 플레이하는 다른 유저보다 유리하다. 서버에 직접 있는게 응답성이 훨씬 좋고.. 보안쪽으로도 서버측 플레이어가 마음먹으면 훨씬 취약해지니 핵 문제에도 취약해진다.
  • 네트워크 부하가 높거나(서버측 플레이어에게 높은 부하가 걸린다) 경쟁이 치열한 게임엔 부적합.
  • 소규모 플레이어의 협동게임, 경쟁 멀티플레이어 게임엔 매우 편하다.

 

Dedicated Server

  • 더 비싸고 환경설정이 어렵다.
  • 게임에 참여하는 모든 플레이어가 개별 컴퓨터를 사용해 자체적으로 네트워크에 접속해야함
  • 서버에 참여하는 모든 플레이어가 같은 타입의 접속으로 게임을 경험하므로 공정성 굿
  • 이 서버 유형은 그래픽을 렌더링하지도 않고, 로컬 플레이어와 관련된 로직을 수행하지도 않는다 -> 게임 플레이 이벤트나 네트워크 퍼포먼스가 더 효율적이다.

 

5. 액터 레플리케이션

 

Replication - 레플리케이션이란?

  • 서버가 네트워크 세션에 있는 클라이언트들에 게임의 데이터를 보내 클라이언트와 서버의 상태를 동기화하는 프로세스.
  • 서버에 실제 게임이 돌아가고 있고, 연결된 클라이언트들이 서버의 상태(그래픽이나 오디오 등)를 replicate 해서 게임에 참여중인 다른 클라이언트들과 통신한다.
  • 대부분 액터는 기본적으로 레플리케이션x, 로컬에서 함수 수행.
  • C++ 액터 클래스에서 bRplicates 변수 설정하거나, 액터 BP의 Replicates 세팅 T로 설정해 특정 클래스 액터에 대한 레플리케이션 활성화.

[가장 많이 사용하는 레플리케이션 기능]

레플리케이션 기능 설명
Creation and Destruction (생성 및 소멸)
  • 서버에서 Authority 있는 액터가 스폰되면 접속된 모든 클라이언트에 자동으로 원격 프록시 생성
  • 이후 정보가 원격 프록시에 레플리케이트 된다.
  • 해당 액터를 소멸시키면 접속된 모든 클라이언트에 있는 원격 프록시 자동 소멸
Movement Replication
  • Authority 있는 액터에 대해 Replicate Movement가 활성화 되어 있거나 C++에서 bReplicateMovement가 true로 설정되어 있으면, 자동으로 Location, Rotation, Velocity가 replicate 된다.
Properties Replication
  • Replicate 대상으로 지정된 속성은 값이 변경될 때마다
    Authority 있는 액터에서 원격 프록시로 자동으로 복제된다.
Component Replication
  • 액터 컴포넌트가 해당 컴포넌트를 소유한 액터의 일부로 Replicate 된다.
  • 이것으로 지정한 컴포넌트 내 모든 변수가 Replicate 된다.
  • 컴포넌트 내 모든 RPC가 액터 클래스에서 호출된 RPC와 일관되게 행동함.
Subobjects Replication
  • 모든 UObject기반 클래스는 액터에 붙어 서브 오브젝트로써 replicated 될 수 있다.
Remote Procedure Calls (원격 프로시저 호출)
  • RPC -> 네트워크 게임에서 특정 컴퓨터로 전송되는 특수한 함수
  • RPC를 처음 호출한 컴퓨터가 어떤 컴퓨터든 간에 지정된 컴퓨터에서만 RPC가 실행된다.
  • 서버(서버에서만 실행)나 클라이언트(액터 소유 클라이언트에서만 실행) 또는 NetMulticast(서버를 포함, 세션에 접속된 모든 컴퓨터에서 실행)로 지정할 수 있다.

 

생성 및 소멸, 무브먼트 같은 일반적인 레플리케이션에서는 자동으로 처리되지만, 레플리케이션을 활성화해도 다른 모든 게임플레이 기능 자체는 기본적으로 복제되지 않기 때문에, 수동으로 처리해야하는 항목도 있다.

  • replicate할 속성과 사용자 설정
  • replicate할 함수와 코드에서 수동으로 호출하는 함수
  • replicate할 컴포넌트와 서브 오브젝트 및 그에 관련된 속성과 함수

액터, 폰, 캐릭터의 몇가지 기능(Skeletal mesh component, static mesh component, Materials, animation bp, particle system component, Sound emitters, physics object)은 복제되지 않는다.

 

이러한 기능들은 모든 클라이언트에서 개별적으로 실행되지만, 시작적 요소를 구동하는 변수를 복제하면 모든 클라이언트가 동일한 정보를 가지고 유사하게 시뮬레이션이 가능하다.

 

6. Unreal이 제공하는 Replication System

언리얼에서는 네트워크 연결을 통해 이러한 데이터를 복제하는 세가지 시스템을 제공한다.

 

1. Generic Replication System (일반 레플리케이션 시스템)

  • 현재 UE에서 기본적으로 사용하는 복제 시스템.
  • 액터, 프로퍼티, RPC를 복제하는 기능을 지원하며, 세가지 주요 특성이 있다
  • Dormancy: 네트워크 replicate시 액터가 복제 대상 연결 리스트에 추가될지 여부를 제어
  • Priority: 네트워크 대역폭이 제한된 상태에서, 액터가 각 프레임마다 얼마나 중요한지 제어하여 replicate 우선순위를 결정하는 기능
  • Relevancy: 특정 연결에 대해 액터가 관련성이 있는지 제어하는 기능. 네트워크에서 복제할 액터 선택하는데 사용되며 관련성 없다면 복제되지 않는다.

2. Replication Graph

  • 많은 수의 복제된 액터를 효율적으로 처리하도록 설계된 네트워크 복제 시스템

3. Iris Replication System

  • UE 네트워킹 시스템에 새로 추가된 기능.
  • 기존의 Generic Replication System과 함께 작동하며 일부 기능 대체하고 개선함.

7. 네트워킹 팁

  • RPC와 복제된 BP 함수는 가능한 적게 사용. RepNotify 프로퍼티를 대신 쓸수있다면 쓰는것 권장
  • 멀티캐스트 함수는 트래픽이 증가하니 신중하게 사용
  • 서버 전용 로직이 꼭 서버 RPC에 포함되진 않아도 괜찮다. 복제되지 않은 함수가 서버에서만 실행되도록 할 수 있다면 그것으로 충분하다.
  • 신뢰할수 있는 RPC를 플레이어 입력에 바인딩할때 주의해라. RPC queue가 넘칠 수 있다. 플레이어 입력 기반 RPC를 호출할 땐 플레이어가 RPC 활성화 가능한 빈도를 제한하는 메커니즘 만들어라
  • 매우 자주 호출되는 RPC는 신뢰할 수 없는 RPC로 처리해라. 예시로, 액터의 tick 함수 내에서 자주 호출되는 RPC 같은것.
  • 함수를 최대한 재사용해라. 클라이언트와 서버가 병렬 실행되도록 보장하기 위한 RepNotify로 호출? RepNotify가 뭐지?
  • 액터의 네트워크 역할 확인. 서버와 클라이언트 모두에서 실행되는 함수에서 실행을 필터링할때 좋다.
  • IsLocallyControlled 사용해 폰이 로컬에서 제어되는지 확인. 폰이 소유 클라이언트와 관련 있는지에 따라 실행 필터링할때 좋다.
  • 네트워크 게임플레이에서 가장 중요한 최적화 중 하나인 네트워크 휴면상태를 활용해라.

8. 부록 - RepNotify가 무엇인가..

 

RepNotify는 Replication 시스템의 하나.

특정 프로퍼티가 네트워크를 통해 복제될 때, 그 속성값이 변경되면 자동으로 호출되는 알림 함수를 지정하는 기능.

이를 통해 클라이언트가 속성 값의 변경에 따라 추가적인 로직 실행.

  • 자동으로 업데이트 처리
  • 프로퍼티 값의 변경에 따른 로직을 프로퍼티 자체와 분리해서 코드 관리
  • 필요한 경우에만 추가 로직을 실행함으로써 네트워크 성능 최적화

UI 업데이트, 애니메이션 트리거, 게임플레이 로직, 이벤트 발생 등이 프로퍼티 변경시 일어난다면 RepNotify를 써서 로직을 작동시키면 아주 맛있다고 한다.

 

이렇게만 보면 여기저기 난사해서 사용해도 될 것 같지만, 주의사항이 있다.

 

1. 알림 함수 내에서 속성값 변경 금지

  • 복제된 속성 값의 변경을 반영하는 용도로만 사용.
  • 알림 함수 내에서 속성값을 다시 변경하면 무한루프 발생

2. 네트워크 성능 최적화

  • 속성이 변경될 때 마다 알림 함수 호출해서, 빈번한 속성 변경이 있으면 네트워크 성능에 영향.
  • 속성 변경 빈도를 최소화하거나, 중요한 변경 사항에서만 사용

3. 서버 권한 확인

  • 속성값 변경 로직은 서버에서만 실행되어야 한다.
  • 클라이언트에서 실행되면 네트워크 동기화 문제 발생

4. 알림 함수 경량화

  • 가능한 한 가벼운 로직으로만 구성해라
  • 복잡한 로직이나 무거운 연산 수행하면 성능 저하 발생

5. GetLifetimeReplicatedProps에 속성 추가

  • 복제하려는 모든 속성은 반드시 위의 함수 내에서 복제 대상으로 지정해야함.
  • 안쓴다? -> 속성값 클라이언트로 복제되지 않는다.

6. 속성 초기화

  • 속성 초기값 설정할 땐 생성자에서 초기화 하거나 BeginPlay 함수에서 해라.

우선 이정도로 정리해두고, 두고두고 씹어먹고 머리 박아가며 구현해보자.

 

참고: https://dev.epicgames.com/documentation/en-us/unreal-engine/networking-overview-for-unreal-engine?application_version=5.3

댓글()

3. 플레이어 랜덤 스폰 구현

개발 일지|2024. 8. 31. 22:14

게임맵으로 이동할 때 랜덤한 위치에 캐릭터가 스폰되도록 해야한다.

이유는 정해진 위치에만 스폰되는것보다 더 재밌을것 같아서.

 

스폰 로직을 구성하기 위해 RandomSpawnSys 이름으로 빈 클래스를 하나 생성했다.

이 클래스에 랜덤 위치를 계산하고, 지형 위에 스폰되도록 위치를 조정하는 로직을 구현할 예정이다.

이 방식을 위해 지형을 스캔하여 전체 맵에서 특정 지역에 랜덤으로 떨어지게 하려고 하였으나, 캐릭터 스폰 시스템이 꼬여 캐릭터 클래스가 null이 되는 문제 발생.

 

제대로 작동하지 않는 부분을 고치지 못해 스폰 위치를 지정해서 그중에서 랜덤으로 스폰되도록 코드를 수정했다.

 

만들어진 C++ 클래스를 블루프린트에 옮겨 에디터에서만 보이고 플레이어에게는 보이지 않는, 충돌 옵션을 제외한 스태틱 메쉬를 입힌다.

Add Component에서 간단하게 Sphere 하나 입혀준다.

Detail 패널에서 Actor hidden in game 옵션을 켜 에디터에서만 보이게 하고, collision 옵션에서 NoCollision 옵션을 선택해 플레이어가 보이지 않는 스폰지점에 막히지 않도록 해준다.

 

랜덤 위치 스폰을 위해 플레이어 캐릭터 클래스에 함수를 하나 추가했다.

void AFirstPersonCharacter::SpawnPlayerAtRandomLocation()
{
	TArray<AActor*> SpawnPoints;
	UGameplayStatics::GetAllActorsOfClass(GetWorld(), ASpawnPoints::StaticClass(), SpawnPoints);

	if (SpawnPoints.Num() == 0) {
		UE_LOG(LogTemp, Error, TEXT("No Spawn Points! Fix your Logic."));
		return;
	}

	int32 RandomPoint = FMath::RandRange(0, SpawnPoints.Num() - 1);
	ASpawnPoints* ChosenSpawnPoint = Cast<ASpawnPoints>(SpawnPoints[RandomPoint]);

	if (ChosenSpawnPoint) {
		SetActorLocation(ChosenSpawnPoint->GetActorLocation());
		SetActorRotation(ChosenSpawnPoint->GetActorRotation());
	}
	else {
		UE_LOG(LogTemp, Error, TEXT("Failed to cast Chosen Spawn Point!"));
	}
}

 

SpawnPoint를 AActor 타입의 포인터 배열로 선언해, 여러개의 스폰 포인트들을 담을 수 있도록 준비한다.

모든 액터를 포괄적으로 다룰수 있는 AActor 타입으로 먼저 선언해둔 다음, 가져온 객체들을 ASpawnPoints 객체로 캐스팅 한다.

현재 월드에 존재하는 SpawnPoint 클래스 객체들을 찾아 SpawnPoints 배열에 담는다.

RandomPoint 변수에 랜덤 인덱스를 저장한 다음, AActor 타입으로 가져온 SpawnPoint 액터들을  ASpawnPoints로 타입캐스팅 한다.

 

이후 선택된 ChosenSpawnPoint 객체에서 Location 정보와 Rotation 정보를 가져와 플레이어 캐릭터의 위치와 회전값을 결정해주면 랜덤 스폰 시스템이 완성된다.

 

처음에 계획했던 시스템처럼 되진 않았지만, 내가 처음 결정해둔 위치에 플레이어들이 랜덤하게 스폰되는 시스템은 구축했다.

 

처음 개발자가 결정해둔 위치 중 랜덤으로 스폰하는 방식은 Dead By Daylight처럼 오랜시간 플레이한 유저가 처음 스폰 위치를 모두 외우고 시작시점의 플레이를 유리하게 가져가는 모습이 나올까봐 전체 지형중에 완전하게 랜덤하게 떨어지는 방식을 구현하려 했었다.

하지만 로직의 에러를 해결하지 못하고 위와 같은 방식으로 변경했다.

완전하게 랜덤하게 떨어진다면 같은 방 안에 여러명의 유저가 떨어지는 등의 대참사가 발생할 수 있었다고 좋게 생각하기로 했다..

 

'개발 일지' 카테고리의 다른 글

5. 멀티플레이 개발  (0) 2024.11.08
4. 서버 구축을 위한 삽질  (0) 2024.09.02
2. 움직이는 플레이어 캐릭터 만들기  (0) 2024.08.26
1. 메인메뉴 구성  (0) 2024.08.26
시작  (0) 2024.08.21

댓글()

2. 움직이는 플레이어 캐릭터 만들기

개발 일지|2024. 8. 26. 22:41

이제 레벨에서 캐릭터를 움직여보고 총을 쏘는 부분을 구현해보려고 한다.

 

먼저 Tools 에서 C++ 클래스를 하나 만드는데, 캐릭터로 선택했다.

 

Actor는 레벨에 배치하는 가장 기본적인 클래스. 

Pawn은 마우스나 키보드의 입력을 받아서 처리하는 클래스.

Character는 마우스나 키보드의 입력을 받아 플레이어가 움직이는 클래스.

 

변수 앞에 붙는 접두사에는

A(Actor 클래스), U(Unreal 클래스), F(구조체 이름), b(boolean 타입) 등이 있다.

 

라고 이해하고 우선 개발을 시작해보겠다.

 

 

캐릭터 클래스를 생성하면 처음에 뜨는 cpp 코드이다.

맨위는 생성자

2번째는 BeginPlay() 함수로 레벨이 시작될 때 처음 호출되는 함수이다.

3번째는 매 프레임 마다 호출되는 Tick() 함수

마지막 SetupPlayerComponent함수로 키보드나 마우스 입력을 바인딩하는 함수이다.

 

이렇게 생성해놓고, 우선 카메라를 세팅한다.

protected:

	UPROPERTY(EditAnywhere)
	class UCameraComponent* Camera;

 

FirstPersonCharacter.h 파일의 맨 밑에 protected로 Camera Unreal C++ 클래스를 생성한다.

U가 붙으면 일반 C++ 클래스가 아닌 언리얼에서 컴파일 단계에서 관리하는 언리얼 C++ 클래스라고 배운것 같다.

컴파일 단계에서 관리한다는 말이 맞는지 정확하지 않아 다시 알아봐야겠다.

 

그리고 FirstPersonCharacter.cpp 파일의 생성자에 다음과 같이 추가한다.

Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Player_Camera"));
Camera->SetupAttachment(RootComponent);
Camera->bUsePawnControlRotation = true;

 

Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Player_Camera"));

-> "UCameraComponent" 라는 카메라 컴포넌트 생성하는 역할.

-> "CreateDefaultSubobject"컴포넌트를 생성하고 초기화 할때 사용

 

Camera->SetupAttachment(RootComponent);

-> 카메라 컴포넌트를 액터의 기본 컴포넌트인 "RootComponent"에 할당. RootComponent가 이동할 때 카메라도 함께 따라가도록 설정

-> 카메라를 캐릭터 움직임에 맞춰 위치시킨다고 이해

 

Camera->bUsePawnControlRotation = true;

-> 카메라가 Pawn의 controller 회전을 따라가도록 설정. 이 설정으로 플레이어가 캐릭터를 움직일 때 카메라 방향이 함께 변경된다.

-> 카메라가 캐릭터의 회전을 따라가도록 설정한다고 이해

 

여기까지 한 후, 언리얼5 엔진을 킨다.

아까 만들어뒀던 캐릭터의 블루프린트를 열면 카메라가 생겨있는데, 위치를 조정하고 간단한 캐릭터 메쉬도 배치해준다.

 

그리고 Lobby Map의 World setting을 열어서 Gamemode Override를 내가 만들었던 Default Game Mode로 설정한다.

 

이러면 게임을 실행시켰을 때 캐릭터가 현재 위치에서 스폰되는걸 알 수 있다.

아직 캐릭터에 키보드나 마우스 입력을 바인딩하지 않아 움직이지 않는데, 이것부터 해결해서 캐릭터로 맵을 뛰어다녀보자.

 

기본값
내가 쓸 키보드&마우스에 맞춰서 이름도 바꾸고 값을 세팅해줬다

 

엔진의 input창을 보면 기본적으로 wasd, 마우스 시점 컨트롤, 스페이스바 점프까지 바인딩 되어있는걸 확인할 수 있다.

여기 적혀있는 이름대로 cpp 코드에서 구현하여 바인딩해주자.

 

근데 노란색 경고문이 있어 읽어보니 더이상 쓰이지 않는 기능? 인것 같다. 더이상 쓰이지 않으면 왜 넣어놓은거지? 일단 cpp로 구현해보자.

 

	// character movement function
	void MoveForward(float val);
	void MoveRight(float val);

	void Turn(float val);
	void LookUp(float val);

	void StartJump();
	void StopJump();

 

우선 키보드와 마우스를 받을 함수들을 Character 헤더파일에 선언해준다.

// Called to bind functionality to input
void AFirstPersonCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// Keyboard Binding
	PlayerInputComponent->BindAxis("MoveForward", this, &AFirstPersonCharacter::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &AFirstPersonCharacter::MoveRight);

	PlayerInputComponent->BindAxis("Turn", this, &AFirstPersonCharacter::Turn);
	PlayerInputComponent->BindAxis("LookUp", this, &AFirstPersonCharacter::LookUp);

	PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &AFirstPersonCharacter::StartJump);
	PlayerInputComponent->BindAction("Jump", IE_Released, this, &AFirstPersonCharacter::StopJump);

}

// Character, Camera Movement Function
void AFirstPersonCharacter::MoveForward(float val)
{
	if (Controller && val != 0.0f) {
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);
		
		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, val);
	}
}

void AFirstPersonCharacter::MoveRight(float val)
{
	if (Controller && val != 0.0f) {
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		AddMovementInput(Direction, val);
	}
}


void AFirstPersonCharacter::Turn(float val)
{
	AddControllerYawInput(val);
}

void AFirstPersonCharacter::LookUp(float val)
{
	AddControllerPitchInput(val);
}

void AFirstPersonCharacter::StartJump()
{
	bPressedJump = true;
}

void AFirstPersonCharacter::StopJump()
{
	bPressedJump = false;
}

 

MoveForward 함수에서는 w와 s의 입력에 따른 캐릭터의 움직임을 계산한다.

If 조건문에서 캐릭터가 플레이어에 의해 제어되고 있는지 확인 && 키보드 val 값이 실제로 입력이 들어왔는지 확인.

Rotation 변수에서 카메라나 플레이어의 컨트롤러가 현재 바라보는 방향값을 Controller->GetControlRotation() 구문으로 받는다.

YawRotation(0, Rotation.Yaw, 0)로 Yaw값을 사용하는 회전 값을 만든다.

FRotator는 회전을 나타내는 구조체(F 접두사가 구조체를 가리킨다)인데, Pitch, Yaw, Roll 축을 나타낸다.

더보기

[Pitch, Yaw, Roll에 관하여]

 

Pich는 상하, Yaw는 좌우, Roll은 좌우 기울기.

Pitch와 Roll을 0으로 설정하고, Yaw값만 사용하면 캐릭터가 위아래를 바라보더라도 이동할땐 Yaw값만 사용해 좌우로만 이동한다.

Yaw는 Z축 회전각 -> 도리도리

Pitch는 Y축 회전각 -> 끄덕끄덕

Roll은 X축 회전각 -> 갸우뚱갸우뚱

 

참고한 블로그분이 정말 자세하게 남겨주셔서 잘 이해되었다.

 

참고: https://wise-eun.tistory.com/entry/Unreal-Yaw-Pitch-Roll-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%BD%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0

FRotationMatrix 함수를 사용해 회전을 방향 벡터로 변환한다.

YawRotation 값을 회전 행렬로 변환해서, GetUnitAxis(EAxis::X) 를 사용해 회전행렬값에서 X축 방향을 얻어 전진한다.

캐릭터가 앞을 향해 이동할 방향 벡터를 계산한다고 이해하자.

 

AddMovementInput 함수를 사용해 캐릭터를 특정 방향으로 이동시킨다. Direction 변수는 앞에서 계산한 X축 방향, val은 이동할 때 입력 강도(아까 엔진에서 설정했던 값에 따라 1.0이면 앞으로, -1.0이면 뒤로 이동).

 

MoveRight 함수는 위와 비슷하고 X축 대신 Y축 벡터를 사용하는것만 다르다.

 

Turn과 LookUp 함수에서는 AddControllerYawInput 함수를 사용해 Value값만큼 카메라를 회전시킨다.

 

StartJump와 StopJump 함수에선 bPressedJump를 T나 F값으로 설정해 점프를 시작하거나 멈춘다.

 

여기까지 해주고, 컴파일 한 후 LobbyMap에서 실행시키면 wasd 이동, 마우스에 따른 카메라의 회전, 스페이스바 입력시 점프가 잘 동작한다.

 

다음에는 메인메뉴에서 게임 시작시 캐릭터 스폰 위치 조정, 카메라 시점에 맞게 팔 달아주기, 총 하나 쥐여줘서 쏴보기 구현해보자.

 

[참고 많이 한 블로그]

https://tech-interview.tistory.com/295

'개발 일지' 카테고리의 다른 글

5. 멀티플레이 개발  (0) 2024.11.08
4. 서버 구축을 위한 삽질  (0) 2024.09.02
3. 플레이어 랜덤 스폰 구현  (0) 2024.08.31
1. 메인메뉴 구성  (0) 2024.08.26
시작  (0) 2024.08.21

댓글()

1. 메인메뉴 구성

개발 일지|2024. 8. 26. 18:04

메인 메뉴 구성 과정을 남겨보자.

처음 언리얼 기본 템플릿 중 First Person으로 해보려 했는데,

내가 만들지도 않은 코드 쳐다보고 있어봤자 지금은 별 도움 안될것 같아 빈 프로젝트로 만들어서 처음부터 해보려고 한다.

 

빈 프로젝트를 만들어 UMG 에디터로 메인화면을 구성해보고, 게임시작 -> 세션 설정 UI 클릭 이벤트 -> 레벨 이동까지 만들어 보자.

 

Content Browser에서 Maps와 UI 폴더를 만들어 관련 파일들을 저장한다. 사람들 간의 국룰 저장 폴더 경로를 들어본적 있는데 잘 기억나지 않아서 일단 Content 폴더에다가 만들었다.

메인 메뉴 UI를 띄울 MainMenu Level을 생성
UI 폴더에서 우클릭 -> 맨밑의 User Interface -> Widget Blueprint 클릭

 

이름은 MainMenuUI로 만들어 주고, 더블클릭해 UMG 에디터를 연다.

Unreal5 엔진에 Canvas 패널이 자동으로 생성되어있지 않기 때문에 직접 추가

 

Canvas Panel은 딱봐도 백그라운드 배경인것 같다. 추가해준다.

 

타이틀을 적을 Text box를 canvas hierarchy에 추가해주고, Size To Content 클릭해서 크기 맞춰준다. Anchors 클릭해서 기준점은 일단 상단 가운데로 잡아주고, 타이틀도 적어줬다.

폰트나 색상도 입맛대로 바꿔주고, Alignment 옵션을 X, Y 둘다 0.5로 맞춰서 정렬해준다. 

 

다음은 버튼들을 만들 차례이다. Vertical Box 하나 가져와서 그 안에 버튼들을 넣고, 그 버튼에 텍스트 박스도 넣어준다. 당연하겠지만 하나 만들고 복붙했다. Spacer 넣어주고 Padding 조절해서 간격 예쁘게 맞춰준다.

그리고 Detail 상단의 Is Variable을 체크해야 이벤트를 추가해줄수 있다. 왜 없지 하고 헤매지 말고 꼭 체크해주자.

Detail 맨 하단에 보면 On Clicked 있으니까 체크해서 클릭하고, 레벨 이동 구현해주자.

같은방식으로 Quit 메뉴도 클릭 이벤트 만들어준다.

 

여기까지 했는데 슴슴한 메뉴가 맘에 안들어서 뭐 없나 찾아보다가 괜찮은걸 발견해 적용하려 한다.

 

버튼의 테두리 역할을 할 이미지를 하나 가져와서 삽입한다. 나는 아이패드로 직접 그렸다.

적절한 백그라운드 이미지가 있으면 넣어주고, 나는 없어서 그냥 Border 하나 넣어서 색깔만 바꿨다. (버튼 아님)

그리고 scale 0.93으로 적당히 조정해 테두리 안쪽으로 들어가게 만들어준다.

 

 

텍스트 박스도 하나 추가해서 크기와 위치, 기준점 맞춰주고 Content->bind 들어간다

 

 

Variables의 TEXT 추가 후 Return node에 연결, Transform Policy를 To Upper로 설정

 

 

그리고 Stroke와 Background를 같이 묶어 마우스 호버 애니메이션을 지정해주는데, 핵심은 백그라운드는 작아졌다 원상복구, Stroke는 커졌다가 원상복구이다.

 

 

그리고 애니메이션을 지정해주는데, 버튼에 Hoveranimation을 바인딩 해주고 button text는 Instance Editable과 Expose on Spawn 옵션을 체크해준다.

 

 

그리고 했던걸 시원하게 날리고 새로 간격 맞춰준다

 

여러 레벨 디자인을 보고 따라해봤는데, 예전에 받아놨던 무료 에셋 중에 하나를 일단 사용하기로 했다.

 

widget blueprint에 버튼을 생성하고, 다시 main menu widget blueprint를 생성해서 메인 메뉴를 구성했다.

 

그런데 Start btn에는 click 이벤트를 할당해서 잘 동작이 됐는데, 다른 버튼들은 클릭 이벤트가 작동하지 않아 어디가 문제인지, 어떻게 고치는지 해결이 안돼서 헛다리를 많이 짚었다.

모듈화를 풀고 main menu ui에 하나하나 다 만들면 쉽게 해결이 되지만, 맞는 방법은 아니라는 생각에 다른 방법이 없나 계속 찾아보았다.

 

결론은 이것도 최선은 아닌것 같지만 일단 해결하였다.

bind event 노드끼리 연결해줘서 최종적으로 Event construct에 연결되도록 해줬더니 잘 작동했다.

이게 최선은 아니겠지만 일단 다음 스텝으로 넘어가고싶다...ㅠ

 

메인 화면 구성을 마쳤고, 우선 Create Game과 Exit에 기능을 할당하여 사용 할 예정이다.

ui에 mouse hover 이벤트가 일어나면 좌우를 감싸는 대괄호가 나타나는 애니메이션을 입혔다.

좀 구리긴 하지만 일단 다음 프로그래밍을 위해 빨리 넘어가기로 했다.

 

참고한 유튜브는 아래와 같다.

https://www.youtube.com/watch?v=07H6INJ_3hI

 

https://www.youtube.com/watch?v=pTTHen7vhJ8

 

https://www.youtube.com/watch?v=3YnzCbrvfPI

 

'개발 일지' 카테고리의 다른 글

5. 멀티플레이 개발  (0) 2024.11.08
4. 서버 구축을 위한 삽질  (0) 2024.09.02
3. 플레이어 랜덤 스폰 구현  (0) 2024.08.31
2. 움직이는 플레이어 캐릭터 만들기  (0) 2024.08.26
시작  (0) 2024.08.21

댓글()

시작

개발 일지|2024. 8. 21. 16:29

1인칭 FPS 마피아 게임을 개발하는 과정을 기록하는 첫 페이지.

 

Unreal5 C++ 이론 공부를 하다가 이러다 어느세월에 게임 만들어 보겠나 싶어서 머리 박아보려 만든다.

 

Deceit에서 대부분의 영감을 받았고, 기타 FPS게임들의 방식을 채용한 마피아 게임이 재밌지 않을까? 생각했다.

 

개발하면서 구현하는 주요 목표는 아래와 같다.

 

1. 개발환경

Unreal5 C++을 사용해 주요 기능들을 개발하고, Unreal5의 UMG를 사용하여 UI를 디자인 한다.

 

2. 멀티플레이어 환경

P2P 네트워크를 사용한다. 게임 호스트가 서버 역할을 맡고 다른 플레이어들이 참여하는 방식.

위와 같은 방식으로 서버 유지 부담을 줄일 수 있을것으로 예상.

 

3. 랜덤 역할군 배정, 랜덤 루팅 시스템

게임 시작시 직업군을 랜덤으로 배정한다. 마피아 역할군과 시민 역할군 모두 기본 권총을 지급하고 총알은 게임 레벨에서 랜덤하게 생성되는 박스를 루팅하여 사용. 투표 시스템 대신 HP를 모두 줄이면 아웃되는 시스템.

마피아의 승리 조건은 기존 마피아류 게임과 동일, 시민의 승리 조건은 마피아를 직접 총으로 쏴 죽이면 승리.

역할군에 따라 특수 총기와 총알을 파밍하고 사용하는 구조. 역할군간 밸런스 조절을 위한 시스템은 추후 추가

(시민이 어떻게 마피아를 조사하여 죽일지, 마피아 장르의 전통적인 낮과밤 시스템을 어떻게 적용할지 등)

 

4. UI

UI 클릭시 동작할 함수를 C++로 작성하고, 블루프린트에 매핑해 화면에 출력되는 UI를 구성할 예정. 직접 해봐야 이대로 작성하는게 맞는지 알 수 있을것.

 

5. 그래픽

1차로 언리얼 마켓 플레이스 등에서 적절한 무료 라이센스 에셋을 채택해 사용할 예정. 필요시 3D Blender를 사용할지 고려 예정

 

큰 틀은 위와 같으며, 세부과정은 아래와 같다.

 

1. 게임 시작 화면에서 게임 세션 생성

2. 세션에 진입하여 플레이어의 캐릭터가 움직이는것 구현

3. 세션에 다른 유저들이 접속하여 유저간 인게임 보이스 및 상호작용 구현

4. 유저들의 준비 투표 및 게임시작 후 기본 맵으로 레벨 이동 구현

5. 기본 레벨에서 권총 지급 및 사격 구현

6. 유저간 사격 기능 및 HP 시스템, 랜덤 루팅 시스템 구현

7. 죽은 유저의 관전 및 채팅 기능 구현

8. 맵과 캐릭터, 총기류 등에 적절한 에셋 적용

9. 기타 세부사항 구현

'개발 일지' 카테고리의 다른 글

5. 멀티플레이 개발  (0) 2024.11.08
4. 서버 구축을 위한 삽질  (0) 2024.09.02
3. 플레이어 랜덤 스폰 구현  (0) 2024.08.31
2. 움직이는 플레이어 캐릭터 만들기  (0) 2024.08.26
1. 메인메뉴 구성  (0) 2024.08.26

댓글()