Unity 3D RPG 제작

Chaco22·2024년 12월 12일

Unity

목록 보기
1/2
post-thumbnail

언리얼
Actor의 사용 목적에 따라 만드는 과정에서 부모를 선택하여 부모에 할당되어 있는 기능 중에 사용할 기능을 넣거나 빼는 시스템

유니티
프리팹(블루프린트나 액터의 기능을 함)에 필요한 기능을 추가하는 시스템


지면 Terrain에 NavMesh Surface를 추가하고 Player에 Nav Mesh Agent를 추가한다.


Assets 파일에 Scripts 파일을 만들고 마우스 클릭이 일어난 경우 함수 MoveToCursor를 실행한다.
함수 MoveToCursor는 카메라 스크린에서 클릭이 일어난 곳을 반환하고 그곳이 유효할 경우 해당 장소를 플레이어의 목적지로 설정한다.

Core

Camera

카메라의 새로운 위치 지정 방법

  1. 카메라의 Transform 직접 변경
    Hierarchy에서 Main Camera 선택 후 Inspector -> Transform 에서 직접 카메라의 Postion과 Rocation을 조절하여 방향을 설정
  2. Scene에서 바라보고 있는 화면을 카메라의 화면으로 지정
    Scene 화면에서 카메라가 바라보면 좋을 방향으로 이동한 후 Hierarchy에서 Main Camera 선택 후 위의 메뉴바 GameObject -> Align With View

Follow Camera


Create Empty로 Fllow Cmera를 생성하고 Main Camera를 자손으로 넣는다.


Update 대신 LateUpdate를 사용하여 플레이어가 움직이기 전 카메라가 움직이는 오류를 사전에 방지한다.
Script FollowCamera를 생성하여 FollowCamera의 위치를 target의 위치로 지정한다.
FollowCamera의 타겟을 Player로 지정한다.

유니티에서 생성된 오브젝트의 Transform은 World 기반이지만 오브젝트에 부모-자식 관계가 있으면, 자식 오브젝트의 Transform은 부모의 Transform을 기준으로 Local 기반으로 변경된다.

IAction


IAction은 스크립트 간의 의존성을 줄이기 위해 ActionScheduler에서 Mover와 Fighter간의 역할 전환이 있을때 사용하는 인터페이스다.

IAction을 상속받아 사용하기 위해서는 각 스크립트에 동일한 이름의 함수가 존재해야 한다.

ActionScheduler


Mover와 Fighter에서 해당 컴포넌트가 실행되면 현재 실행되고 있는 액션을 중지하고 새로운 액션을 현재 액션으로 바꾼다.


(Mover)


(Fighter)

새로운 행동을 시작하는 함수에서 자신을 담아 ActionScheduler로 보낸다.

Animation

캐릭터 메쉬를 플레이어의 자손으로 넣고 NavMeshAgent에서 플레이어의 Obstacle Avoidance -> Radius를 0.3으로 플레이어의 캐릭터에 맞게 조절
Add Component -> Animator를 추가한다.

Animator

Locomotion


Create -> Animator Controller, Character를 생성하고 블렌더 트리에 Idle, Walk, Run 세개의 애니메이션을 추가
Automate Thresholds를 해제하고 Compute Thresholds를 Velocity Z로 설정하여 설정된 속도에 따라 애니메이션이 자연스럽게 전환되도록 만든다.

Player -> Animator에서 Controller를 생성한 Character로 Avatar를 Prefab에 맞는 아바타로 설정한다.


Mover.cs에서 애니메이터를 가져와 SetFloat으로 애니메이터의 변수 forwardSpeed를 전달한다.

Attack


State Attack을 만들고 Trigger attack이 발동되면 Locomotion에서 Attack으로 이동한다. Attack에서 Locomotion으로 이동은 Has Exit Time을 활성화하여 Attack이 끝나면 Locomotion으로 이동한다.

공격 애니메이션에 할당되어있는 Hit을 사용하여 애니메이션에서 공격이 들어가는 순간에 대미지가 전달되도록 만든다.

Death


상태 Death를 만들어 Any State에 연결하여 현재 어떤 상태이든 Trigger die가 실행되면 사망 애니메이션이 실행되도록 만든다.

Movement

Mover


함수 StartMoveAction을 제작, 공격 상태를 종료하고 MoveTo를 실행하는 함수.
플레이어가 이동 조건을 충족할 경우 이동을 멈추는 함수 Cancel 생성.

navMeshAgent로 월드 Velocity를 가져오고 Transform.InverseTransformDirection를 통해서 월드 좌표계에서의 방향 벡터를 객체의 로컬 좌표계로 변환한다.

Control

PlayerController


전투 부분과 이동 부분을 나누고 RayCastAll을 통해 마우스가 투영하는 부분에 CombatTarget을 가지고 있는 적이 탐지될 경우 공격을 실행.

Fighter의 CanAttack 함수를 이용하여 적이 공격 가능한 상태인지 확인하고 아닐 경우 다음 대상을 찾는 탐색 진행.

AIController




플레이어에게 Player 태그를 부여하고 해당 태그를 가진 프리팹이 chaseDistance의 거리 안에 들어올 경우 추적하여 공격한다.

플레이어를 탐지한 경우 마지막으로 플레이어가 탐지된 곳에서과 웨이포인트에 도달하면 잠시 탐색하는 시간을 재는 타이머를 생성.

Patrol Path


PatrolPath 파일을 만들고 파일 아래 Waypoint를 두 개 이상 생성한다.
파일에 PatrolPath 컴포넌트를 추가하고 반복문으로 자식을 탐색하여 기즈모와 라인을 입힌다.
다음 Waypoint와 인덱스를 찾는 함수를 생성.

Combat

Combat Target


마우스 클릭 부분에 적이 있는지 확인하는 CombatTarget을 생성하고 적에게 추가한다.
[RequireComponent(typeof(Health))]를 추가하여 컴포넌트 CombatTarget이 추가된 프리팹에는 자동적으로 Health도 추가되도록 설정.

Fighter

[SerializeField]

[SerializeField]를 통해서 에디터상에서도 변수 편집이 가능하도록 만든다.

공격 딜레이

공격 딜레이를 넣어주기 위해 float timeSinceLastAttack에 델타 타임을 받고 float timeSinceLastAttack이 timeBetweenAttacks보다 큰 경우 attack을 활성화 timeSinceLastAttack을 0으로 초기화하여 공격 이후 어느정도의 시간이 지났는지 측정한다.

캐릭터 회전

함수 LookAt을 사용하여 공격 대상을 바라보도록 설정.

ResetTrigger, SetTrigger

캐릭터 애니메이터의 이동 상태에서 stopAttack 트리거가 켜지는 경우 다음 공격 상태로 전환되고 나서 stopAttack 트리거가 소비되고 공격이 나가지 않는 상황이 벌어진다.
공격 상태로 들어가는 경우 stopAttack는 ResetTrigger 해주고 attack은 SetTrigger로 해당 상황을 방지한다.

Health


캐릭터의 체력을 관리하는 Health.cs
bool isDead를 만들어 Trigger die가 반복 실행되는 경우를 막는다.

Cinematic

유니티 Window -> Package Manager -> Cinemachine 설치

Vertual Camera


GameObject -> Cinemachine -> Virtual Camera
보여줄 장소를 비추도록 가상 카메라를 배치한다.

Dolly Camera with Track


GameObject -> Cinemachine -> Dolly Camera with Track
정해진 트랙을 이동하는 카메라 생성.
돌리 트랙에서 경로와 Waypoint를 지정한다.
돌리 카메라에서 Look At으로 촬영 대상을 지정하고 Position Units를 Normalized로 지정하여 첫 Waypoint를 0, 마지막 Waypoint를 1로 만듦.

TimeLine


유니티 Window -> Sequencing -> Timeline 으로 타임라인 창을 불러와서 Intro Sequence에 새로운 타임라인을 생성 Playable Director 컴포넌트를 추가된다.
카메라를 배치하고 Animation Track을 생성하여 돌리 카메라의 패스 포지션을 지정해준다.

CinematicTrigger.cs


자신과 다른 콜라이더 간의 충돌이 일어난 경우 해당 물체의 태그가 플레이어인지 확인하고 다시 실행되는 일이 없도록 bool로 방지한다.

CinematicControlRemover.cs


옵저버 패턴을 활용하여 PlayableDirector가 played 되면 함수 DisableControl을 통해 현재 입력된 스케줄을 취소하고 플레이어 컨트롤러를 비활성화하고 PlayableDirector가 stopped가 되면 함수 EnableControl을 통해 플레이어 컨트롤러를 다시 활성화한다.

액션 스케줄러의 캔슬 함수를 호출할때 플레이어가 이미 공격 상대를 지정한 경우라면 공격은 취소되지만 해당 거리까지 이동하는 현상이 있어 Fighter의 Cancel 함수에 Mover의 Cancel 함수를 첨부함.

Scene Management

Portal

Rigidbody와 Box Collider를 넣어 플레이어와 Overlap될 경우 Trigger 발동.
Portal 아래 SpawnPoint를 넣어 해당 포탈에서 스폰될 플레이어의 위치를 지정.


enum을 사용하여 같은 알파벳을 가지는 것으로 다른 맵에 있는 포탈과 연결.
유니티 File -> Build Settings를 통해 맵의 인덱스 번호를 지정, 로드.

코루틴을 사용하여 맵의 로드 이전에 FadeOut을 하고 로드가 완료된 뒤에 대응하는 포탈을 찾고 플레이어를 스폰 위치에 이동시킨 뒤 FadeIn 이후에 객체를 파괴한다.

Fader


검정색 CanvasGroup을 카메라 앞에 두고 Alpha로 투명도를 조정하여 화면이 어두워지는 효과를 준다.
화면이 전환되는 시간을 기준으로 Alpha값을 증가, 감소 시킨다.

SavingModule

SavingModule을 설치하여 Import 해주었다.
SavingModule에는 네가지 스크립트가 존재한다.

ISaveable : Save, Load 해야할 정보가 있는 스크립트에 상속하는 인터페이스로 정보를 저장하는 object CaptureState(); 와 불러오는 void RestoreState(object state); 두 가지 함수가 존재한다.

SaveableEntity : 저장해야할 정보가 있는 프리팹에 넣어 해당 프리팹에 저장해야할 정보가 있음을 알려준다.

SavingSystem : 시스템의 근간으로 Save(), Load() 등 저장에 필요한 많은 함수들이 존재한다.

SerializableVector3 : 유니티의 벡터 함수를 직렬화하여 저장해주는 클래스가 존재.

Core & Saving


빛, 플레이어, 카메라, 팔로우 카메라 등 씬이 바뀌어도 플레이에 있어서 꼭 필요하다고 생각되는 파일들을 한 곳에 묶어 Core라 지칭하고 프리팹화 하였다.

Persistent Object Spawner

게임이 종료되기까지는 파괴되지 않고 계속 유지되어야할 오브젝트들을 Persistent Object로 지칭하여 코어와 함께 계속 작동하도록 하였다.
Persistent Object는 Fader와 Saving이 존재.

Saving Wrapper


게임이 시작될때 함수 FadeOutImmediate()를 사용해 Alpha를 1로 변경하고 함수 LoadLastScene을 이용하여 마지막에 저장된 씬을 불러온 뒤 페이드인.
-> 게임을 종료 이후 다시 시작하면 저장된 씬 저장된 정보로 다시 시작한다.

키보드 입력을 통해 SavingSystem의 Save, Load를 실행한다.

Save & Load


Mover에서는 위치와 회전 정보를 구조체화하여 저장, 로드한다.
Health에서는 체력을 저장, 로드하고 로드시에 사망을 체크한다.

Portal.cs


플레이어가 포탈을 타고 씬을 이동하기 전 정보를 저장하고 씬을 불러오고 나서 로드하여 전 씬에서 가지고 있던 정보(체력 등)를 이동한 씬에서도 그대로 유지되도록 한다.

UpdatePlayer() 이후 다시 저장하여 체크포인트 역할을 해준다.

Weapon : Combat

Scriptable Object (Weapon.cs)

ScriptableObject는 데이터를 관리하고 공유하기 위해 사용되는 특별한 클래스로 에디터상에서 만들고 유니티에서 생성하여 관리한다.

무기별로 Prefab, Animator, 대미지와 거리를 다르게 설정하기 위해 Fighter에서 몇몇 변수들을 이동해왔다.
무기를 장착하는 손이 어디인지 확인하는 변수와 투사체가 존재하는 무기인 경우 투사체 프리팹을 선택해준다.

Spawn 함수가 실행되면 해당 위치에 프리팹을 소환, 애니메이션을 Override.


새로운 무기를 휙득한 경우 전의 무기를 삭제하는 함수와 무기에 투사체가 존재하는지의 여부, 그리고 투사체를 소환하는 함수가 존재.


Sword 프리팹과 검으로 공격하는 애니메이션 재생을 위해 Animator Override Controller를 생성, 선택.

Animator Override Controller


검을 찼을 때 Unarmed의 주먹질과는 다른 검을 휘두르는 애니메이션 재생을 위해 Animator Override Controller를 생성하고 새로운 공격 애니메이션을 Override.

Fighter.EquipWeapon, Hit

Editor


함수 EquipWeapon를 실행하면 현재 무기를 업데이트하고 weapon.Spawn()에 손의 정보를 주어 실행한다.

애니메이션에서 Hit이 실행된 경우 투사체를 가진 무기면 함수 LaunchProjectile를 실행하여 투사체 소환.

Unity


유니티 상에서 캐릭터 뼈대의 손에 해당하는 위치를 전달하고 기본 상태에서 대미지와 거리만 수정된 Unarmer를 전달.

WeaponPickup

Sword Pickup


Sword Pickup에 Collider와 WeaponPickup.cs를 추가.

WeaponPickup.cs


충돌한 물체가 플레이어일 경우 해당 무기를 함수 EquipWeapon에 전달하여 설정된 시간동안 비활성화한다.

Bow & Projectile

Bow


검과 같은 방식으로 Weapon과 Animator Override Controller, 장착 파일(메쉬)의 위치를 설정하여 생성.

Porjectile.cs


투사체가 적의 방향을 추적하는 투사체일 경우 Update에서 프레임마다 적을 바라보며 앞으로 나간다.
타겟이 지정된 경우 설정된 시간 뒤에 파괴한다.


타겟의 collider 크기에 따라 도착 지점을 설정.
충돌이 일어날 경우 이팩트 재생과 함께 먼저 메쉬를 파괴하고 타겟에게 대미지를 입힌다.

Effect


Effect -> Trail을 생성하여 설정.


적이 화살을 맞을 경우 생성될 이펙트를 Effect -> ParticleSystem을 만들어 조정해준다.

Fireball


파이어볼의 머리가 될 부분을 가져와 회전시켜 움직임을 준다.

Effect


애셋 스토어에서 SimpleFX를 다운받아 크기를 조정하여 파이어볼 머리의 뒤에 두어 불덩이가 날아가는 연출을 함.

Weapon Destroying, Saving & Spawning

무기를 이름으로 다뤄 파괴, 저장을 진해하고 WeaponPickup과 충돌하여 무기를 휙득하면 일정 시간 뒤에 무기를 다시 소환한다.

Weapon Destroying (Weapon.DestroyOldWeapon)


각 손에서 무기를 찾아 장착 무기와 같은 이름을 가진 무기를 찾는 경우 이름을 DESTROYING으로 바꾸고 삭제한다.

무기의 이름을 DESTROYING으로 바꾸지 않으면 현재 무기와 혼동되는 경우가 생긴다.

Weapon Saving (Fighter)


현재 무기의 이름을 저장하고 불러올 때는 Resources 파일에 저장된 무기의 이름으로 로드하여 장착한다.

Weapon Spawning (WeaponPickup)


WeaponPickup에서 충돌이 일어나면 코루틴 HideForSeconds를 실행하여 WeaponPickup의 콜라이더와 자식 오브젝트를 일정 시간동안 비활성화한다.

Stat

Scriptable Object (Progression.cs)


Health



Experience

event Action

경험치를 얻으면 델리게이트 이벤트인 onExperienceGained가 실행 -> BaseStats

BaseStats(Level)


onExperienceGained가 실행되면 저장해둔 UpdateLevel이 실행되면서 경험치를 얻을때마다 레벨이 계산된다.

UI

Display

HealthDisplay

EnemyHealthDisplay

ExperienceDisplay

0개의 댓글