4 분 소요

Flag

유니티에서 제공하는 샘플인 보스룸을 보는 도중 다음과 같은 코드를 봤다.

public struct ActionRequestData : INetworkSerializable
{
    public ActionID ActionID; //index of the action in the list of all actions in the game - a way to recover the reference to the instance at runtime
    public Vector3 Position;           //center position of skill, e.g. "ground zero" of a fireball skill.
    public Vector3 Direction;          //direction of skill, if not inferrable from the character's current facing.
    public ulong[] TargetIds;          //NetworkObjectIds of targets, or null if untargeted.
    public float Amount;               //can mean different things depending on the Action. For a ChaseAction, it will be target range the ChaseAction is trying to achieve.
    public bool ShouldQueue;           //if true, this action should queue. If false, it should clear all current actions and play immediately.
    public bool ShouldClose;           //if true, the server should synthesize a ChaseAction to close to within range of the target before playing the Action. Ignored for untargeted actions.
    public bool CancelMovement;        // if true, movement is cancelled before playing this action

    //O__O Hey, are you adding something? Be sure to update ActionLogicInfo, as well as the methods below.

    [Flags]
    private enum PackFlags
    {
        None = 0,
        HasPosition = 1,
        HasDirection = 1 << 1,
        HasTargetIds = 1 << 2,
        HasAmount = 1 << 3,
        ShouldQueue = 1 << 4,
        ShouldClose = 1 << 5,
        CancelMovement = 1 << 6,
        //currently serialized with a byte. Change Read/Write if you add more than 8 fields.
    }

    ...

}

여기서 [Flags] 속성이 달려있는 enum 형이 무엇을 의미하는지를 몰라 찾아보았고 이 글에 남겨두려고한다.

[Flags]는 enum형이 동시에 여러개가 선택될 수 있도록 해주는 속성이다. 보통 enum은 열거된 값 중 하나만 선택할 수 있다. 하지만 비트연산을 통해 동시에 여러개를 선택할 수 있도록 한다.

위의 코드를 보면 PackFalgs라는 enum자료형에 8가지의 값들이 열거되어있다.(None, … , CancleMovement)
enum은 열거형이기 int으로 바꿀 수도 있다. 다음은 평범한 enum형태이다.

// 보통의 enum의 예시
private enum PackFlags
{
    None = 0,
    HasPosition = 1,
    HasDirection = 2,
    HasTargetIds = 3,
    HasAmount = 4,
    ShouldQueue = 5,
    ShouldClose = 6,
    CancelMovement = 7,
}

하지만 이렇게 하면 한 번에 한 개씩만 선택이 가능하다.(None이면 0, CancleMovement면 7)

이때 한 번에 여러개를 선택할 수 있게 하기 위해서 비트연산이 적용된다.

00000000 이렇게 8자리의 2진수가 있다. 각 자리수마다 열거형 값과 대응시켜 가지고 있는경우 1, 가지지 않은 경우 0 을하면 다중 선택 처리가 가능하다. (None, HasDirection, HasAmount를 가지고 있다면 00010101 이런식이 되는것이다.)

// 이 코드에서는 None은 다른 것과 다중선택되지 않는다는 가정하에 7자리로 처리한듯 보인다.
[Flags]
private enum PackFlags
{
    None = 0,
    HasPosition = 1,
    HasDirection = 1 << 1,
    HasTargetIds = 1 << 2,
    HasAmount = 1 << 3,
    ShouldQueue = 1 << 4,
    ShouldClose = 1 << 5,
    CancelMovement = 1 << 6,
}

유니티에서 제공하는 Layer역시도 이런식으로 처리되어 있기 때문에 열거형이면서도 동시에 여러개 선택이 가능한 것 같다.

비트연산이니 비교나 값 추가, 제거등의 연산은 다음과 같이한다.

PackFlags pack = PackFlags.None;

//값이 존재하는지 확인 
if((pack & PackFlags.HasPosition ) != 0) 
 
//특정 값을 추가 
pack |= PackFlags.HasPosition ;
 
//특정 값을 제거 
pack &= ~PackFlags.HasPosition;
 
//특정 값을 반전(1은 0으로, 0은 1로):
pack ^= PackFlags.HasPosition;
 
//모든 값 삭제:
pack = PackFlags.None;

위의 보스룸 코드는 네트워크상으로 전달되는 값을 이 열거형을 통해서 유효한 값만 직렬화,역직렬화 하여 네트워크상으로 보내고받아 대역폭을 아끼는 방식을 구현했다.

public struct ActionRequestData : INetworkSerializable
{
    public ActionID ActionID; //index of the action in the list of all actions in the game - a way to recover the reference to the instance at runtime
    public Vector3 Position;           //center position of skill, e.g. "ground zero" of a fireball skill.
    public Vector3 Direction;          //direction of skill, if not inferrable from the character's current facing.
    public ulong[] TargetIds;          //NetworkObjectIds of targets, or null if untargeted.
    public float Amount;               //can mean different things depending on the Action. For a ChaseAction, it will be target range the ChaseAction is trying to achieve.
    public bool ShouldQueue;           //if true, this action should queue. If false, it should clear all current actions and play immediately.
    public bool ShouldClose;           //if true, the server should synthesize a ChaseAction to close to within range of the target before playing the Action. Ignored for untargeted actions.
    public bool CancelMovement;        // if true, movement is cancelled before playing this action

    //O__O Hey, are you adding something? Be sure to update ActionLogicInfo, as well as the methods below.

    [Flags]
    private enum PackFlags
    {
        None = 0,
        HasPosition = 1,
        HasDirection = 1 << 1,
        HasTargetIds = 1 << 2,
        HasAmount = 1 << 3,
        ShouldQueue = 1 << 4,
        ShouldClose = 1 << 5,
        CancelMovement = 1 << 6,
        //currently serialized with a byte. Change Read/Write if you add more than 8 fields.
    }

    ...

    // 유효한 값을 판별하는 함수
    private PackFlags GetPackFlags()
    {
        PackFlags flags = PackFlags.None;
        if (Position != Vector3.zero) { flags |= PackFlags.HasPosition; }
        if (Direction != Vector3.zero) { flags |= PackFlags.HasDirection; }
        if (TargetIds != null) { flags |= PackFlags.HasTargetIds; }
        if (Amount != 0) { flags |= PackFlags.HasAmount; }
        if (ShouldQueue) { flags |= PackFlags.ShouldQueue; }
        if (ShouldClose) { flags |= PackFlags.ShouldClose; }
        if (CancelMovement) { flags |= PackFlags.CancelMovement; }


        return flags;
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        PackFlags flags = PackFlags.None;
        // 직렬화 , 쓰기작업
        if (!serializer.IsReader)
        {
            // 유효한 값을 판별하여 열거형으로 표현
            flags = GetPackFlags();
        }

        serializer.SerializeValue(ref ActionID);
        serializer.SerializeValue(ref flags);

        // 역직렬화 , 읽기작업
        if (serializer.IsReader)
        {
            ShouldQueue = (flags & PackFlags.ShouldQueue) != 0;
            CancelMovement = (flags & PackFlags.CancelMovement) != 0;
            ShouldClose = (flags & PackFlags.ShouldClose) != 0;
        }

        if ((flags & PackFlags.HasPosition) != 0)
        {
            serializer.SerializeValue(ref Position);
        }
        if ((flags & PackFlags.HasDirection) != 0)
        {
            serializer.SerializeValue(ref Direction);
        }
        if ((flags & PackFlags.HasTargetIds) != 0)
        {
            serializer.SerializeValue(ref TargetIds);
        }
        if ((flags & PackFlags.HasAmount) != 0)
        {
            serializer.SerializeValue(ref Amount);
        }
    }
}

댓글남기기