Endeavor/Unity Stuff

Unity - FPS microgame code 분석

hiheybye 2024. 5. 24. 20:11

Goals - Unity / FPS microgame code를 분석한다

FPS microgame / Assets / FPS / Scripts /
AI /
  DetectionModule.cs   EnemyController.cs  
  DetectionModule.cs   EnemyMobile.cs  
  EnemyTurret.cs   FollowPlayer.cs  
  NavigationModule.cs   PatrolPath.cs  
Editor /
  MiniProfiler.cs   PrefabReplacerEditor.cs  
  ShaderBuildStripping.cs   UITableEditor.cs  
Game /
  Actor.cs   AudioUtility.cs  
  ConstantRotation.cs   DebugUtility.cs  
  Event.cs T GameConstants.cs  
  IgnoreHeatMap.cs   IgnoreHitDetection.cs  
  MeshCombiner.cs   MeshCombineUtility.cs  
  MinMaxParameters.cs   PrefabReplacer.cs  
  PrefabReplacerOninstance.cs   TimedSelfDestruct.cs  
Game / Managers /
  ActorsManager.cs   AudioManger.cs  
  EventManager.cs T GameFlowManager.cs  
  ObjectiveManager.cs      
Game / Shared /
  Damageable.cs   DamegeArea.cs  
  Destructable.cs   Health.cs  
  Objective.cs   ProjectileBase.cs  
  WeaponController.cs      
Gameplay
  AmmoPickup.cs   ChargedProjectileEffectsHandler.cs  
  ChargedWeaponEffectsHandler.cs   HealthPickup.cs  
  Jetpack.cs   JetpackPickup.cs  
  OverheatBehavior.cs   Pickup.cs  
  PlayerCharacterController.cs   PositionBobbing.cs  
  ProjectileChargeParameters.cs   ProjectileStandard.cd  
  TeleportPlayer.cs   WeaponFuelCellHandler.cs  
  WeaponPickup.cs      
Gameplay / Managers /
  PlayerInputHandler.cs   PlayerWeaponsManager.cs  
Gameplay / Objectives /
  ObjectiveKillEnemies.cs   ObjectivePickupItem.cs  
  ObjectiveReachPoint.cs      
UI /
  AmmoCounter.cs   Compass.cs  
  CompassElement.cs   CompassMarker.cs  
  CrosshairManager.cs   DisplayMessage.cs  
  DisplayMessageManger.cs   EnemyCounter.cs  
  FeedbackFlashHUD.cs   FillBarColorChange.cs  
  FramerateCounter.cs   InGameMenuManager.cs  
  JetpackCounter.cs   LoadSceneButton.cs  
  MenuNavigation.cs   NotificationHUDManager.cs  
  NotificationToast.cs   ObjectiveHUDManager.cs  
  ObjectiveToast.cs   PlayerHealthBar.cs  
  StanceHUD.cs   TakeScreenshot.cs  
  ToggleGameObjectButton.cs   UITable.cs  
  WeaponHUDManager.cs   WorldspaceHealthBar.cs  

 

Daily

더보기
2024/05  
24 Game / Managers / EventManger.cs 분석 시작 
  Game / Mangers / Event.cs  분석 시작, 마무리
25 Game / Managers / EventManger.cs 분석 마무리
   

 

 

 

Game / Managers /  EventManger.cs 

  중앙 집중식 Observer Pattern, 사용자 정의 Event를 사용함. 

  Game /  Event.cs와 엮어서 볼 것.

# 초반부라 익숙치 않은게 많아서 세세하게

더보기
namespace Unity.FPS.Game /
public class GameEvent /
   
public static class EventManager /
Variable  / static readonly Dictionary<Type, Action<GameEvent>> s_Events
  staic readonly Dictionary<Delegate, Acrtion<GameEvent>> s_EventLooksups
Method / public static void AddListener<T>(Action<T> evt) where T : GameEvent
  public static void RemoveListener<T>(Action<T> evt) where T : GameEvent
  public static void Broadcast(GameEvent evt)
  public static static void Clear()

 

public class GameEvent

  • 아무런 member를 가지지 않음
  • 아무런 상속도 받지 않음
  • Game / Events.cs 에서 이를 상속 받은 다수의  class Event가 선언되고, class Events 에서 각 각의 Event class를 맴버 객체로 가지고 생성해 가지고 있음.  다수의 Event class가 각 각의 별도의 용도를 가지고 용도에 따라 형태를 가진것으로 보여짐.
  • 메서드 뒤의 where T : GameEvent 로 보아, GameEvent를 상속 받은 다양한 Event class 를 대상으로 한다는 것을 알 수 있음.
  • 해당 스크립트 (Game / Managers /  EventManger.cs) 내에서 Action<GameEvent> 이라는 자료형으로 사용됨

public static class EventManager

  • 아무런 상속을 받지 않음. static class이기 때문에 게임오브젝트에 컴포넌트로 만들 필요도 없음.
  • 각 static 멤버 메서드의 호출이 용이함

static readonly Dictionary<Type, Action<GameEvent>> s_Events

  • 특정 타입의 이벤트와 일반화 했던 해당 이벤트 타입을 처리하는 핸들러를 매핑
  • 특정 Event 타입 별로 Observer 목록를 생성해서 저장, 각 Event 별로 채널을 가진다고 이해하면 편할듯 

staic readonly Dictionary<Delegate, Acrtion<GameEvent>> s_EventLooksups

  • 여러 이벤트 타입 핸들러와 각 경우를 일반화한 여러 이벤트 타입의 핸들러를 맵핑
  • GameEvent  에서 파생된 Event와 파생된 Event에 대한 이벤트 핸들러를 GameEvent 형식으로 일반화하여 관리한다고 생각하면 될 듯?
  • 이때 각 핸들러의 Event 타입을 보장하기 위해 s_Events 로 해당하는 Event 타입을 저장해둔다.
  • 모든 Observer에 대해 모아두고 관리하기 위한 딕션어리.
  • 여러 타입의 Event(GameEvent 파생)를 인수로 사용하는 delegate를 관리하기 위해, 람다식을 사용하여 GameEvent 타입 인수를 가지는 delegate를 추가 생성하는데, 본래 delegate 및 생성된 delegate 쌍을 저장.

 

Dictionary 타입 변수 선언과 같이 new 를 통해 할당, 

 

public static void AddListener<T>(Action<T> evt) where T : GameEvent

  •   GameEvent 를 상속받은 클래스형(where T : GameEvent) 인스턴스를 인수로 가지는 메서드의 delegate를 (Action<T> evt) 전달해 이벤트에 구독한다 (AddListener<T>)
  • Action<T> 는 T 타입의 매개변수를 받는 메서드를 나타낸다.

  사용 예시  #Event가 별도의 멤버를 가지지 않음.

namespace Unity.FPS.Game
{
    public class GameFlowManager : MonoBehaviour
    {
        //~~~

        void Awake()
        {
            EventManager.AddListener<AllObjectivesCompletedEvent>(OnAllObjectivesCompleted);
            EventManager.AddListener<PlayerDeathEvent>(OnPlayerDeath);
        }

        void OnAllObjectivesCompleted(AllObjectivesCompletedEvent evt) => EndGame(true);
        void OnPlayerDeath(PlayerDeathEvent evt) => EndGame(false);

        void EndGame(bool win)
        {
        //Endgame code
        }

        //~~~
    }
}

 

  •  AllObjectivesCompletedEvent, PlayerDeathEvent는 GameEvent 를 상속받아 있다. 이를 각각 메서드 OnAllObjectivesCompleted,  OnPlayerDeath가 매개변수 타입으로 사용하고 있다.
public static void AddListener<T>(Action<T> evt) where T : GameEvent
   {
       if (!s_EventLookups.ContainsKey(evt))
       {
           Action<GameEvent> newAction = (e) => evt((T) e);
           s_EventLookups[evt] = newAction;

           if (s_Events.TryGetValue(typeof(T), out Action<GameEvent> internalAction))
               s_Events[typeof(T)] = internalAction += newAction;
           else
               s_Events[typeof(T)] = newAction;
       }
   }

 

Action<GameEvent> newAction = (e) => evt((T) e);

: 우선 람다식에 대한 이해를 잡고 간다.

  • Action<GameEvent> newAction = (e) => evt((T) e); 에서, newAction은 (e)를 인수를 갖고  evt((T) e)를 실행하는 delegate가 된다. 인수, 실행 내용이 각각  =, =>으로 나타낸다.( = 을 혼동하지 않도록 한다.)
  • 여기서 Action<GameEvent>에서 <GameEvent> 가 인수 (e)의 타입을 나타낸다.
  • 이를 통해, 여러 Event(GameEvent를 상속 받은) 형에 대한 delegate를 GameEvent를 인수로 갖는 delegate로 표현했다고 보여진다.
  • 또한 Obserber는 반환값이 없기 때문에, void이다. <인수, 결과> 에서 결과가 void이기 때문에 <GameEvent>

 

if(!s_EventLookups.ContainsKey(evt)) 를 통해 이미 구독된 여부를 확인하고 이를 GameObject를 인수로 갖는 delegate로 변환시켜  (delegate, 변환된 delegate)로  s_EventLooksups 맵핑시킨다.

 

 

s_Events.TryGetValue(typeof(T), out Action<GameEvent> internalAction)

: out 에 대해서

  • 인수에 out을 사용하면, 메서드의 return값 외에도 out에 해당하는 결과값을 받을 수 있다.
  • .TryGetValue()의 리턴형은 bool 이고, 추가적으로  internalAction도 보내준다.

 

if (s_Events.TryGetValue(typeof(T), out Action<GameEvent> internalAction)) 에서, 각 Events의 Subject는 가장 우선으로 AddListener<T>를 하여, 후에 AddListener<T> 로 옵저버가 구독하게 해야한다.

# Broadcast(GameEvent evt) 방식으로 인해, 모두 Observer인 듯 하다.  Subject가 등록되야 한다기 보단, Subject에서 Broadcast(GameEvent evt)를 통해 옵저버를 모두 실행시키면 되는 듯

 

public static void RemoveListener<T>(Action<T> evt) where T : GameEvent

        public static void RemoveListener<T>(Action<T> evt) where T : GameEvent
        {
            if (s_EventLookups.TryGetValue(evt, out var action))
            {
                if (s_Events.TryGetValue(typeof(T), out var tempAction))
                {
                    tempAction -= action;
                    if (tempAction == null)
                        s_Events.Remove(typeof(T));
                    else
                        s_Events[typeof(T)] = tempAction;
                }

                s_EventLookups.Remove(evt);
            }
        }

 Delegate를 사용해 해당하는 Action<GameEvent> newAction를 찾고 제거해준다.

vat는 컴파일러가 타입을 추론한다. 런타임 영향 없음

 

 Action A;

A  -= A; //null

2024.05.25 - [Experiment/Unity] - Unity, C#, Action 과 +=, -=

 

Unity, C#, Action 과 +=, -=

using System;using System.Collections;using System.Collections.Generic;using UnityEngine;public class DelegateTest : MonoBehaviour{ public Action A; public Action B; public Action C; void Start() { A = () => Debug.Log("Action A"); B = () => Debug.Log("Acti

roaring-stretching-thought-wood.tistory.com

 

 

 

public static void Broadcast(GameEvent evt)

 

        public static void Broadcast(GameEvent evt)
        {
            if (s_Events.TryGetValue(evt.GetType(), out var action))
                action.Invoke(evt);
        }

Event 타입으로 해당하는 구독 목록을 불러오고 invoke().

Obserber의 구독을 해당하는 Event별로 묶고, Event 타입을 사용해 Invoke() 시키도록하여서, Subject는 Broadcast(GameEvent evt)만하면된다. (캐스팅을 알아서 되는듯?)

 

 

public static static void Clear()

        public static void Clear()
        {
            s_Events.Clear();
            s_EventLookups.Clear();
        }

자원 반환인 듯?

 

 

Game /  Event.cs

  Game / Managers /  EventManger.cs 의 public class GameEvent 와 관련

  Game / Managers /  EventManger.cs와 엮어서 볼 것

더보기
namespace Unity.FPS.Game / 
public static class Events /
Variable/ 
public static ObjectiveUpdateEvent objectiveUpdateEvent 
 
public static AllObjectivesCompletedEvent AllobjectiveCompletedEvent 
 
public static GameOverEvent GameOverEvent 
 
public static PlayerDeathEvent PlayerDeathEvent
 
public static EnemyKillEvent EnemyKillEvent 
 
public static PickupEvent PickupEvent 
 
public static AmmoPickupEvent AmmoPickupEvent 
 
public static DamageEvent DamageEvent 
 
public static DisplayMessageEvent DisplayMessageEvent 
public class ObjectiveUpdateEvent : GameEvent /
 
public Objective Objective 
 
public string DescriptionText 
 
public string CounterText 
 
public bool IsComplete 
 
public string NotificationText 
public class AllObjectivesCompletedEvent : GameEvent /
 
 
public class GameOverEvent : GameEvent /
 
public bool Win
public class PlayerDeathEvent : GameEvent /
 
 
public class EnemyKillEvent : GameEvent /
 
public GameObject Enemy
 
public int RemainingEnemyCount
public class PickupEvent : GameEvent /
 
public GameObject Pickup
public class AmmoPickupEvent : GameEvent /
 
public WeaponController Weapon
public class DamageEvent : GameEvent /
 
public GameObject Sender
 
public float DamageValue
public class DisplayMessageEvent : GameEvent /
 
public string Message
 
public float DelayBeforeDisplay

 

public static class Events

  • 모든 멤버는 선언과 동시에 할당되었다.
  • Broadcast(GameEvent evt) 를 하기전에 이벤트 인스턴스를 생성할 필요없이, 정적 클래스 Events을 통해, 각 Event 객체에 접근하여 메세지를 보낼 수 있도록 만들어졌다.
  • 더불어서 여러 Event의 모양을 여기서 정의하여 한 파일내에서 관리하고 있다. 
  • 같은 멤버를 가지고 있더라도, EventManager를 통해 Event 종류 단위로 구독하기 때문에 각 이벤트 종류 하나하나가 마치 채널과 같은 역할을 하고 있다고 보여진다.

 

사용 예시

using UnityEngine;
using UnityEngine.SceneManagement;

namespace Unity.FPS.Game
{
    //~~~


    void EndGame(bool win)
    {

        //~~~
        DisplayMessageEvent displayMessage = Events.DisplayMessageEvent;
        
        displayMessage.Message = WinGameMessage;
        displayMessage.DelayBeforeDisplay = DelayBeforeWinMessage;
        
        EventManager.Broadcast(displayMessage);
        //~~~
    }

}

 

 

 

 

'Endeavor > Unity Stuff' 카테고리의 다른 글

Unity Learn - Creative Core Pathway - Final Submission  (0) 2024.05.16