Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(63일차) - Network 기초 및 Photon 사용법 / 격투 게임 (1)

by 독기품은토끼 2025. 8. 18.
✅ 오늘의 학습 목표
1. Network 기초 이론
2. Photon 설치 및 활용
3. 격투 게임 (1)

1. Network 기초

 

[서버]

멀티플레이 게임은 서버를 중심으로 돌아간다.

모든 플레이어가 접속해서 같은 게임 상태를 공유할 수 있도록 관리하는 역할을 한다.

 

[동기화 (Synchronization)]

멀티플레이에서 제일 중요한 건 모두가 같은 게임을 보고 있어야 한다는 점이다.

이를 동기화라고 부른다.

 

[시간 보정 (Interpolation)]

네트워크는 항상 약간의 지연(Latency)가 있다.

내가 움직였다고 바로 상대 화면에 반영되는 것이 아니라 약간의 딜레이가 발생할 수 있다는 뜻이다.

 

[TCP / UDP]

수업에서 TCP와 UDP에 대한 내용도 다뤘는데 아래 포스팅에서 자세히 적어놓았으니 생략하겠다.

 

[네트워크 관리사 - 2급] 필기 요약 & 합격 팁

작년 10월 네트워크 관리사 2급 시험에 합격하였다.필기 / 실기 모두 시험 난이도가 어렵지 않다고 해서 1주일 전부터 공부를 시작했던 것 같다. 최종적으로는 필기 84점 / 실기 89점 으로 합격!합

toxicbunny.tistory.com

 

2. Photon

 

Photon은 멀티플레이를 구현하기 위한 프레임워크이다.

  • Client A (Host)
    → 게임에 가장 먼저 들어온 플레이어가 방을 만들고 기준이 됨
  • Photon View
    → 네트워크에 생성된 오브젝트를 식별할 때 쓰이는 핵심 컴포넌트
    → 각 오브젝트는 Photon View ID를 가지고 있어서 "누가 누구인지"를 구분할 수 있음
  • ID로 식별
    → Player A, B, C, D가 있다고 하면 각각의 Photon View ID를 통해 모든 클라이언트에서 동일한 오브젝트로 인식
  • 동기화 결과
    → Client A가 움직였을 때 그 정보가 네트워크를 통해 전송되고 Client B, C, D 화면에서도 동일하게 반영

 

[Photon 주요 타입]

구분 설명 사용 예시 특징
Photon Realtime 가장 기본이 되는 네트워크 라이브러리 룸 생성, 플레이어 매칭, 메시지 전송 - 가벼움- 로우레벨 API 제공
- 멀티플레이 핵심 기능 담당
Photon Pun (Photon Unity Networking) Unity 전용으로 만든 멀티플레이 프레임워크 PhotonNetwork.Instantiate()로 오브젝트 생성 - Unity 친화적- Photon View를 이용한 오브젝트 동기화
- 초보자가 쓰기 쉬움
Photon Fusion 차세대 멀티플레이 엔진 FPS, MOBA 같은 빠른 액션 게임 - 서버/클라이언트 권한 구조 지원
- 예측(Prediction), 보정(Reconciliation) 내장
- 고성능 멀티플레이에 적합
Photon Quantum 완전한 Deterministic(결정론적) 멀티플레이 엔진 RTS, 대전격투, 카드게임 - 모든 클라이언트가 동일한 연산 수행
- 렉 없는 동기화 가능
- 단, 개발 난이도 높음
Photon Voice / Chat 음성/채팅 특화 라이브러리 보이스 채팅, 텍스트 채팅 - Pun, Realtime 등과 함께 사용 가능
- 실시간 커뮤니케이션 기능 제공

 

1. Photon 생성 및 기본 셋팅

🥕 예행 작업
1. Asset 다운로드
 

PUN 2 - FREE | 네트워크 | Unity Asset Store

Get the PUN 2 - FREE package from Photon Engine and speed up your game development process. Find this & other 네트워크 options on the Unity Asset Store.

assetstore.unity.com


2. Photon 회원가입
 

글로벌 크로스 플랫폼 실시간 게임 개발 | Photon Engine

EssentialPhoton Details Discover a summary of our product range, notable features, the power of the Photon Cloud, and our cost-effective pricing plans. HAVE A LOOK

www.photonengine.com

 

1.1. 애플리케이션 만들기 클릭

 

1.2. 애플리케이션 유형 및 Photon 종류 설정

 

1.3. 생성된 애플리케이션의 App ID 복사

 

1.4. \Assets\Photon\PhotonUnityNetworking\Resources\PhotonServerSettings 에 ID 넣기

 

 

2. Photon View

멀티플레이 게임을 만들 때 네트워크를 통해 동기화할 오브젝트를 지정

프리팹에 Photon View 추가

using Photon.Pun;
using Photon.Realtime;
using UnityEngine;

// MonoBehaviourPunCallbacks -> 매니저일 때 사용
// MonoBehaviourPun -> 못들음!
public class Simple_NetworkManager : MonoBehaviourPunCallbacks
{
    private string gameVersion = "1";

    void Awake()
    {
        Screen.SetResolution(1920, 1080, false); // 해상도 설정, false = Full Screen 사용 여부
        PhotonNetwork.SendRate = 60; // 내 컴퓨터 게임 정보에 대한 전송률
        PhotonNetwork.SerializationRate = 30; // Photon View 관측 중인 대상에 대한 전송률
        PhotonNetwork.GameVersion = gameVersion;
    }

    void Start()
    {
        Connect();
    }

    private void Connect()
    {
        PhotonNetwork.ConnectUsingSettings(); // App ID 기반으로 접속
        Debug.Log("서버 접속");
    }

    public override void OnConnectedToMaster()
    {
        PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 20}, null);
        Debug.Log("서버 접속 완료");
    }

    public override void OnJoinedRoom()
    {
        // 네트워크 상에 생성 (/Assets/Resource 폴더에 있는 "Player" 이름의 오브젝트 생성)
        PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity);

        Debug.Log("캐릭터 생성");
    }
}

 

 

  1. Awake()
    • Screen.SetResolution(1920, 1080, false) : 해상도를 고정해서 테스트 환경을 일정하게 맞춰줌
    • PhotonNetwork.SendRate = 60 : 내 컴퓨터에서 발생하는 입력이나 움직임 정보를 초당 60번 서버로 전송
    • PhotonNetwork.SerializationRate = 30 : Photon View가 붙은 오브젝트의 상태(위치, 애니메이션 등)를 초당 30번 동기화
    • PhotonNetwork.GameVersion = gameVersion : 같은 버전끼리만 매칭되도록 설정
  2. OnJoinedRoom()
    • PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity) : 네트워크 상에 Player Prefab을 생성
    • 이때 Player Prefab에 Photon View가 붙어 있어야 다른 클라이언트와도 공유됨
    • /Assets/Resources 폴더 안에 있는 Prefab 이름을 지정해야 정상적으로 불러올 수 있음
  3. MonoBehaviour
    • PunCallbacks : 네트워크 매니저, 로비/룸 관리, 매칭등 매니저 역할 스크립트에서 사용
    •  Pun : 씬 위에서 동작하는 개별 오브젝트 스크립트에서 사용

 

 

 

 

3. Multiplayer Play Mode

멀티플레이 테스트를 도와주는 패키지

 

  • Window > Multiplayer > Multiplayer Play Mode 메뉴를 통해서 테스트용 창을 열 수 있다.
  • 여기에서 새로운 Player를 추가할 수 있고 Active로 설정하면 하나의 에디터 안에서 두 명 이상의 플레이어를 동시에 실행할 수 있다.
  • 그러나 Photon이 Pun 타입으로 생성되었기 때문에 기본 상태에서는 Player2 창에서 내 움직임이 바로 반영되지 않는다.

 

4. 동기화

 

네트워크에서 게임 오브젝트는 크게 두 가지로 나눌 수 있다.

  • Local 오브젝트 : 내가 직접 조작하는 내 캐릭터
  • Remote 오브젝트 : 다른 플레이어가 조작하는 캐릭터

Photon에서는 이를 아래와 같이 구분한다.

  • photonView.IsMine → 내가 조종하는 내 오브젝트
  • !photonView.IsMine → 다른 플레이어의 오브젝트

 

using Photon.Pun;
using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))] // 필수 컴포넌트 표시

public class Simple_PlayerController : MonoBehaviourPun
{
    private CharacterController cc;

    private Vector3 moveInput;

    [SerializeField] private float moveSpeed = 2f;
    [SerializeField] private float turnSpeed = 10f;

    void Start()
    {
        cc = GetComponent<CharacterController>();
    }

    void Update()
    {
        if (photonView.IsMine)
        {
            Move();
            Turn();
        }
    }

    private void Move()
    {
        cc.Move(moveInput * moveSpeed * Time.deltaTime);
    }

    void OnMove(InputValue value)
    {
        var moveValue = value.Get<Vector2>();
        moveInput = new Vector3(moveValue.x, 0, moveValue.y);
    }

    void Turn()
    {
        if (moveInput == Vector3.zero)
            return;

        Quaternion targetRot = Quaternion.LookRotation(moveInput);
        transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, turnSpeed * Time.deltaTime);
    }
}

 

플레이어의 움직임을 InputSystem을 활용해서 구현한 코드이다.

 

void Update()
{
    if (photonView.IsMine)
    {
        Move();
        Turn();
    }
}

 

위와 같이 IsMine 조건을 넣어주면 내 캐릭터만 입력을 받아서 움직이고 다른 사람 캐릭터는 입력을 무시하게 된다.

만약 IsMine 처리를 하지 않으면 씬에 있는 모든 플레이어가 동시에 똑같이 움직이는 문제가 생긴다.

 

여기서 Photon Transform View 컴포넌트가 중요한 역할을 한다.

  • 내 캐릭터(Local)는 내가 입력을 처리하고 그 좌표값을 네트워크에 전송
  • 다른 사람 캐릭터(Remote)는 입력은 하지 않고 전송받은 좌표값을 반영

즉, 내가 움직이면 Photon Transform View가 위치/회전 정보를 패킷으로 보내고 → 다른 플레이어 화면에서도 내 캐릭터가 동일하게 움직이는 모습을 볼 수 있다.

 

 

3. Photon 실습

1. 닉네임 동기화

1.1. Intro 씬 생성 및 Scene List 설정

 

1.2. Intro 씬 UI 배치

 

1.3. Player 오브젝트에 text 추가

 

1.4. 스크립트

public class NetworkManager : MonoBehaviourPunCallbacks
{
    private string gameVersion = "1";

    [SerializeField] private TMP_InputField nickNameField;
    [SerializeField] private Button connectButton;

    void Awake()
    {
        Screen.SetResolution(1920, 1080, false); // 해상도 설정, false = Full Screen 사용 여부
        PhotonNetwork.SendRate = 60; // 내 컴퓨터 게임 정보에 대한 전송률
        PhotonNetwork.SerializationRate = 30; // Photon View 관측 중인 대상에 대한 전송률
        PhotonNetwork.GameVersion = gameVersion;
    }

    void Start()
    {
        connectButton.onClick.AddListener(Connect);
    }

    private void Connect()
    {
        PhotonNetwork.NickName = nickNameField.text;

        PhotonNetwork.ConnectUsingSettings(); // App ID 기반으로 접속
        Debug.Log("서버 접속");
    }

    public override void OnConnectedToMaster()
    {
        PhotonNetwork.JoinOrCreateRoom("Room", new RoomOptions { MaxPlayers = 20 }, null);
        Debug.Log("서버 접속 완료");
    }

    public override void OnJoinedRoom()
    {
        PhotonNetwork.LoadLevel(1);
        Debug.Log("방 입장");
    }
}
// 플레이어 오브젝트 생성
public class Simple_GameManager : MonoBehaviour
{
    // 방에 입장하기 전에 작동되면 에러
    IEnumerator Start()
    {
        yield return null; // 동기화 과정에서의 타이밍 조절용?

        PhotonNetwork.Instantiate("Player", Vector3.up, Quaternion.identity);
    }
}
// PlayerController.cs 추가된 내용
public class Simple_PlayerController : MonoBehaviourPun
{
    [SerializeField] private TextMeshPro nickName;

    void Start()
    {
        if (photonView.IsMine)
        {
            nickName.text = PhotonNetwork.NickName;
            nickName.color = Color.black;
        }
    }
}

 

  • 인트로 씬에서 TMP_InputField로 닉네임을 입력하고 버튼을 누르면 해당 닉네임이 PhotonNetwork.NickName에 저장된 뒤 서버에 접속
  • PhotonNetwork.LoadLevel(1)을 호출해서 플레이어가 동일한 씬(1번 씬)으로 이동
  • PhotonNetwork.Instantiate("Player")로 Player 오브젝트를 네트워크 상에 생성
  • photonView.IsMine을 체크해서 본인 캐릭터에만 닉네임을 세팅

 

1.5. 인스펙터 연결

 

1.6 타인 닉네임 동기화

// PlayerController.cs
void Start()
{
    cc = GetComponent<CharacterController>();

    if (photonView.IsMine) // 내 이름 불러오기
    {
        nickName.text = PhotonNetwork.NickName;
        nickName.color = Color.black;
    }
    else // 다른 플레이어의 이름도 불러오기
    {
        nickName.text = photonView.Owner.NickName;
        nickName.color = Color.green;
    }
}

 

  • photonView가 !IsMine일 때에는 그 소유자(Owner)의 닉네임을 불러오도록 설정
  • 위 영상을 보면 Player2의 닉네임이 Player1 화면에서 정상적으로 동기화되는 것을 확인할 수 있다.

 

2. 이벤트 동기화

2.1. 새로운 InputSystem 이벤트 등록

 

2.2. Player 모자 생성

 

2.3. 모자 동기화 전 / 후

public class Simple_PlayerController : MonoBehaviourPun
{
    [SerializeField] private GameObject hat;

    void OnHat()
    {
        if (photonView.IsMine)
        {
            hat.SetActive(!hat.activeSelf);
        }
    }
}
public class Simple_PlayerController : MonoBehaviourPun
{
    [SerializeField] private GameObject hat;
    
    void OnHat()
    {
        if (photonView.IsMine)
        {
            photonView.RPC("Hat", RpcTarget.All);
        }
    }

    [PunRPC]
    private void Hat()
    {
        hat.SetActive(!hat.activeSelf);
    }
}


// -------------- 매개변수 활용 --------------
void OnHatOn()
{
    if (photonView.IsMine)
    {
        photonView.RPC("Hat", RpcTarget.All, true);
    }
}

void OnHatOff()
{
    if (photonView.IsMine)
    {
        photonView.RPC("Hat", RpcTarget.All, false);
    }
}

[PunRPC]
private void Hat(bool isOn)
{
    hat.SetActive(isOn);
}

 

  • RPC는 모든 대상에게 내가 실행시킨 함수를 알려주는 것

 

4. Photon 응용 - 격투 게임

🥕 예행 작업
1. Assets 다운로드
 

Creative Characters FREE - Animated Low Poly 3D Models | 3D 휴머노이드 | Unity Asset Store

Elevate your workflow with the Creative Characters FREE - Animated Low Poly 3D Models asset from ithappy. Find this & other 휴머노이드 options on the Unity Asset Store.

assetstore.unity.com

 

Starter Assets - ThirdPerson | Updates in new CharacterController package | Unity 필수에셋 | Unity Asset Store

Get the Starter Assets - ThirdPerson | Updates in new CharacterController package package from Unity Technologies and speed up your game development process. Find this & other Unity 필수에셋 options on the Unity Asset Store.

assetstore.unity.com

 

1. 기본 셋팅

1.1. 씨네머신 업데이트

씨네머신 업그레이드 하고,  인스펙터 창에서 업그레이드 하란거 다 하고 브레인 설정까지 해줌

 

1.2. 캐릭터 생성 및 적용

 

1.3. 애니메이션 새로 적용

 

1.4. Hit 박스 만들기

 

1.5. InputSystem에 새로운 Action 등록

 

1.6 스크립트

using System.Collections;
using Photon.Pun;
using TMPro;
using UnityEngine;

public class Fight_PlayerController : MonoBehaviourPun
{
    private Animator anim;
    [SerializeField] private TextMeshPro nickName;

    [SerializeField] private GameObject punchBox;
    [SerializeField] private GameObject kickBox;

    void Start()
    {
        anim = GetComponent<Animator>();

        // if (photonView.IsMine)
        // {
        //     nickName.text = PhotonNetwork.NickName;
        //     nickName.color = Color.green;
        // }
        // else
        // {
        //     nickName.text = photonView.Owner.NickName;
        //     nickName.color = Color.red;
        // }
    }

    void OnPunch()
    {
        StartCoroutine(PunchRoutine());
    }

    IEnumerator PunchRoutine()
    {
        anim.SetTrigger("Punch");

        yield return new WaitForSeconds(0.5f);
        punchBox.SetActive(true);

        yield return new WaitForSeconds(0.3f);
        punchBox.SetActive(false);
    }

    void OnKick()
    {
        StartCoroutine(KickRoutine());
    }

    IEnumerator KickRoutine()
    {
        anim.SetTrigger("Kick");

        yield return new WaitForSeconds(0.5f);
        kickBox.SetActive(true);

        yield return new WaitForSeconds(0.3f);
        kickBox.SetActive(false);
    }
}