멀티플레이어 슈팅 게임에서 총알 시스템은 단순한 발사 기능을 넘어, 충돌 판정, 데미지 처리, 서버와 클라이언트 간의 네트워크 동기화까지 다양한 기능을 포함해야 합니다. 특히, Unity에서 Mirror 네트워크 라이브러리를 사용해 이러한 시스템을 최적화하는 것은 멀티플레이 환경에서 안정적인 게임플레이를 유지하기 위해 매우 중요합니다. 이 글에서는 실전에 적용 가능한 고급 총알 시스템 구현 방안을 제시하고, 각 단계별 코드와 설명을 통해 서버-클라이언트 동기화를 완벽하게 다루는 방법을 소개합니다.


주요 기능: 완전한 멀티플레이 총알 시스템 설계 요소

  1. 총알 발사 및 관리
    서버에서 생성된 총알 객체를 클라이언트 간에 동기화하여 일관된 총알 발사 애니메이션과 속도를 제공합니다.
  2. 충돌 판정과 데미지 처리
    각 총알은 충돌 시 피해를 입히고 사라지도록 하며, 이를 서버에서 판정해 모든 클라이언트에 결과를 동기화합니다.
  3. 최적화된 메모리 관리
    Object Pooling을 통해 총알 객체의 생성 및 삭제를 최적화하여 네트워크 트래픽을 줄이고 서버 부하를 최소화합니다.
  4. 총알 데이터 동기화
    클라이언트가 서버에서의 총알 위치와 충돌 여부를 정확히 반영하도록 하여 모든 플레이어가 동일한 결과를 경험할 수 있도록 합니다.

예제 코드: 유용한 총알 시스템 구현

여기서는 Mirror 패키지를 사용하여 총알 시스템을 구현합니다. 해당 예제는 총알 발사, 충돌 판정, 메모리 관리, 네트워크 동기화 등 모든 기능을 포함하고 있으며, 실제 게임에서 재사용이 용이하도록 모듈화된 코드로 작성되었습니다.


1. Object Pooling을 통한 총알 관리

Object Pooling을 사용하여 총알을 재사용하는 방식으로 성능을 최적화합니다. 이는 네트워크 트래픽과 서버 부하를 줄이는 데 매우 유용합니다.

using System.Collections.Generic;
using UnityEngine;

public class BulletPool : MonoBehaviour
{
    public GameObject bulletPrefab;
    public int poolSize = 20;
    private Queue<GameObject> bulletPool = new Queue<GameObject>();

    void Start()
    {
        for (int i = 0; i < poolSize; i++)
        {
            GameObject bullet = Instantiate(bulletPrefab);
            bullet.SetActive(false);
            bulletPool.Enqueue(bullet);
        }
    }

    public GameObject GetBullet()
    {
        if (bulletPool.Count > 0)
        {
            GameObject bullet = bulletPool.Dequeue();
            bullet.SetActive(true);
            return bullet;
        }
        else
        {
            GameObject bullet = Instantiate(bulletPrefab);
            bullet.SetActive(true);
            return bullet;
        }
    }

    public void ReturnBullet(GameObject bullet)
    {
        bullet.SetActive(false);
        bulletPool.Enqueue(bullet);
    }
}

2. 서버 동기화된 총알 발사 (플레이어 스크립트)

플레이어가 총알을 발사할 때 서버에 요청하여 모든 클라이언트에 동기화되도록 구현합니다. CmdFire 명령어로 서버에서 총알을 생성하여 네트워크에 반영합니다.

using Mirror;
using UnityEngine;

public class PlayerController : NetworkBehaviour
{
    public BulletPool bulletPool;
    public Transform bulletSpawn;
    public float bulletSpeed = 20f;

    [Command]
    void CmdFire()
    {
        GameObject bullet = bulletPool.GetBullet();
        bullet.transform.position = bulletSpawn.position;
        bullet.transform.rotation = bulletSpawn.rotation;
        NetworkServer.Spawn(bullet);

        RpcInitializeBullet(bullet, bulletSpeed);
    }

    [ClientRpc]
    void RpcInitializeBullet(GameObject bullet, float speed)
    {
        bullet.GetComponent<Bullet>().Initialize(speed);
    }

    void Update()
    {
        if (isLocalPlayer && Input.GetButtonDown("Fire1"))
        {
            CmdFire();
        }
    }
}

3. 총알 이동과 충돌 처리 코드 (서버와 클라이언트 간 동기화)

총알의 이동과 충돌 판정은 서버에서 관리되며, 적중 시 데미지를 입히고 총알을 풀로 되돌립니다. 이를 통해 모든 클라이언트에서 동일한 총알 이동과 적중이 이루어지도록 합니다.

using Mirror;
using UnityEngine;

public class Bullet : NetworkBehaviour
{
    private float speed;
    private Vector3 startPosition;
    private BulletPool pool;

    public void Initialize(float bulletSpeed)
    {
        speed = bulletSpeed;
        startPosition = transform.position;
    }

    void Update()
    {
        if (!isServer) return;

        transform.Translate(Vector3.forward * speed * Time.deltaTime);
        if (Vector3.Distance(startPosition, transform.position) > 100f)
        {
            ReturnToPool();
        }
    }

    [ServerCallback]
    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Enemy"))
        {
            other.GetComponent<Health>().TakeDamage(10);
            ReturnToPool();
        }
    }

    private void ReturnToPool()
    {
        pool.ReturnBullet(gameObject);
        NetworkServer.UnSpawn(gameObject);
    }
}

4. Health 시스템: 데미지 판정과 사망 처리

Health 스크립트는 캐릭터의 체력을 관리하며, 총알과 충돌 시 데미지를 적용합니다. 이 코드는 서버에서 실행되며 모든 클라이언트에서 동일한 결과가 반영되도록 동기화합니다.

using Mirror;
using UnityEngine;

public class Health : NetworkBehaviour
{
    [SyncVar] private int currentHealth = 100;

    public void TakeDamage(int amount)
    {
        if (!isServer) return;

        currentHealth -= amount;
        if (currentHealth <= 0)
        {
            Die();
        }
    }

    private void Die()
    {
        // 사망 로직, 필요 시 부활 기능 포함
        NetworkServer.Destroy(gameObject);
    }
}

총알 시스템의 장점과 단점

장점:

  • Object Pooling을 통한 성능 최적화로 메모리 사용 감소.
  • 서버 기반 충돌 판정으로 해킹 방지 및 데이터의 일관성 유지.
  • 모든 클라이언트에서 정확한 데이터 동기화를 통해 일관된 사용자 경험 제공.

단점:

  • 네트워크 환경에 따라 응답 속도가 느려질 수 있으며, 최적화가 미흡할 경우 지연 발생 가능.
  • 서버에 모든 충돌 판정 로직을 맡기면 부하가 집중될 수 있음.

결론

이번 글에서는 Unity와 Mirror 네트워킹을 활용하여 멀티플레이어 슈팅 게임의 총알 시스템을 구현하는 방법을 살펴보았습니다. Object Pooling, 서버와 클라이언트의 동기화, 충돌 판정 최적화 등의 기능을 추가하여 실전에서도 충분히 사용 가능한 고급 시스템을 구축할 수 있었습니다. 이 시스템을 통해 개발자는 멀티플레이어 게임에서 발생할 수 있는 다양한 상황을 효율적으로 처리할 수 있으며, 플레이어들에게 일관된 경험을 제공할 수 있습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다