자료실

[Photon엔진강좌] 초보자도 쉽게 할 수 있는 포톤 유니티 네트워크 (PUN) 튜토리얼 5 기초편
작성자 | admin 2020-09-21  |    조회수 : 5916  


이제 전투를 하면서 정보를 전송해 보겠습니다. 마지막 단계입니다!
유니티에서 GameManager.Photon 스크립트를 만들어 줍니다.



그리고 다음과 같이 코딩해 줍니다.
길어보이지만 별 거 없습니다. Crtl +C , Ctrl + V 만 해 주세요^^
//-----------------------------------------------------------------------------

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine.EventSystems;

enum TARGET{
MASTER = 0,
CLIENT = 1,
}
public partial class GameManager : Photon.MonoBehaviour {

public GameObject[] communicators = new GameObject[2];
// 생성된 플레이어들의 객체를 넣어줌
public bool bGameEnd = false; // 게임이 끝났는지
public bool bNetWarStart = false; // 네트워크 전투가 시작됐는지

void PhotonGameSetting () { // 게임 시작 시 셋팅

if (PhotonNetwork.isMasterClient) {
Debug.Log ("Master Login2:");
uiMe [0].SetActive (true);
uiMe [1].SetActive (false);
communicators[0] = PhotonNetwork.Instantiate (photonPrefab.name, new Vector3 (1f, 10f, 0f), Quaternion.identity, 0);
communicators[0].GetComponent ().iHp = iMyHpBase;
communicators[0].transform.tag = "MASTER";
} else {
Debug.Log ("Client Login2:");
uiMe [0].SetActive (false);
uiMe [1].SetActive (true);
communicators[1] = PhotonNetwork.Instantiate (photonPrefab.name, new Vector3 (5.1f, 10f, 0f), Quaternion.identity, 0);
communicators[1].GetComponent ().iHp = iMyHpBase;
communicators[1].transform.tag = "CLIENT";
}

ResetCharAni (); // 캐릭터 애니메이션 IDLE로 초기화
ResetDisplayHp (); // HP 초기값 넣어주기

StartCoroutine (WaitPlayer()); // 대전 상대 기다리기
}

IEnumerator WaitPlayer(){ // 대전 상대 기다리기

while(!bNetWarStart){

GameObject tmpComm = GameObject.FindGameObjectWithTag("COMM");

if (PhotonNetwork.isMasterClient) {

if (tmpComm && tmpComm.GetComponent ().viewID == 2001) { // 내가 방장일 때 유저가 들어옴
bNetWarStart = true;

communicators [1] = tmpComm;

DeleteAllBlock (); // 기존 블록 삭제
CreateBlock(); // 블록 다시 생성

Debug.Log ("client come in");
}

} else {

if (tmpComm && tmpComm.GetComponent ().viewID == 1001) { // 내가 방원일 때 방장 정보가 들어옴 bNetWarStart = true;

communicators [0] = tmpComm;

DeleteAllBlock (); // 기존 블록 삭제
CreateBlock(); // 블록 다시 생성

Debug.Log ("master come in");
}

}
yield return null;
}
}

void UpdatePhoton () { // GameManager 의 Update() 함수에서 돌아감

if(bNetWarStart){ // 네트워크 대전일 때

CheckWhoWin ();
DisplayHp (); // HP 보여주기

}

}

void CheckWhoWin(){
if (!bGameEnd && communicators [0].GetComponent ().iHp <=0 || communicators
[1].GetComponent ().iHp <=0 ) {
bGameEnd = true;

if (PhotonNetwork.isMasterClient) { // Master Win
if (communicators [1].GetComponent ().iHp <= 0) { // Master Win
panelWin.SetActive (true);
StartCoroutine (UpDownPanel (panelWin));
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.WinPose1);
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Dead);
StartCoroutine(LeaveEnd(3f));
}
if (communicators [0].GetComponent ().iHp <= 0) { // Master Lose
panelLose.SetActive (true);
StartCoroutine (UpDownPanel (panelLose));
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Dead);
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.WinPose1);
StartCoroutine(LeaveEnd(3f));
}
} else {
if (communicators [1].GetComponent ().iHp <= 0) { // Client Lose
panelLose.SetActive (true);
StartCoroutine (UpDownPanel (panelLose));
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.WinPose1);
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Dead);
StartCoroutine(LeaveEnd(3f));
}
if (communicators [0].GetComponent ().iHp <= 0) { // Client Win
panelWin.SetActive (true);
StartCoroutine (UpDownPanel (panelWin));
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.WinPose1);
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Dead);
StartCoroutine(LeaveEnd(3f));
}
}

}
}

void ResetCharAni(){ // 캐릭터 애니 리셋
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Idle);
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Idle);
}
void DisplayHp(){ // 점수를 보여줌
TxtHpMaster.text = communicators [0].GetComponent ().iHp.ToString ();
TxtHpClient.text = communicators [1].GetComponent ().iHp.ToString ();

hpGaugeMaster.fillAmount = (float)communicators [0].GetComponent ().iHp / iMyHpBase;
hpGaugeClient.fillAmount = (float)communicators [1].GetComponent ().iHp / iMyHpBase;
}
void ResetDisplayHp(){ // HP 표시 리셋
TxtHpMaster.text = iMyHpBase.ToString ();
TxtHpClient.text = iEnemyHpBase.ToString ();

hpGaugeMaster.fillAmount = 1f;
hpGaugeClient.fillAmount = 1f;
}

public void NetAttackDamage(int damage){

int attackTarget = 0;

if (PhotonNetwork.isMasterClient) {
attackTarget = (int)TARGET.CLIENT;

} else {
attackTarget = (int)TARGET.MASTER;
}

communicators [0].GetComponent ().RPC ("AttackInfo", PhotonTargets.Others, attackTarget, damage);

}
public void AttackProcess (int attackTarget,int damage){
communicators [attackTarget].GetComponent ().iHp -= damage;

if (communicators [attackTarget].GetComponent ().iHp <= 0) {
communicators [attackTarget].GetComponent ().iHp = 0;
}

if(attackTarget == (int)TARGET.MASTER){
hpGaugeMaster.fillAmount = (float)communicators [attackTarget].GetComponent ().iHp / iMyHpBase;

clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니
StartCoroutine (RotateChar (clientChar.playerChar,false));
StartCoroutine (HitChar (masterChar.playerChar,true,false,damage));
}else{
hpGaugeClient.fillAmount = (float)communicators [attackTarget].GetComponent ().iHp / iMyHpBase;

masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니 StartCoroutine (RotateChar (masterChar.playerChar,true));
StartCoroutine (HitChar (clientChar.playerChar,false,false,damage));
}
}

public void OnPhotonPlayerConnected( PhotonPlayer other ) // 다른 유저가 접속했을 때
{
Debug.Log ("other player connect");
}

public void OnPhotonPlayerDisconnected( PhotonPlayer other ) // 상대의 접속이 끊겼을 때
{
if (PhotonNetwork.isMasterClient) {
Debug.Log ("other player disconnected");

} else {
Debug.Log ("master disconnected");

}

StartCoroutine (LeaveEnd (4f));
}

public IEnumerator LeaveEnd(float ftime){
yield return new WaitForSeconds (ftime);
LeaveRoom();
}

public void LeaveRoom()
{
communicators [0] = null;
communicators [1] = null;

bGameEnd = false;
bNetWarStart = false;

panelWin.SetActive (false);
panelLose.SetActive (false);

ResetCharAni (); // 캐릭터 애니메이션 IDLE로 초기화
ResetDisplayHp (); // HP 초기값 넣어주기

uiMe [0].SetActive (true);
uiMe [1].SetActive (false);

PhotonNetwork.LeaveRoom();
}

}
//-----------------------------------------------------------------------------------




코드를 살펴볼까요?
enum TARGET{


MASTER = 0,
CLIENT = 1,
}
내가 누굴 공격하는지 enum으로 표시해주기 위해서 만들었습니다.

먼저 변수를 보겠습니다. 추가할 변수는 단 3개 뿐입니다.

public GameObject[] communicators = new GameObject[2];
public bool bGameEnd = false; // 게임이 끝났는지
public bool bNetWarStart = false; // 네트워크 전투가 시작됐는지


public GameObject[] communicators = new GameObject[2];
// 생성된 플레이어들의 객체를 넣어줌
communicators 배열을 만들어서 생성된 플레이어들의 객체를 넣어줄 것입니다.

bGameEnd 변수는 게임이 끝났는지를 알기 위해서 추가해 줍니다.
bNetWarStart 변수는 네트워크 대전이 시작됐는지 알기 위해서 추가해 줍니다.

public bool bGameEnd = false; // 게임이 끝났는지
public bool bNetWarStart = false; // 네트워크 전투가 시작됐는지

게임이 종료되었을 때 더 이상 블록을 맞추지 않도록 코드를 추가해 주겠습니다.
GameManager.Photon -> GameManager 스크립트로 넘어갑니다.

그다음 아래 코드를 찾아 bGameEnd 변수를 추가해 줍니다.

void Update () {
....................
....................
if (Input.GetMouseButton (0)) { // 드래그 중
...........
...........
if (selectBlock && bDrag && !bGameEnd ) {
...........
...........
}
...........
...........
}
}

다시 GameManager -> GameManager.Photon 로 넘어옵니다.

이제 변수 설정이 모두 끝났습니다. 간단하네요!



다음은 함수입니다.

void PhotonGameSetting () // 게임 시작 시 셋팅
함수가 보입니다. 게임을 시작시 생성된 네트워크상의 플레이어들의 객체를 셋팅해 주는 함수입니다.

void PhotonGameSetting () { // 게임 시작 시 셋팅

if (PhotonNetwork.isMasterClient) {
// 내가 방장인지 방원인지 체크합니다.

uiMe [0].SetActive (true);
uiMe [1].SetActive (false);
// 내가 방장이면 왼쪽에 Me라는 표시를 해 줍니다.

communicators[0] = PhotonNetwork.Instantiate (photonPrefab.name, new Vector3 (1f, 10f, 0f), Quaternion.identity, 0);

// 내가 방장이면 communicators[0] 배열에 photonPrefab.name 을 복사 생성해 줍니다. communicators[1]는 방원을 넣어줄 것입니다.
Instantiate 함수가 아니라 PhotonNetwork.Instantiate 함수라
photonPrefab.name 프리팹에 name 이 붙습니다.

communicators[0].GetComponent ().iHp = iMyHpBase;
// 기본 체력 HP를 셋팅해줍니다.

communicators[0].transform.tag = "MASTER";
// 태그를 구별하기 쉽게 MASTER로 바꿔줍니다.

} else { // 내가 방원일 때

uiMe [0].SetActive (false);
uiMe [1].SetActive (true);
// 내가 방원일 때 오른쪽에 Me 표시를 해 줍니다.

communicators[1] = PhotonNetwork.Instantiate (photonPrefab.name, new Vector3 (5.1f, 10f, 0f), Quaternion.identity, 0); // 내가 방원이면 communicators[1] 배열에 photonPrefab.name 을 복사 생성해 줍니다. communicators[0]는 방장을 넣어줄 것입니다.

communicators[1].GetComponent ().iHp = iMyHpBase;
// 기본 체력 HP를 셋팅해줍니다.

communicators[1].transform.tag = "CLIENT";
// 태그를 구별하기 쉽게 CLIENT로 바꿔줍니다.

}

ResetCharAni (); // 캐릭터 애니메이션 IDLE로 초기화
ResetDisplayHp (); // HP 초기값 넣어주기

StartCoroutine (WaitPlayer()); // 대전 상대 기다리기
// 나 자신의 셋팅이 끝난 후 상대의 접속을 기다리기 위해 코루틴을 생성하여 다른 유저의 접속을 기다립니다.
}

대전 유저를 기다리는 코루틴 함수 WaitPlayer() 함수를 보겠습니다. ^^

IEnumerator WaitPlayer(){ // 대전 상대 기다리기

while(!bNetWarStart){
// 대전이 아직 시작되지 않았으면 계속 기다린다.

GameObject tmpComm = GameObject.FindGameObjectWithTag("COMM");
// 새로운 유저가 들어오면 COMM태그를 통해 찾는다.

if (PhotonNetwork.isMasterClient) {
// 내가 방장이면

if (tmpComm && tmpComm.GetComponent ().viewID == 2001) { // 내가 방장일 때 유저가 들어옴 // 새로운 유저가 들어오면 포톤에서는 생성된 순서대로 1000단위로 아이디가 생성됩니다. 처음 생성된 방장은 1001 두번째부터는 2001,3001,4001,... 순으로 viedID가 생성됩니다.

bNetWarStart = true; // 대전이 시작됨을 알림

communicators [1] = tmpComm;
// 새로 들어온 유저의 객체를 communicators [1]에 넣어줌

DeleteAllBlock (); // 기존 블록 삭제
CreateBlock(); // 블록 다시 생성

Debug.Log ("client come in");
}

} else {

if (tmpComm && tmpComm.GetComponent ().viewID == 1001) {
// 내가 방원일 때 방장의 정보가 들어오면 방장임을 확인합니다.

bNetWarStart = true; // 대전이 시작됨을 알림

communicators [0] = tmpComm;
// 방장의 객체를 communicators [0]에 넣어줌

DeleteAllBlock (); // 기존 블록 삭제
CreateBlock(); // 블록 다시 생성

Debug.Log ("master come in");
}

}
yield return null;
}
}

하하! 이제 유저들의 접속을 확인하고 전투를 할 모든 준비를 마쳤습니다.



다음은 UpdatePhoton ()함수 입니다.

void UpdatePhoton () {
// GameManager 의 Update() 함수에서 돌아갈 것입니다.
if(bNetWarStart){ // 네트워크 대전일 때

CheckWhoWin (); // 서로의 승패를 체크하는 함수입니다.
DisplayHp (); // 서로의 HP를 보여주는 함수입니다.

}
}
별 거 없습니다.
GameManager.Photon -> GameManager 스크립트로 넘어가서
Update()를 찾아서 UpdatePhoton () 함수를 추가해 줍니다.
void Update () {
..............
..............
UpdatePhoton (); // 추가
}
다시 GameManager -> GameManager.Photon 로 넘어옵니다.

다음은
void CheckWhoWin() {} // 승패를 체크하는 함수입니다.
void ResetCharAni(){} // 캐릭터의 애니를 IDLE로 리셋
void DisplayHp(){} // HP를 표시해 줍니다.
void ResetDisplayHp() // HP를 초기값으로 리셋해 줍니다.
4개의 함수입니다.
(포톤과는 관련없는 함수라 자세한 설명은 생략합니다.)




다음은 네트워크 상에서 정보를 전송하는 중요한 함수입니다!

상대를 공격할 때 NetAttackDamage(int damage) 함수를 통하여 적에게 데미지를 전달합니다.



public void NetAttackDamage(int damage){
// 공격시의 데미지를 상대에게 전달함.
int attackTarget = 0;

if (PhotonNetwork.isMasterClient) {
attackTarget = (int)TARGET.CLIENT;
// 공격하는 대상이 방원일 때
} else {
attackTarget = (int)TARGET.MASTER;
// 공격하는 대상이 방장일 때
}

communicators [0].GetComponent ().RPC ("AttackInfo", PhotonTargets.Others, attackTarget, damage);

// AttackInfo 는 PlayerPhoton 스크립트에 있는 함수이름입니다.
PhotonTargets.Others 자신을 제외한 다른 유저에게 정보를 전송합니다. damage 는 전달하려고 하는 값입니다.
}

다음은 AttackProcess (int attackTarget,int damage) 함수입니다.
damage 값을 PlayerPhoton 스크립트의 RPC함수 AttackInfo 를 통하여
실행되는 함수입니다.

잠깐 PlayerPhoton 스크립트를 보겠습니다.

[PunRPC]
public void AttackInfo(int attackTarget,int damage){
gameManager.AttackProcess (attackTarget,damage);
}

이와 같이 PlayerPhoton 스크립트의 AttackInfo 함수를 통해
GameManager스크립트의 AttackProcess 함수를 실행하게 됩니다.
* 중요!!

public void AttackProcess (int attackTarget,int damage){

communicators [attackTarget].GetComponent ().iHp -= damage;
// 전송받은 damage 값을 받아서 처리해 줍니다. 처리 받은 값은 UpdatePhoton ()의 DisplayHp ()에서 보여주게 됩니다.

if (communicators [attackTarget].GetComponent ().iHp <= 0) { // HP가 0 이 되서 죽었을 때

communicators [attackTarget].GetComponent ().iHp = 0;
}

if(attackTarget == (int)TARGET.MASTER){
hpGaugeMaster.fillAmount = (float)communicators [attackTarget].GetComponent ().iHp /
iMyHpBase;

clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니 StartCoroutine (RotateChar (clientChar.playerChar,false));
StartCoroutine (HitChar (masterChar.playerChar,true,damage));
}else{
hpGaugeClient.fillAmount = (float)communicators [attackTarget].GetComponent ().iHp / iMyHpBase;

masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니 StartCoroutine (RotateChar (masterChar.playerChar,true));
StartCoroutine (HitChar (clientChar.playerChar,false,damage));
}
}

이제 다 됐습니다. 이 강의도 거의 끝나가네요^^



OnPhotonPlayerConnected 함수는 다른 유저가 방에 접속했을 때 호출됩니다.

public void OnPhotonPlayerConnected( PhotonPlayer other ) // 다른 유저가 접속했을 때
{
Debug.Log ("other player connect");
}

OnPhotonPlayerDisconnected 함수는 게임 도중 상대 플레이어가 접속이 끊어졌을 때 호출 됩니다.

public void OnPhotonPlayerDisconnected( PhotonPlayer other ) // 상대의 접속이 끊겼을 때
{
if (PhotonNetwork.isMasterClient) {
Debug.Log ("other player disconnected");
} else {
Debug.Log ("master disconnected");
}

StartCoroutine (LeaveEnd (3f));
}

LeaveEnd 함수는 게임이 종료되었을 때 갑자기 종료되면 섭섭할까봐 코루틴 함수를 만들어 줬습니다.

public IEnumerator LeaveEnd(float ftime){
yield return new WaitForSeconds (ftime);
LeaveRoom();
}

LeaveRoom() 는 이제 진짜 게임이 끝났을 때 방을 떠나는 함수입니다.

public void LeaveRoom() // 룸을 떠남 게임 종료
{
communicators [0] = null; // 게임 초기화
communicators [1] = null; // 게임 초기화

bGameEnd = false; // 게임 초기화
bNetWarStart = false; // 게임 초기화

panelWin.SetActive (false); // 게임 초기화
panelLose.SetActive (false); // 게임 초기화

ResetCharAni (); // 캐릭터 애니메이션 IDLE로 초기화
ResetDisplayHp (); // HP 초기값 넣어주기

uiMe [0].SetActive (true); // 게임 초기화
uiMe [1].SetActive (false); // 게임 초기화

PhotonNetwork.LeaveRoom(); // 방을 떠나게 됩니다.
}

자 이제 다 끝났습니다!!





진짜 마지막으로 GameManager.War 스크립트에서 혼자서 플레이할 때와 네트워크 플레이할 때를 구별해 주겠습니다.

GameManager.Photon->GameManager.War 스크립트로 이동합니다.

MyAttack 함수를 다음과 같이 수정해 주세요.

void MyAttack(int damage){

if (bNetWarStart) { // 네트워크 대전 시
if (PhotonNetwork.isMasterClient) { // Master 일 때
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니
StartCoroutine (RotateChar (masterChar.playerChar,true));
StartCoroutine (HitCharSend (clientChar.playerChar,false,damage));
}else{ // Client 일 때
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니
StartCoroutine (RotateChar (clientChar.playerChar,false));
StartCoroutine (HitCharSend (masterChar.playerChar,true,damage));
}
}else{
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Attack); // 캐릭터 공격 애니 StartCoroutine (RotateChar (masterChar.playerChar,true));
StartCoroutine (HitChar (clientChar.playerChar,false,damage));
}

}

그다음 SendHpDamage 함수도 마찬가지로 다음과 같이 수정해주세요.

void SendHpDamage(int damage){

if (bNetWarStart) { // 네트워크 대전 시
NetAttackDamage (damage);
} else {
iEnemyHp -= damage;
hpGaugeClient.fillAmount = (float)iEnemyHp / iEnemyHpBase;

if (iEnemyHp <= 0) {
iEnemyHp = 0;
panelWin.SetActive (true);
StartCoroutine (UpDownPanel (panelWin));
masterChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.WinPose1);
clientChar.playerChar.GetComponent ().SetState (CHARCTERSTATE.Dead);
StartCoroutine (ReGame());
}

TxtHpClient.text = iEnemyHp.ToString ();
}
}






이제 진짜 다 끝났습니다!! ^^ 축하드립니다!!!
이제 빌드를 하여 확인해 보겠습니다. ^^
윈도우 모드에서 build를 눌러 photonduel.exe 파일을 생성하겠습니다.




그다음 폴더에 생성된 photonduel.exe 파일을 실행합니다.
윈도우모드에서 세로모드를 지원하지 않는 건 함정이네요.
뭔가 심히 아쉽습니다. TT
아쉬움을 뒤로하고 게임을 실행하고 우측 하단의 Join 버튼을 누르면
캐릭터의 머리 위로 번개 표시가 보일 것입니다. 방에 접속했다는 표시입니다. 2개의 번개 표시가 뜨면 게임을 시작하면 됩니다.
공격을 했을 때 서로의 HP가 줄어들면 모든 것이 정상적으로 잘 작동하는 것입니다.




축하드립니다!! ^^
이제 PUN의 기초 튜토리얼이 모두 끝났습니다!
수고하셨습니다.

완성된 소스는 아래에 보관되어 있습니다.
https://drive.google.com/file/d/1WB2b_JQs8ZFTcDAgPNGgAHwFH8Gu4nKJ/view?usp=sharing
Samturn3MatchwithPhoton_lobbytutorial.zip
drive.google.com




★더 많은 글은Photon HelpCenter
https://support.photonengine.jp/hc/ko/categories/204651467 에서 확인하세요!
★Photon 공식 홈페이지
https://www.photonengine.com/ko-kr/Photon