새소식

유니티/Fusion

[Fusion] Asteroids.SharedSimple 뜯어보기

  • -

 

[Version 2.0.0]

https://doc.photonengine.com/fusion/current/game-samples/fusion-asteroids

 

Fusion 2 Asteroids Simple | Photon Engine

The sample is a beginner sample showing how to implement simple arcade top-down gameplay. The sample comes in two variants: Asteroids Host Mode Si

doc.photonengine.com

 

 

 

! Player

 

PlayerDataNetworked.cs

플레이어의 모든 정보를 가지고 있으며, 모든 클라이언트에 정보가 복제되는 것을 보장한다.

스폰과 디스폰 시 정보 초기화도 진행한다.

  • Score
  • Lives
  • NickName

각 정보를 가지고 있으며, 변경 사항을 받는다.

모든 플레이어가 OverView에 참조되며, 각 정보에 대해 변경될  업데이트 된다.

 

 

LocalInputPoller.cs

INetworkRunnerCallbacks을 상속받아 OnInput을 통해 입력에 대한 값을 지정한다.

 

 

LocalPlayerData.cs

플레이어 데이터 중 닉네임에 대한 설정

 

 

 

Struct SpaceshipInput

플레이어 입력에 대한 정보

 

 

 

 

SpaceshipController.cs

 

  • 우주선에 대한 생명주기 동안 조작하며
  • NetworkBehaviour에서 기본적으로 제공되는 기능을 사용하는 FusionPlayer에서 파생된 컨트롤러
  • 플레이어의 관리와 틱정렬 시스템

주요기능

1. 행성과의 충돌 판정

2. 네트워크 인터페이스를 통한 이동 판정

3. Fixed, Simulation, Render를 통한 우주선 업데이트

4. 이동, 죽음 효과

5. 스폰, 총알 발사 등 TickTimer의 사용

        //스폰될 경우
        public override void Spawned()
        {
            _playerDataNetworked = GetComponent<PlayerDataNetworked>();

            // 모델의 컬러를 현재 클라이언트의 번호에 따라 부여한다.
            var playerRef = Object.InputAuthority;
            _spaceshipModel.material.color = GetColor(playerRef.PlayerId);

            // Grab a change detector for this NB so we can detect when a life is lost and play an appropriate effect
            // 플레이어가 생명을 잃거나 충분한 효과가 생겼을 경우 감지가능하다.
            //Simulation으로부터 가져오는 것
            _changeDetector = GetChangeDetector(ChangeDetector.Source.SimulationState);

            // We're controlling the ship using forces, so grab the rigidbody
            _rigidbody = GetComponent<Rigidbody>();

            //현재 객체에 대한 권한이 있는 경우 실행
            if (HasStateAuthority)
            {
                IsAlive = true;
            }
        }



        //네트워크 상 모든 시뮬레이션이 끝나고, Fusion이 물리는 사용하고 있는 경우 유니티 Update에서 사용
        public override void Render()
        {
            // Adjust the engine effect based on acceleration
            ParticleSystem.EmissionModule e = _engineTrailVFX.emission;
            e.rateOverTimeMultiplier = Mathf.Abs(Acceleration);
            var main = _engineTrailVFX.main;
            main.startSpeedMultiplier = -0.2f * Acceleration;

            // Check if there were any changes to our network state and handle changes in our alive state
            foreach (var change in _changeDetector.DetectChanges(this, out var prev, out var current))
            {
                switch (change)
                {
                    //변경된 값이 IsAlive라면?
                    case nameof(IsAlive):
                    {
                        // Get a property reader and read the previous and current values of the changed property
                        //네트워크 Reader로부터 변경된 값을 가져와 이전값과 다음값을 읽는다.
                        (var wasAlive, var isAlive) = GetPropertyReader<NetworkBool>(change).Read(prev, current);
                        ToggleVisuals(wasAlive, isAlive);
                        break;
                    }
                }
            }
        }

        //파괴와 생성을 확인한다.
        private void ToggleVisuals(bool wasAlive, bool isAlive)
        {
            // Check if the spaceship was just brought to life
            //막 살아난 경우
            if (isAlive && !wasAlive)
            {
                _spaceshipModel.enabled = true;
                _engineTrailVFX.Play();
                _destructionVFX.Stop();
            }
            // 막 죽은 경우
            else if (wasAlive && !isAlive)
            {
                _spaceshipModel.enabled = false;
                _engineTrailVFX.Stop();
                _destructionVFX.Play();
            }
        }

        //플레이어 숫자에 따른 색 변경
        public static Color GetColor(int player)
        {
            switch (player % 8)
            {
                case 0: return Color.red;
                case 1: return Color.green;
                case 2: return Color.blue;
                case 3: return Color.yellow;
                case 4: return Color.cyan;
                case 5: return Color.grey;
                case 6: return Color.magenta;
                case 7: return Color.white;
            }

            return Color.black;
        }

        //네트워크 상에서의 업데이트
        public override void FixedUpdateNetwork()
        {
            var gamestate = GameController.Singleton;

            //게임 동작 중인 경우
            if (!gamestate.GameIsRunning)
                return;

            // Checks if the spaceship is ready to be respawned.
            //우주선이 스폰 준비가 된 경우
            if (RespawnTimer.Expired(Runner))
            {
                IsAlive = true;
                RespawnTimer = default;
            }

            // Checks if the spaceship got hit by an asteroid
            //우주선이 행성에 강타 당한 경우
            if (IsAlive && HasHitAsteroid())
            {
                ShipWasHit();
            }

            // Handle input if we have StateAuthority over the object (GetInput returns false otherwise)
            //GetInput은 현재 객체가 StateAuthority인 경우 반환된다.
            //아닌 경우 false가 반환된다.
            if (AcceptInput && GetInput<SpaceshipInput>(out var input)) //SpaceshipInput입력 -> NetworkInput 변환 등록 -> SpaceshipInput 변환 반환
            {
                Move(input);
                Fire(input);
            }

            // Keep spaceship on screen
            //우주선이 현재 바탕 화면에 존재하는지 판단
            CheckExitScreen(gamestate);
        }

        //행성에 타격 당한 경우
        private bool HasHitAsteroid()
        {
            //네트워크 상 충돌 객체
            var count = Runner.GetPhysicsScene().OverlapSphere(_rigidbody.position, _spaceshipDamageRadius, _hits,
                _asteroidCollisionLayer.value, QueryTriggerInteraction.UseGlobal);

            if (count <= 0)
                return false;

            var asteroidBehaviour = _hits[0].GetComponent<AsteroidBehaviour>();
            
            return asteroidBehaviour.OnAsteroidHit();
        }


        // Toggle the IsAlive boolean if the spaceship was hit and check whether the player has any lives left.
        // If they do, then the RespawnTimer is activated.
        //남은 생명이 있는경우 충돌 판정과 충돌 된 경우 리스폰을 판단
        private void ShipWasHit()
        {
            if (!HasStateAuthority) return;
            
            _rigidbody.velocity = Vector3.zero;
            _rigidbody.angularVelocity = Vector3.zero;
            
            IsAlive = false;

            //타이머 발동
            if (_playerDataNetworked.Lives > 1)
                RespawnTimer = TickTimer.CreateFromSeconds(Runner, _respawnDelay);
            else
                RespawnTimer = default;

            //보여지는 라이프를 감소
            _playerDataNetworked.SubtractLife();
        }

        // Moves the spaceship RB using the input for the client with InputAuthority over the object
        //InputAuthority가 포함된 클라이언트 입력을 사용
        private void Move(SpaceshipInput input)
        {
            Transform xform = transform;
            //up 방향에 회전가속도 더하기
            _rigidbody.AddRelativeTorque(
                Mathf.Clamp(input.HorizontalInput, -1, 1) * _rotationSpeed * Runner.DeltaTime * xform.up,
                ForceMode.VelocityChange);

            //선형 가속도
            Acceleration = Mathf.Clamp(input.VerticalInput, -1, 1) * _acceleration * Runner.DeltaTime;

            //가속
            Vector3 force = xform.forward * Acceleration;
            _rigidbody.AddForce(force);

            //최고속도를 초과한 경우 최고값으로 지정
            if (_rigidbody.velocity.magnitude > _maxSpeed)
                _rigidbody.velocity = _rigidbody.velocity.normalized * _maxSpeed;
        }

        // Moves the ship to the opposite side of the screen if it exits the screen boundaries.
        // 가장자리에 우주선이 위치한 경우 반대 편으로 이동
        private void CheckExitScreen(GameController ctrl)
        {
            var position = _rigidbody.position;

            if (Mathf.Abs(position.x) < ctrl.ScreenBoundaryX && Mathf.Abs(position.z) < ctrl.ScreenBoundaryY) return;

            if (Mathf.Abs(position.x) > ctrl.ScreenBoundaryX)
                position = new Vector3(-Mathf.Sign(position.x) * ctrl.ScreenBoundaryX, 0, position.z);

            if (Mathf.Abs(position.z) > ctrl.ScreenBoundaryY)
                position = new Vector3(position.x, 0, -Mathf.Sign(position.z) * ctrl.ScreenBoundaryY);

            //가장자리에서 이동하는 것을 방지
            position -= position.normalized *
                        0.1f; // offset a little bit to avoid looping back & forth between the 2 edges 
            
            //네트워크 객체를 텔레포트
            //런시뮬레이션 이전에 FixedNetwork에서 호출할 수 있다.
            GetComponent<NetworkRigidbody3D>().Teleport(position);
        }

        // Checks the Buttons in the input struct against their previous state to check
        // if the fire button was just pressed.
        // 구조체를 통해 버튼의 입력을 비교하여 눌렀는지 판단
        private void Fire(SpaceshipInput input)
        {
            //버튼을 누른 경우 발사
            if (input.Buttons.WasPressed(ButtonsPrevious, SpaceshipButtons.Fire))
                SpawnBullet();

            ButtonsPrevious = input.Buttons;
        }

        // Spawns a bullet which will be travelling in the direction the spaceship is facing
        //우주선이 바라보는 방향으로 이동하는 총알을 생성
        private void SpawnBullet()
        {
            if (_shootCooldown.ExpiredOrNotRunning(Runner) == false) return;

            Runner.Spawn(_bullet, _rigidbody.position, _rigidbody.rotation, Object.InputAuthority);

            _shootCooldown = TickTimer.CreateFromSeconds(Runner, _delayBetweenShots);
        }

 

 

 

! Server

 

OnServerDisconnected.cs

INetworkRunnerCallbacks를 통해 서버가 닫힐 경우 씬을 불러온다,

 

 

! UI

 

StartMenu.cs

1. 타이틀에서 네트워크 시작을 위한 Runner 생성과 방 번호를 통해 네트워크에 연결된다.

  • 닉네임
  • 방이름
  • 네트워크 러너 프리팹을 통한 생성

 

 

PlayerOverviewPanel.cs


1. 현재 씬에 생성된 모든 플레이어 에 대한 모든 객체에 대한 정보를 업데이트 한다.

2. PlayerDataNetworked.cs 를 Dictionary 형태로 참조하여 값을 업데이트 한다.

 

새로 생성되는 Text 정보는 Grid Layout Group를 통해 정렬한다.

 

 

 

 

행성

 

AsteroidSpawner.cs

  • 큰 행성에 대한 생성
  • 네트워크 소유 주체가 바뀌면 다시 생성

 

 

AsteroidBehaviour.cs

  • 행성에 대한 움직임과 생성 파괴에 대한 값이 들어가 있음
  • 행성의 파괴는 Host가 아니어도 RPC를 통한 통신을 통해 다른 클라이언트들과의 소통이 이루어짐

 

 

 

 

GameController.cs

 

  • 게임 진행 시 3가지 상태에 따라 업데이트 한다.
  • 플레이어가 Master일 경우에만 행성의 생성과 게임의 종료를 판단한다.
  • 플레이어들이 생성될 경우 고유 ID가 등록된다.

어떻게 마스터 클라이언트를 판단하는 것일까?

 

 

 

 

 

 

 

 

'유니티 > Fusion' 카테고리의 다른 글

flag |= NetworkObjectFlags.MasterClientObject;  (0) 2024.06.18
Fusion 기본 정보  (1) 2024.06.06
photon fusion tutorial #3  (1) 2024.06.04
Photon Fusion Tutorial #2  (0) 2024.06.04
Fusion Shared Tutorial #1  (0) 2024.06.04
Contents

아핫

땡큐하다