유니티 RPG - 60. 멀티플레이모드 구현 - 보스몬스터
Intro
이번 포스팅에서는 보스몬스터와 컷씬을 네트워크 동기화하는 작업을 수행하였습니다.
-
보스몬스터 소환
-
컷씬
보스몬스터
보스몬스터의 등장과 끝은 다음과 같이 진행됩니다.
수정할 보스몬스터와 관련된 스크립트는 다음 5개입니다.
-
GruntExplosion, GruntThunderbolt : 플레이어에게 피해를 입히는 부분을 RPC로 호출하도록 하였습니다.
-
GruntRange : 컷씬동안 보스몬스가 초기상태를 유지하도록하고, 컷씬이 끝나면 플레이어를 탐지하도록 하였습니다.
-
BossMonster : 타겟 플레이어를 설정하는 로직을 추가하고, 애니메이션 동기화를위해 RPC 메서드를 추가하였습니다.
-
Grunt : 모든 플레이어화면에서 동일한 공격을 수행하도록 동기화하였습니다.
MultiDungeonManager 클래스
보스몬스터를 소환하고 컷씬을 플레이하는 로직을 추가하였습니다.
// MultiDungeonManager.cs
public class MultiDungeonManager : MonoBehaviourPunCallbacks
{
[SerializeField] private Transform[] playerSpawnPositions; // 플레이어 스폰위치
[SerializeField] private Transform[] monsterSpawnPositions; // 몬스터 스폰위치
[SerializeField] private Transform bossSpawnPosition; // 보스몬스터 스폰위치
[SerializeField] private GameObject cutSceneObj; // 컷씬 카메라 오브젝트
[SerializeField] private TimelineAsset[] timelineAsset; // 타임라인 에셋
public int currentMonsterCount = -1; // 현재 몬스터 수(기본 -1)
private GameObject boss; // 보스몬스터 오브젝트
private PlayableDirector pd;
private bool bossSpawned = false; // 보스 등장여부
private static MultiDungeonManager instance;
public static MultiDungeonManager Instance => instance;
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
}
private void Start()
{
SpawnPlayer();
SpawnMonster();
Init();
}
private void Update()
{
if(currentMonsterCount == 0 && PhotonNetwork.IsMasterClient && !bossSpawned)
{
SpawnBossMonster();
}
}
// 플레이어 오브젝트 생성
private void SpawnPlayer()
{
var localPlayerIndex = PhotonNetwork.LocalPlayer.ActorNumber - 1; // 플레이어 넘버
var spawnPosition = playerSpawnPositions[localPlayerIndex]; // 플레이어 위치 설정
PhotonNetwork.Instantiate("Player/MultiPlayer", spawnPosition.position, spawnPosition.rotation);
}
// 초기화
private void Init()
{
// 데이터 리프레쉬
GameManager.Instance.FindPlayerObject();
GameManager.Instance.FindCameraObject();
DataManager.Instance.LoadPlayerData();
EquipmentUI.Instance.LoadEquipmentSlotData();
pd = GetComponent<PlayableDirector>();
pd.played += OnCutSceneStarted;
pd.stopped += OnCutSceneEnded;
}
// 몬스터 생성
private void SpawnMonster()
{
// 마스터 클라이언트만
if (!PhotonNetwork.IsMasterClient) return;
// 몬스터 소환
for(int i = 0;i<monsterSpawnPositions.Length;i++)
{
PhotonNetwork.Instantiate("Monsters/TurtleShell_Multi",
monsterSpawnPositions[i].position, monsterSpawnPositions[i].rotation);
currentMonsterCount = i + 1;
}
}
// 보스몬스터 생성
private void SpawnBossMonster()
{
// 마스터 클라이언트만
if (!PhotonNetwork.IsMasterClient) return;
bossSpawned = true;
// 보스몬스터 소환
boss = PhotonNetwork.Instantiate("Monsters/Boss_Grunt_Multi", bossSpawnPosition.position, bossSpawnPosition.rotation);
// 죽음 이벤트 등록
boss.GetComponent<BossMonster>().OnBossDied += OnBossDied;
// 컷씬 플레이
photonView.RPC(nameof(PlayCutScene), RpcTarget.All);
}
// 보스 죽음 이벤트
private void OnBossDied()
{
}
// 던전 클리어
private IEnumerator DungeonClear()
{
yield return null;
}
[PunRPC]
public void PlayCutScene()
{
cutSceneObj.gameObject.SetActive(false);
pd.Play(timelineAsset[0]);
}
// 컷씬 시작
public void OnCutSceneStarted(PlayableDirector director)
{
GameManager.Instance.player.isCutscenePlaying = true;
GameManager.Instance.player.transform.position = playerSpawnPositions[PhotonNetwork.LocalPlayer.ActorNumber - 1].position;
GameManager.Instance.player.transform.LookAt(bossSpawnPosition);
}
// 컷씬 끝
public void OnCutSceneEnded(PlayableDirector director)
{
GameManager.Instance.player.isCutscenePlaying = false;
}
}
댓글남기기