유니티 Flags 속성
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);
}
}
}
댓글남기기