DEV Community

Kevin Nox Yuan
Kevin Nox Yuan

Posted on

Practical Bitwise Operations and Bitmasks in Unity

I recently ran into an interviewer who couldn’t understand why skills and buff systems use bitmasks. I tried explaining 2<<1=4 and 4<<1=8, brought up notification dots and Unity’s LayerMask as examples—still didn’t land. I lost my temper and then got told I was “hard to communicate with,” lol. Anyway, here’s a primer to jog memories and spark ideas on practical ways to use bitmasks—consider it both a refresher and an expansion.

Practical Bitwise Operations and Bitmasks in Unity

Bitwise basics

Core bitwise operators

using UnityEngine;

public class BitOperationBasics : MonoBehaviour
{
    void Start()
    {
        // Basic bitwise operations
        int a = 5;  // binary: 0101
        int b = 3;  // binary: 0011

        // Bitwise AND (&) - result bit is 1 only if both bits are 1
        int and = a & b;  // 0001 = 1

        // Bitwise OR (|) - result bit is 1 if any bit is 1
        int or = a | b;   // 0111 = 7

        // Bitwise XOR (^) - result bit is 1 if bits differ
        int xor = a ^ b;  // 0110 = 6

        // Bitwise NOT (~) - flips bits: 0 -> 1, 1 -> 0
        int not = ~a;     // 11111010 = -6 (two's complement)

        // Left shift (<<) - shift left, fill with 0 on the right
        int leftShift = a << 2;  // 010100 = 20

        // Right shift (>>) - shift right, sign-extend on the left
        int rightShift = a >> 1; // 0010 = 2

        Debug.Log($"AND: {and}, OR: {or}, XOR: {xor}");
        Debug.Log($"NOT: {not}, LEFT: {leftShift}, RIGHT: {rightShift}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Handy bit utilities

public static class BitUtils
{
    /// <summary>
    /// Set bit i to 1.
    /// Use cases: enable a feature, unlock content, set a status.
    /// </summary>
    public static uint SetBit(uint flags, int i)
    {
        return flags |= (1u << i);
    }

    /// <summary>
    /// Clear bit i to 0.
    /// Use cases: disable a feature, lock content, remove a status.
    /// </summary>
    public static uint ClearBit(uint flags, int i)
    {
        return flags &= ~(1u << i);
    }

    /// <summary>
    /// Test if bit i is 1.
    /// Use cases: status checks, permission checks, branching.
    /// </summary>
    public static bool TestBit(uint flags, int i)
    {
        return (flags & (1u << i)) != 0;
    }

    /// <summary>
    /// Toggle bit i.
    /// Use cases: on/off toggles, state inversion, UI interactions.
    /// </summary>
    public static uint ToggleBit(uint flags, int i)
    {
        return flags ^= (1u << i);
    }

    /// <summary>
    /// Clear the lowest set bit (rightmost 1).
    /// Use cases: process active flags one-by-one, priority queues, resource allocation.
    /// </summary>
    public static uint ClearLowestSetBit(uint x)
    {
        return x &= x - 1;
    }

    /// <summary>
    /// Isolate the lowest set bit (rightmost 1).
    /// Use cases: find the next item to process, bit scan, priority handling.
    /// </summary>
    public static uint IsolateLowestSetBit(uint x)
    {
        return x & (uint)-(int)x;
    }

    /// <summary>
    /// Count number of set bits (population count).
    /// Use cases: count actives, resource usage, degree of fulfillment.
    /// </summary>
    public static int CountSetBits(uint x)
    {
        int count = 0;
        while (x != 0)
        {
            x = ClearLowestSetBit(x);
            count++;
        }
        return count;
    }

    /// <summary>
    /// Find index of the lowest set bit.
    /// Use cases: find the first matching index.
    /// </summary>
    public static int FindLowestSetBitIndex(uint x)
    {
        if (x == 0) return -1;

        int index = 0;
        uint isolated = IsolateLowestSetBit(x);
        while (isolated > 1)
        {
            isolated >>= 1;
            index++;
        }
        return index;
    }
}
Enter fullscreen mode Exit fullscreen mode

What is a bitmask?

A bitmask uses bitwise operations to store and manipulate multiple boolean states within a single integer. Packing dozens of booleans into one integer can significantly reduce memory and improve performance.

// Traditional approach - uses many bytes
public class TraditionalFlags
{
    public bool canJump;
    public bool canFly;
    public bool canSwim;
    public bool canClimb;
    public bool isInvincible;
    public bool isVisible;
    // ... more flags
}

// Bitmask approach - a single int (4 bytes)
[System.Flags]
public enum PlayerAbilities
{
    None = 0,
    CanJump = 1 << 0,      // 0001
    CanFly = 1 << 1,       // 0010
    CanSwim = 1 << 2,      // 0100
    CanClimb = 1 << 3,     // 1000
    IsInvincible = 1 << 4, // 10000
    IsVisible = 1 << 5,    // 100000
    // Composites
    BasicMovement = CanJump | CanSwim,
    AllAbilities = CanJump | CanFly | CanSwim | CanClimb | IsInvincible | IsVisible
}
Enter fullscreen mode Exit fullscreen mode

Common Unity use cases

LayerMask

LayerMask is a textbook bitmask in Unity, used to filter what layers are affected by raycasts, overlaps, collisions, etc.

using UnityEngine;

public class LayerMaskExample : MonoBehaviour
{
    [SerializeField] private LayerMask groundLayers;
    [SerializeField] private LayerMask enemyLayers;
    [SerializeField] private LayerMask interactableLayers;

    void Update()
    {
        // Raycast against specific layers
        if (Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit, 10f, groundLayers))
        {
            Debug.Log("Hit ground: " + hit.collider.name);
        }

        // Sphere overlap against multiple layers
        Collider[] enemies = Physics.OverlapSphere(transform.position, 5f, enemyLayers);
        foreach (var enemy in enemies)
        {
            Debug.Log("Found enemy: " + enemy.name);
        }
    }

    void Start()
    {
        // Build a LayerMask manually
        LayerMask customMask = 0;
        customMask |= 1 << LayerMask.NameToLayer("Ground");
        customMask |= 1 << LayerMask.NameToLayer("Platform");

        // Check whether a layer is included
        bool isGroundIncluded = (groundLayers & (1 << LayerMask.NameToLayer("Ground"))) != 0;
        Debug.Log("Ground included: " + isGroundIncluded);

        // Remove a layer from the mask
        groundLayers &= ~(1 << LayerMask.NameToLayer("Water"));

        // Add a layer to the mask
        groundLayers |= 1 << LayerMask.NameToLayer("Ice");
    }
}
Enter fullscreen mode Exit fullscreen mode

Notification dots (red-dot badges)

Notification-dot systems are a very common UI pattern in games. Bitwise operations make it efficient to manage complex badge states.

using UnityEngine;
using System.Collections.Generic;

[System.Flags]
public enum RedDotType : uint
{
    None = 0,

    // Main menu (bits 0–7)
    MainMenu = 1u << 0,
    Store = 1u << 1,
    Mail = 1u << 2,
    Achievement = 1u << 3,

    // Inventory (bits 8–15)
    Inventory = 1u << 8,
    Equipment = 1u << 9,
    Consumables = 1u << 10,

    // Quests (bits 16–23)
    DailyQuest = 1u << 16,
    MainQuest = 1u << 17,
    SideQuest = 1u << 18,

    // Social (bits 24–31)
    Friends = 1u << 24,
    Guild = 1u << 25,
    Chat = 1u << 26,

    // Grouped badges
    AllQuests = DailyQuest | MainQuest | SideQuest,
    AllInventory = Inventory | Equipment | Consumables,
    AllSocial = Friends | Guild | Chat
}

public class RedDotManager : MonoBehaviour
{
    private static RedDotManager instance;
    public static RedDotManager Instance => instance;

    private uint redDotFlags = 0;
    private Dictionary<RedDotType, List<System.Action<bool>>> redDotCallbacks 
        = new Dictionary<RedDotType, List<System.Action<bool>>>();

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    /// <summary>
    /// Set a badge state.
    /// </summary>
    public void SetRedDot(RedDotType type, bool show)
    {
        bool wasSet = HasRedDot(type);

        if (show)
        {
            redDotFlags = BitOperations.SetBit(redDotFlags, GetBitIndex(type));
        }
        else
        {
            redDotFlags = BitOperations.ClearBit(redDotFlags, GetBitIndex(type));
        }

        // Only fire callbacks if the state actually changed
        if (wasSet != show)
        {
            TriggerRedDotCallback(type, show);
            UpdateParentRedDots(type);
        }
    }

    /// <summary>
    /// Check if a badge is present.
    /// </summary>
    public bool HasRedDot(RedDotType type)
    {
        return (redDotFlags & (uint)type) != 0;
    }

    /// <summary>
    /// Toggle a badge state.
    /// </summary>
    public void ToggleRedDot(RedDotType type)
    {
        SetRedDot(type, !HasRedDot(type));
    }

    /// <summary>
    /// Clear all badges of given types.
    /// </summary>
    public void ClearRedDots(RedDotType types)
    {
        var originalFlags = redDotFlags;
        redDotFlags &= ~(uint)types;

        // Determine which badges were cleared
        var clearedFlags = originalFlags & ~redDotFlags;
        NotifyChangedRedDots(clearedFlags, false);
    }

    /// <summary>
    /// Get the next badge to process (lowest set bit).
    /// Use case: auto-navigation to the next feature with a notification.
    /// </summary>
    public RedDotType GetNextRedDot()
    {
        if (redDotFlags == 0) return RedDotType.None;

        uint lowestBit = BitOperations.IsolateLowestSetBit(redDotFlags);
        return (RedDotType)lowestBit;
    }

    /// <summary    /// <summary>
    /// Process badges one by one (auto-clear after processing)
    /// Use case: batch-processing notifications.
    /// </summary>
    public RedDotType ProcessNextRedDot()
    {
        var nextRedDot = GetNextRedDot();
        if (nextRedDot != RedDotType.None)
        {
            SetRedDot(nextRedDot, false);
        }
        return nextRedDot;
    }

    /// <summary>
    /// Get number of active badges.
    /// </summary>
    public int GetRedDotCount()
    {
        return BitOperations.CountSetBits(redDotFlags);
    }

    /// <summary>
    /// Register a callback for badge state changes.
    /// </summary>
    public void RegisterRedDotCallback(RedDotType type, System.Action<bool> callback)
    {
        if (!redDotCallbacks.ContainsKey(type))
        {
            redDotCallbacks[type] = new List<System.Action<bool>>();
        }
        redDotCallbacks[type].Add(callback);

        // Fire current state immediately
        callback?.Invoke(HasRedDot(type));
    }

    /// <summary>
    /// Unregister a badge callback.
    /// </summary>
    public void UnregisterRedDotCallback(RedDotType type, System.Action<bool> callback)
    {
        if (redDotCallbacks.ContainsKey(type))
        {
            redDotCallbacks[type].Remove(callback);
        }
    }

    private int GetBitIndex(RedDotType type)
    {
        return BitOperations.FindLowestSetBitIndex((uint)type);
    }

    private void TriggerRedDotCallback(RedDotType type, bool show)
    {
        if (redDotCallbacks.ContainsKey(type))
        {
            foreach (var callback in redDotCallbacks[type])
            {
                callback?.Invoke(show);
            }
        }
    }

    private void UpdateParentRedDots(RedDotType changedType)
    {
        // Update parent/group badges
        if (IsQuestRedDot(changedType))
        {
            UpdateGroupRedDot(RedDotType.AllQuests);
        }
        else if (IsInventoryRedDot(changedType))
        {
            UpdateGroupRedDot(RedDotType.AllInventory);
        }
        else if (IsSocialRedDot(changedType))
        {
            UpdateGroupRedDot(RedDotType.AllSocial);
        }
    }

    private void UpdateGroupRedDot(RedDotType groupType)
    {
        bool hasAnyChildRedDot = HasRedDot(groupType);
        TriggerRedDotCallback(groupType, hasAnyChildRedDot);
    }

    private bool IsQuestRedDot(RedDotType type)
    {
        return (type & RedDotType.AllQuests) != 0;
    }

    private bool IsInventoryRedDot(RedDotType type)
    {
        return (type & RedDotType.AllInventory) != 0;
    }

    private bool IsSocialRedDot(RedDotType type)
    {
        return (type & RedDotType.AllSocial) != 0;
    }

    private void NotifyChangedRedDots(uint changedFlags, bool newState)
    {
        while (changedFlags != 0)
        {
            uint lowestBit = BitOperations.IsolateLowestSetBit(changedFlags);
            var redDotType = (RedDotType)lowestBit;
            TriggerRedDotCallback(redDotType, newState);
            changedFlags = BitOperations.ClearLowestSetBit(changedFlags);
        }
    }
}

// UI badge component
public class RedDotUI : MonoBehaviour
{
    [SerializeField] private RedDotType redDotType;
    [SerializeField] private GameObject redDotIcon;

    void Start()
    {
        RedDotManager.Instance.RegisterRedDotCallback(redDotType, OnRedDotChanged);
    }

    void OnDestroy()
    {
        if (RedDotManager.Instance != null)
        {
            RedDotManager.Instance.UnregisterRedDotCallback(redDotType, OnRedDotChanged);
        }
    }

    private void OnRedDotChanged(bool hasRedDot)
    {
        redDotIcon.SetActive(hasRedDot);
    }
}

// Usage example
public class RedDotExample : MonoBehaviour
{
    void Start()
    {
        var redDotManager = RedDotManager.Instance;

        // Set badges
        redDotManager.SetRedDot(RedDotType.Mail, true);
        redDotManager.SetRedDot(RedDotType.DailyQuest, true);

        // Check badges
        Debug.Log($"Mail badge: {redDotManager.HasRedDot(RedDotType.Mail)}");
        Debug.Log($"Quest badges: {redDotManager.HasRedDot(RedDotType.AllQuests)}");

        // Process badges one by one
        while (redDotManager.GetRedDotCount() > 0)
        {
            var nextRedDot = redDotManager.ProcessNextRedDot();
            Debug.Log($"Processed badge: {nextRedDot}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Skill system

Bitwise operations help manage skill states, cooldowns, and condition checks.

[System.Flags]
public enum SkillState : uint
{
    None = 0,
    Learned = 1u << 0,        // learned
    OnCooldown = 1u << 1,     // on cooldown
    Channeling = 1u << 2,     // channeling
    Disabled = 1u << 3,       // disabled
    Enhanced = 1u << 4,       // enhanced
    Silenced = 1u << 5,       // silenced

    // Composite states
    Unusable = OnCooldown | Disabled | Silenced,
    Active = Learned & ~Unusable
}

[System.Flags]
public enum SkillType : uint
{
    None = 0,
    Physical = 1u << 0,       // physical
    Magical = 1u << 1,        // magical
    Defensive = 1u << 2,      // defensive
    Healing = 1u << 3,        // healing
    Buff = 1u << 4,           // buff
    Debuff = 1u << 5,         // debuff
    Ultimate = 1u << 6,       // ultimate
    Passive = 1u << 7,        // passive

    // Composite types
    AllCombat = Physical | Magical,
    AllSupport = Defensive | Healing | Buff,
    AllActive = ~Passive
}

public class SkillManager : MonoBehaviour
{
    [System.Serializable]
    public struct SkillInfo
    {
        public int skillId;
        public SkillType type;
        public uint stateFlags;
        public float cooldownTime;
        public float lastUseTime;
    }

    private Dictionary<int, SkillInfo> skills = new Dictionary<int, SkillInfo>();
    private uint globalSkillDisable = 0; // global skill-type disable flags

    /// <summary>
    /// Learn a skill.
    /// </summary>
    public void LearnSkill(int skillId, SkillType type)
    {
        if (skills.ContainsKey(skillId))
        {
            var skill = skills[skillId];
            skill.stateFlags = BitOperations.SetBit(skill.stateFlags, 0); // set Learned
            skills[skillId] = skill;
        }
        else
        {
            skills[skillId] = new SkillInfo
            {
                skillId = skillId,
                type = type,
                stateFlags = (uint)SkillState.Learned
            };
        }
    }

    /// <summary>
    /// Check if a skill can be used.
    /// </summary>
    public bool CanUseSkill(int skillId)
    {
        if (!skills.ContainsKey(skillId)) return false;

        var skill = skills[skillId];

        // Check state
        if ((skill.stateFlags & (uint)SkillState.Unusable) != 0) return false;

        // Check global disable by type
        if ((globalSkillDisable & (uint)skill.type) != 0) return false;

        // Check cooldown
        if (Time.time - skill.lastUseTime < skill.cooldownTime) return false;

        return BitOperations.TestBit(skill.stateFlags, 0); // must be learned
    }

    /// <summary>
    /// Use a skill.
    /// </summary>
    public bool UseSkill(int skillId)
    {
        if (!CanUseSkill(skillId)) return false;

        var skill = skills[skillId];
        skill.lastUseTime = Time.time;
        skill.stateFlags = BitOperations.SetBit(skill.stateFlags, 1); // set OnCooldown
        skills[skillId] = skill;

        return true;
    }

    /// <summary>
    /// Disable specific skill types.
    /// Use cases: silence effects, special restrictions.
    /// </summary>
    public void DisableSkillTypes(SkillType types)
    {
        globalSkillDisable |= (uint)types;
    }

    /// <summary>
    /// Enable specific skill types.
    /// </summary>
    public void EnableSkillTypes(SkillType types)
    {
        globalSkillDisable &= ~(uint)types;
    }

    /// <summary>
    /// Get the next available skill.
    /// Iterates using bitwise filters.
    /// </summary>
    public int GetNextAvailableSkill(SkillType typeFilter = SkillType.AllActive)
    {
        uint availableTypes = (uint)typeFilter & ~globalSkillDisable;

        foreach (var skill in skills.Values)
        {
            if ((availableTypes & (uint)skill.type) != 0 && CanUseSkill(skill.skillId))
            {
                return skill.skillId;
            }
        }
        return -1;
    }
}
Enter fullscreen mode Exit fullscreen mode

Inventory slot management

public class InventoryManager : MonoBehaviour
{
    private const int MAX_SLOTS = 64;
    private ulong occupiedSlots = 0; // 64-bit flags for 64 slots

    /// <summary>
    /// Find the first empty slot.
    /// Uses "isolate lowest set bit" logic.
    /// </summary>
    public int FindFirstEmptySlot()
    {
        ulong emptySlots = ~occupiedSlots;
        if (emptySlots == 0) return -1;

        return BitOperations.FindLowestSetBitIndex((uint)(emptySlots & 0xFFFFFFFF));
    }

    /// <summary>
    /// Occupy a slot.
    /// </summary>
    public bool OccupySlot(int slotIndex)
    {
        if (slotIndex < 0 || slotIndex >= MAX_SLOTS) return false;
        if (IsSlotOccupied(slotIndex)) return false;

        occupiedSlots |= (1ul << slotIndex);
        return true;
    }

    /// <summary>
    /// Free a slot.
    /// </summary>
    public void FreeSlot(int slotIndex)
    {
        if (slotIndex >= 0 && slotIndex < MAX_SLOTS)
        {
            occupiedSlots &= ~(1ul << slotIndex);
        }
    }

    /// <summary>
    /// Check if a slot is occupied.
    /// </summary>
    public bool IsSlotOccupied(int slotIndex)
    {
        return (occupiedSlots & (1ul << slotIndex)) != 0;
    }

    /// <summary>
    /// Get the number of free slots.
    /// </summary>
    public int GetFreeSlotCount()
    {
        return MAX_SLOTS - BitOperations.CountSetBits((uint)occupiedSlots) - BitOperations.CountSetBits((uint)(occupiedSlots >> 32));
    }
}
Enter fullscreen mode Exit fullscreen mode

State flag management

Managing many object states with bitmasks is more efficient than scattered booleans.

using UnityEngine;

[System.Flags]
public enum CreatureState
{
    None = 0,
    Alive = 1 << 0,        // alive
    Poisoned = 1 << 1,     // poisoned
    Frozen = 1 << 2,       // frozen
    Burning = 1 << 3,      // burning
    Stunned = 1 << 4,      // stunned
    Invisible = 1 << 5,    // invisible
    Flying = 1 << 6,       // flying
    Invincible = 1 << 7,   // invincible

    // Composite states
    Disabled = Frozen | Stunned,
    ElementalDamage = Poisoned | Burning,
    CantMove = Frozen | Stunned,
    CantBeHurt = Invisible | Invincible
}

public class CreatureStateManager : MonoBehaviour
{
    [SerializeField] private CreatureState currentState = CreatureState.Alive;

    // Add a state
    public void AddState(CreatureState state)
    {
        currentState |= state;
        OnStateChanged();
    }

    //    // Remove a state
    public void RemoveState(CreatureState state)
    {
        currentState &= ~state;
        OnStateChanged();
    }

    // Toggle a state
    public void ToggleState(CreatureState state)
    {
        currentState ^= state;
        OnStateChanged();
    }

    // Check if a specific state is set
    public bool HasState(CreatureState state)
    {
        return (currentState & state) != 0;
    }

    // Check if any of the given states are set
    public bool HasAnyState(CreatureState states)
    {
        return (currentState & states) != 0;
    }

    // Check if all given states are set
    public bool HasAllStates(CreatureState states)
    {
        return (currentState & states) == states;
    }

    // Clear all states
    public void ClearAllStates()
    {
        currentState = CreatureState.None;
        OnStateChanged();
    }

    private void OnStateChanged()
    {
        // Update gameplay logic based on state changes
        UpdateMovement();
        UpdateVisual();
        UpdateDamage();
    }

    private void UpdateMovement()
    {
        bool canMove = !HasAnyState(CreatureState.CantMove);
        GetComponent<Rigidbody>().isKinematic = !canMove;
    }

    private void UpdateVisual()
    {
        var renderer = GetComponent<Renderer>();

        if (HasState(CreatureState.Invisible))
        {
            renderer.material.color = new Color(1, 1, 1, 0.3f);
        }
        else if (HasState(CreatureState.Burning))
        {
            renderer.material.color = Color.red;
        }
        else if (HasState(CreatureState.Frozen))
        {
            renderer.material.color = Color.cyan;
        }
        else
        {
            renderer.material.color = Color.white;
        }
    }

    private void UpdateDamage()
    {
        if (HasAnyState(CreatureState.CantBeHurt))
        {
            // Set to an invincible layer
            gameObject.layer = LayerMask.NameToLayer("Invincible");
        }
        else
        {
            gameObject.layer = LayerMask.NameToLayer("Damageable");
        }
    }

    void Update()
    {
        // Example: damage over time
        if (HasState(CreatureState.Poisoned))
        {
            TakeDamage(1 * Time.deltaTime);
        }

        if (HasState(CreatureState.Burning))
        {
            TakeDamage(2 * Time.deltaTime);
        }
    }

    private void TakeDamage(float damage)
    {
        Debug.Log($"Took damage: {damage}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Permission system

Bitmasks are great for permission checks—fast and compact.

[System.Flags]
public enum UserPermissions
{
    None = 0,
    Read = 1 << 0,          // read
    Write = 1 << 1,         // write
    Execute = 1 << 2,       // execute
    Delete = 1 << 3,        // delete
    Admin = 1 << 4,         // admin
    Moderator = 1 << 5,     // moderator

    // Composite roles
    BasicUser = Read,
    PowerUser = Read | Write | Execute,
    SuperAdmin = Read | Write | Execute | Delete | Admin,
    FullControl = ~0         // all permissions
}

public class PermissionSystem : MonoBehaviour
{
    [SerializeField] private UserPermissions userPermissions;

    public bool HasPermission(UserPermissions permission)
    {
        return (userPermissions & permission) == permission;
    }

    public void GrantPermission(UserPermissions permission)
    {
        userPermissions |= permission;
        Debug.Log($"Granted: {permission}");
    }

    public void RevokePermission(UserPermissions permission)
    {
        userPermissions &= ~permission;
        Debug.Log($"Revoked: {permission}");
    }

    public bool TryExecuteAction(UserPermissions requiredPermission, System.Action action)
    {
        if (HasPermission(requiredPermission))
        {
            action?.Invoke();
            return true;
        }
        else
        {
            Debug.LogWarning($"Insufficient permission, required: {requiredPermission}");
            return false;
        }
    }

    void Start()
    {
        // Examples
        TryExecuteAction(UserPermissions.Read, () => Debug.Log("Read file"));
        TryExecuteAction(UserPermissions.Delete, () => Debug.Log("Delete file"));

        // Check combined permissions
        if (HasPermission(UserPermissions.Read | UserPermissions.Write))
        {
            Debug.Log("Read and write permitted");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

以下为英文翻译(保留结构与代码,代码中的注释与日志文本已翻译为英文):

Event Flag System

Use bitmasks to manage event subscriptions and triggers in the game.

[System.Flags]
public enum GameEvents
{
    None = 0,
    PlayerDied = 1 << 0,
    EnemyKilled = 1 << 1,
    ItemCollected = 1 << 2,
    LevelCompleted = 1 << 3,
    BossDefeated = 1 << 4,
    PowerUpActivated = 1 << 5,

    // Event combinations
    CombatEvents = EnemyKilled | BossDefeated,
    ProgressEvents = LevelCompleted | ItemCollected,
    AllEvents = ~0
}

public class EventManager : MonoBehaviour
{
    private static EventManager instance;
    public static EventManager Instance => instance;

    private System.Collections.Generic.Dictionary<GameEvents, System.Action> eventHandlers
        = new System.Collections.Generic.Dictionary<GameEvents, System.Action>();

    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void Subscribe(GameEvents events, System.Action handler)
    {
        foreach (GameEvents eventType in System.Enum.GetValues(typeof(GameEvents)))
        {
            if (eventType != GameEvents.None && (events & eventType) != 0)
            {
                if (!eventHandlers.ContainsKey(eventType))
                {
                    eventHandlers[eventType] = null;
                }
                eventHandlers[eventType] += handler;
            }
        }
    }

    public void Unsubscribe(GameEvents events, System.Action handler)
    {
        foreach (GameEvents eventType in System.Enum.GetValues(typeof(GameEvents)))
        {
            if (eventType != GameEvents.None && (events & eventType) != 0)
            {
                if (eventHandlers.ContainsKey(eventType))
                {
                    eventHandlers[eventType] -= handler;
                }
            }
        }
    }

    public void TriggerEvent(GameEvents eventType)
    {
        if (eventHandlers.ContainsKey(eventType))
        {
            eventHandlers[eventType]?.Invoke();
        }
    }

    // Trigger multiple events
    public void TriggerEvents(GameEvents events)
    {
        foreach (GameEvents eventType in System.Enum.GetValues(typeof(GameEvents)))
        {
            if (eventType != GameEvents.None && (events & eventType) != 0)
            {
                TriggerEvent(eventType);
            }
        }
    }
}

// Usage example
public class GameEventListener : MonoBehaviour
{
    void Start()
    {
        // Subscribe to combat-related events
        EventManager.Instance.Subscribe(GameEvents.CombatEvents, OnCombatEvent);

        // Subscribe to a specific event
        EventManager.Instance.Subscribe(GameEvents.ItemCollected, OnItemCollected);
    }

    void OnDestroy()
    {
        if (EventManager.Instance != null)
        {
            EventManager.Instance.Unsubscribe(GameEvents.CombatEvents, OnCombatEvent);
            EventManager.Instance.Unsubscribe(GameEvents.ItemCollected, OnItemCollected);
        }
    }

    private void OnCombatEvent()
    {
        Debug.Log("Combat event triggered");
    }

    private void OnItemCollected()
    {
        Debug.Log("Item collected event triggered");
    }
}
Enter fullscreen mode Exit fullscreen mode

Optimizing Boolean Collections

Pack multiple related booleans into a single integer to save memory and improve access speed.

public class OptimizedBooleanCollection
{
    private int flags = 0;

    // Set the boolean value at the specified index
    public void SetFlag(int index, bool value)
    {
        if (value)
        {
            flags |= (1 << index);
        }
        else
        {
            flags &= ~(1 << index);
        }
    }

    // Get the boolean value at the specified index
    public bool GetFlag(int index)
    {
        return (flags & (1 << index)) != 0;
    }

    // Toggle the boolean value at the specified index
    public void ToggleFlag(int index)
    {
        flags ^= (1 << index);
    }

    // Clear all flags
    public void ClearAll()
    {
        flags = 0;
    }

    // Set all flags
    public void SetAll()
    {
        flags = ~0;
    }

    // Get the number of flags set to true
    public int GetTrueCount()
    {
        return BitUtils.CountSetBits(flags);
    }

    // Check if any flag is true
    public bool HasAnyTrue()
    {
        return flags != 0;
    }

    // Check if all flags are false
    public bool AllFalse()
    {
        return flags == 0;
    }
}

// Usage example: Level progress management
public class LevelProgressManager : MonoBehaviour
{
    private OptimizedBooleanCollection levelCompleted = new OptimizedBooleanCollection();
    private OptimizedBooleanCollection levelUnlocked = new OptimizedBooleanCollection();

    public void CompleteLevel(int levelIndex)
    {
        levelCompleted.SetFlag(levelIndex, true);

        // Unlock the next level
        if (levelIndex + 1 < 32) // Assume up to 32 levels
        {
            levelUnlocked.SetFlag(levelIndex + 1, true);
        }

        Debug.Log($"Level {levelIndex} completed! Completed levels: {levelCompleted.GetTrueCount()}");
    }

    public bool IsLevelCompleted(int levelIndex)
    {
        return levelCompleted.GetFlag(levelIndex);
    }

    public bool IsLevelUnlocked(int levelIndex)
    {
        return levelUnlocked.GetFlag(levelIndex);
    }

    public float GetProgressPercentage()
    {
        return levelCompleted.GetTrueCount() / 32f * 100f;
    }
}
Enter fullscreen mode Exit fullscreen mode

Collision Detection Optimization

Use bitmasks to optimize collision logic between objects.

[System.Flags]
public enum CollisionCategory
{
    None = 0,
    Player = 1 << 0,
    Enemy = 1 << 1,
    Projectile = 1 << 2,
    Wall = 1 << 3,
    Ground = 1 << 4,
    PowerUp = 1 << 5,
    Trigger = 1 << 6
}

public class CollisionController : MonoBehaviour
{
    [SerializeField] private CollisionCategory myCategory;
    [SerializeField] private CollisionCategory collidesWith;

    private void OnCollisionEnter(Collision collision)
    {
        var otherController = collision.gameObject.GetComponent<CollisionController>();
        if (otherController != null)
        {
            if (CanCollideWith(otherController.myCategory))
            {
                HandleCollision(otherController);
            }
        }
    }

    private bool CanCollideWith(CollisionCategory otherCategory)
    {
        return (collidesWith & otherCategory) != 0;
    }

    private void HandleCollision(CollisionController other)
    {
        Debug.Log($"{myCategory} collided with {other.myCategory}");

        // Execute different logic based on collision type
        if (myCategory == CollisionCategory.Player && other.myCategory == CollisionCategory.PowerUp)
        {
            CollectPowerUp(other.gameObject);
        }
        else if (myCategory == CollisionCategory.Projectile && other.myCategory == CollisionCategory.Enemy)
        {
            DamageEnemy(other.gameObject);
        }
    }

    private void CollectPowerUp(GameObject powerUp)
    {
        Debug.Log("Collect power-up");
        Destroy(powerUp);
    }

    private void DamageEnemy(GameObject enemy)
    {
        Debug.Log("Damage enemy");
        // Damage logic
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Techniques

Bitmask Serialization

Properly serialize bitmask data in Unity.

[System.Serializable]
public class SerializableBitMask
{
    [SerializeField] private int mask;

    public bool this[int index]
    {
        get => (mask & (1 << index)) != 0;
        set
        {
            if (value)
                mask |= (1 << index);
            else
                mask &= ~(1 << index);
        }
    }

    public void SetMask(int newMask) => mask = newMask;
    public int GetMask() => mask;

    public void Clear() => mask = 0;

    public override string ToString()
    {
        return System.Convert.ToString(mask, 2).PadLeft(32, '0');
    }
}

// Custom PropertyDrawer for displaying in the Inspector
#if UNITY_EDITOR
using UnityEditor;

[CustomPropertyDrawer(typeof(SerializableBitMask))]
public class SerializableBitMaskDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var maskProperty = property.FindPropertyRelative("mask");

        EditorGUI.BeginProperty(position, label, property);

        var rect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
        EditorGUI.PropertyField(rect, maskProperty, label);

        rect.y += EditorGUIUtility.singleLineHeight + 2;
        var mask = maskProperty.intValue;
        var binaryString = System.Convert.ToString(mask, 2).PadLeft(16, '0');
        EditorGUI.LabelField(rect, "Binary:", binaryString);

        EditorGUI.EndProperty();
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return EditorGUIUtility.singleLineHeight * 2 + 2;
    }
}
#endif
Enter fullscreen mode Exit fullscreen mode

Dynamic Bitmask Manager

Implement a general-purpose bitmask management system.

public class DynamicBitMaskManager<T> where T : System.Enum
{
    private int mask = 0;

    public void Add(T flag)
    {
        mask |= ConvertToInt(flag);
    }

    public void Remove(T flag)
    {
        mask &= ~ConvertToInt(flag);
    }

    public void Toggle(T flag)
    {
        mask ^= ConvertToInt(flag);
    }

    public bool Has(T flag)
    {
        return (mask & ConvertToInt(flag)) != 0;
    }

    public bool HasAny(params T[] flags)
    {
        foreach (var flag in flags)
        {
            if (Has(flag)) return true;
        }
        return false;
    }

    public bool HasAll(params T[] flags)
    {
        foreach (var flag in flags)
        {
            if (!Has(flag)) return false;
        }
        return true;
    }

    public void Clear()
    {
        mask = 0;
    }

    public T[] GetActiveFlags()
    {
        var activeFlags = new System.Collections.Generic.List<T>();
        foreach (T flag in System.Enum.GetValues(typeof(T)))
        {
            if (Has(flag))
            {
                activeFlags.Add(flag);
            }
        }
        return activeFlags.ToArray();
    }

    private int ConvertToInt(T flag)
    {
        return System.Convert.ToInt32(flag);
    }

    public override string ToString()
    {
        return string.Join(", ", GetActiveFlags());
    }
}

// Usage example
public class DynamicMaskExample : MonoBehaviour
{
    private DynamicBitMaskManager<CreatureState> stateManager;

    void Start()
    {
        stateManager = new DynamicBitMaskManager<CreatureState>();

        // Add states
        stateManager.Add(CreatureState.Alive);
        stateManager.Add(CreatureState.Flying);

        // Check states
        Debug.Log("Alive: " + stateManager.Has(CreatureState.Alive));
        Debug.Log("Current states: " + stateManager.ToString());

        // Toggle state
        stateManager.Toggle(CreatureState.Invisible);
        Debug.Log("After toggling invisibility: " + stateManager.ToString());
    }
}
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

Bitwise Operation Performance Tests

using UnityEngine;
using System.Diagnostics;

public class BitOperationPerformanceTest : MonoBehaviour
{
    private const int ITERATIONS = 1000000;

    void Start()
    {
        TestBooleanArrayVsBitMask();
        TestFlagEnumPerformance();
    }

    void TestBooleanArrayVsBitMask()
    {
        // Test boolean array
        bool[] boolArray = new bool[32];
        var sw = Stopwatch.StartNew();

        for (int i = 0; i < ITERATIONS; i++)
        {
            boolArray[i % 32] = !boolArray[i % 32];
        }

        sw.Stop();
        long boolArrayTime = sw.ElapsedMilliseconds;

        // Test bitmask
        int bitMask = 0;
        sw.Restart();

        for (int i = 0; i < ITERATIONS; i++)
        {
            bitMask ^= (1 << (i % 32));
        }

        sw.Stop();
        long bitMaskTime = sw.ElapsedMilliseconds;

        UnityEngine.Debug.Log($"Boolean array elapsed: {boolArrayTime}ms");
        UnityEngine.Debug.Log($"Bitmask elapsed: {bitMaskTime}ms");
        UnityEngine.Debug.Log($"Speedup: {(float)boolArrayTime / bitMaskTime:F2}x");
    }

    void TestFlagEnumPerformance()
    {
        CreatureState state = CreatureState.Alive;
        var sw = Stopwatch.StartNew();

        for (int i = 0; i < ITERATIONS; i++)
        {
            // Add state
            state |= CreatureState.Flying;
            // Check state
            bool hasFlying = (state & CreatureState.Flying) != 0;
            // Remove state
            state &= ~CreatureState.Flying;
        }

        sw.Stop();
        UnityEngine.Debug.Log($"Flag enum operations elapsed: {sw.ElapsedMilliseconds}ms");
    }
}
Enter fullscreen mode Exit fullscreen mode

Bitwise Operation Benchmark

public class BitOperationBenchmark : MonoBehaviour
{
    void Start()
    {
        const int iterations = 1000000;

        // Test red dot system performance
        var sw = System.Diagnostics.Stopwatch.StartNew();

        uint redDots = 0;
        for (int i = 0; i < iterations; i++)
        {
            // Set bit
            redDots = BitOperations.SetBit(redDots, i % 32);
            // Test bit
            bool hasRedDot = BitOperations.TestBit(redDots, i % 32);
            // Clear bit
            if (i % 10 == 0) redDots = BitOperations.ClearBit(redDots, i % 32);
        }

        sw.Stop();
        Debug.Log($"Bitwise red dot system {iterations} operations took: {sw.ElapsedMilliseconds}ms");

        // Compare with traditional Dictionary approach
        var redDotDict = new Dictionary<int, bool>();
        sw.Restart();

        for (int i = 0; i < iterations; i++)
        {
            redDotDict[i % 32] = true;
            bool hasRedDot = redDotDict.ContainsKey(i % 32) && redDotDict[i % 32];
            if (i % 10 == 0) redDotDict[i % 32] = false;
        }

        sw.Stop();
        Debug.Log($"Dictionary red dot system {iterations} operations took: {sw.ElapsedMilliseconds}ms");
    }
}
Enter fullscreen mode Exit fullscreen mode

Unity Bitwise Memory Optimization: Complete Example

Memory Optimization Scenario Examples

Map Region Management System

In large open-world games, you need to manage the state of tens of thousands of map regions (explored, has enemies, has treasure, etc.).

using UnityEngine;
using System.Collections.Generic;
using System;

[System.Flags]
public enum MapRegionState : byte
{
    None = 0,
    Explored = 1 << 0,      // Explored
    HasEnemies = 1 << 1,    // Has enemies
    HasTreasure = 1 << 2,   // Has treasure
    HasNPC = 1 << 3,        // Has NPC
    IsLocked = 1 << 4,      // Region locked
    IsDangerous = 1 << 5,   // Dangerous area
    HasQuest = 1 << 6,      // Has quest
    IsBossArea = 1 << 7     // Boss area
}

// Traditional approach - high memory usage
public class TraditionalMapManager : MonoBehaviour
{
    [System.Serializable]
    public class RegionData
    {
        public bool explored;
        public bool hasEnemies;
        public bool hasTreasure;
        public bool hasNPC;
        public bool isLocked;
        public bool isDangerous;
        public bool hasQuest;
        public bool isBossArea;
        // Each region needs 8 bytes (8 bools)
    }

    private Dictionary<int, RegionData> regions = new Dictionary<int, RegionData>();
    // 10,000 regions = 10,000 * (8 bytes + Dictionary overhead) ≈ 240KB+
}

// Optimized approach - using bitwise operations
public class OptimizedMapManager : MonoBehaviour
{
    private Dictionary<int, byte> regionStates = new Dictionary<int, byte>();
    // 10,000 regions = 10,000 * 1 byte ≈ 10KB (95%+ memory savings)

    private Dictionary<Vector2Int, int> positionToRegionId = new Dictionary<Vector2Int, int>();
    private Dictionary<int, Vector2Int> regionIdToPosition = new Dictionary<int, Vector2Int>();

    /// <summary>
    /// Set region state
    /// </summary>
    public void SetRegionState(int regionId, MapRegionState state, bool value)
    {
        if (!regionStates.ContainsKey(regionId))
        {
            regionStates[regionId] = 0;
        }

        byte currentState = regionStates[regionId];
        if (value)
        {
            currentState |= (byte)state;
        }
        else
        {
            currentState &= (byte)~state;
        }
        regionStates[regionId] = currentState;
    }

    /// <summary>
    /// Check region state
    /// </summary>
    public bool HasRegionState(int regionId, MapRegionState state)
    {
        if (!regionStates.ContainsKey(regionId)) return false;
        return (regionStates[regionId] & (byte)state) != 0;
    }

    /// <summary>
    /// Get all region states
    /// </summary>
    public MapRegionState GetAllRegionStates(int regionId)
    {
        if (!regionStates.ContainsKey(regionId)) return MapRegionState.None;
        return (MapRegionState)regionStates[regionId];
    }

    /// <summary>
    /// Set multiple states in batch
    /// </summary>
    public void SetMultipleStates(int regionId, MapRegionState states)
    {
        regionStates[regionId] = (byte)states;
    }

    /// <summary>
    /// Check if any of the specified states are present
    /// </summary>
    public bool HasAnyState(int regionId, MapRegionState statesMask)
    {
        if (!regionStates.ContainsKey(regionId)) return false;
        return (regionStates[regionId] & (byte)statesMask) != 0;
    }

    /// <summary>
    /// Find all regions with a specific state
    /// </summary>
    public List<int> FindRegionsWithState(MapRegionState state)
    {
        var result = new List<int>();
        byte stateByte = (byte)state;

        foreach (var kvp in regionStates)
        {
            if ((kvp.Value & stateByte) != 0)
            {
                result.Add(kvp.Key);
            }
        }
        return result;
    }

    /// <summary>
    /// Get the number of regions with a specific state
    /// </summary>
    public int CountRegionsWithState(MapRegionState state)
    {
        int count = 0;
        byte stateByte = (byte)state;

        foreach (var stateValue in regionStates.Values)
        {
            if ((stateValue & stateByte) != 0)
            {
                count++;
            }
        }
        return count;
    }

    /// <summary>
    /// Serialize map data - extremely compact
    /// </summary>
    [System.Serializable]
    public struct CompactMapData
    {
        public int[] regionIds;
        public byte[] states;

        public int GetDataSize()
        {
            return regionIds.Length * 4 + states.Length; // int(4 bytes) + byte(1 byte)
        }
    }

    public CompactMapData SerializeMapData()
    {
        var data = new CompactMapData
        {
            regionIds = new int[regionStates.Count],
            states = new byte[regionStates.Count]
        };

        int index = 0;
        foreach (var kvp in regionStates)
        {
            data.regionIds[index] = kvp.Key;
            data.states[index] = kvp.Value;
            index++;
        }

        return data;
    }

    public void DeserializeMapData(CompactMapData data)
    {
        regionStates.Clear();
        for (int i = 0; i < data.regionIds.Length; i++)
        {
            regionStates[data.regionIds[i]] = data.states[i];
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Achievement System Memory Optimization

[System.Flags]
public enum AchievementCategory : uint
{
    None = 0,
    Combat = 1u << 0,
    Exploration = 1u << 1,
    Social = 1u << 2,
    Collection = 1u << 3,
    Story = 1u << 4,
    Crafting = 1u << 5,
    Trading = 1u << 6,
    PVP = 1u << 7,

    // Combined categories
    AllPVE = Combat | Exploration | Story,
    AllSocial = Social | Trading,
    AllCreative = Crafting | Collection
}

public class CompactAchievementManager : MonoBehaviour
{
    // Traditional approach: one object per achievement with many fields
    // 1000 achievements * ~100 bytes each = 100KB

    // Optimized approach: compact storage
    [System.Serializable]
    public struct AchievementData
    {
        public ushort achievementId;        // 2 bytes - supports up to 65,536 achievements
        public byte progress;               // 1 byte - progress percentage 0-100
        public byte flags;                  // 1 byte - status flags
        // Total only 4 bytes per achievement
    }

    [System.Flags]
    private enum AchievementFlags : byte
    {
        None = 0,
        Unlocked = 1 << 0,      // Unlocked
        Completed = 1 << 1,     // Completed
        Claimed = 1 << 2,       // Reward claimed
        Hidden = 1 << 3,        // Hidden achievement
        Pinned = 1 << 4,        // Pinned
        New = 1 << 5,           // Newly obtained
        Favorite = 1 << 6,      // Favorited
        Shared = 1 << 7         // Shared
    }

    private List<AchievementData> achievements = new List<AchievementData>();
    private Dictionary<ushort, int> achievementIndexMap = new Dictionary<ushort, int>();

    // Category cache - store which achievements each category contains using bits
    private Dictionary<AchievementCategory, uint[]> categoryMasks = new Dictionary<AchievementCategory, uint[]>();

    /// <summary>
    /// Add an achievement
    /// </summary>
    public void AddAchievement(ushort achievementId, AchievementCategory category)
    {
        if (achievementIndexMap.ContainsKey(achievementId)) return;

        var achievementData = new AchievementData
        {
            achievementId = achievementId,
            progress = 0,
            flags = (byte)AchievementFlags.None
        };

        int index = achievements.Count;
        achievements.Add(achievementData);
        achievementIndexMap[achievementId] = index;

        // Update category mask
        UpdateCategoryMask(category, index, true);
    }

    /// <summary>
    /// Set achievement progress
    /// </summary>
    public void SetAchievementProgress(ushort achievementId, byte progress)
    {
        if (!achievementIndexMap.TryGetValue(achievementId, out int index)) return;

        var achievement = achievements[index];
        achievement.progress = progress;

        // Auto-complete check
        if (progress >= 100)
        {
            achievement.flags |= (byte)AchievementFlags.Completed;
            achievement.flags |= (byte)AchievementFlags.New;
        }

        achievements[index] = achievement;
    }

    /// <summary>
    /// Set an achievement flag
    /// </summary>
    public void SetAchievementFlag(ushort achievementId, AchievementFlags flag, bool value)
    {
        if (!achievementIndexMap.TryGetValue(achievementId, out int index)) return;

        var achievement = achievements[index];
        if (value)
        {
            achievement.flags |= (byte)flag;
        }
        else
        {
            achievement.flags &= (byte)~flag;
        }
        achievements[index] = achievement;
    }

    /// <summary>
    /// Check an achievement flag
    /// </summary>
    public bool HasAchievementFlag(ushort achievementId, AchievementFlags flag)
    {
        if (!achievementIndexMap.TryGetValue(achievementId, out int index)) return false;
        return (achievements[index].flags & (byte)flag) != 0;
    }

    /// <summary>
    /// Get the number of completed achievements in a category
    /// </summary>
    public int GetCompletedCountInCategory(AchievementCategory category)
    {
        if (!categoryMasks.TryGetValue(category, out uint[] masks)) return 0;

        int count = 0;
        for (int maskIndex = 0; maskIndex < masks.Length; maskIndex++)
        {
            uint mask = masks[maskIndex];
            uint currentBit = 1;

            for (int bit = 0; bit < 32 && maskIndex * 32 + bit < achievements.Count; bit++)
            {
                if ((mask & currentBit) != 0)
                {
                    int achievementIndex = maskIndex * 32 + bit;
                    if ((achievements[achievementIndex].flags & (byte)AchievementFlags.Completed) != 0)
                    {
                        count++;
                    }
                }
                currentBit <<= 1;
            }
        }
        return count;
    }

    /// <summary>
    /// Get the list of achievements that need a red dot (notification)
    /// </summary>
    public List<ushort> GetAchievementsWithRedDot()
    {
        var result = new List<ushort>();
        byte redDotMask = (byte)(AchievementFlags.New | AchievementFlags.Completed) & ~(byte)AchievementFlags.Claimed;

        for (int i = 0; i < achievements.Count; i++)
        {
            if ((achievements[i].flags & redDotMask) != 0)
            {
                result.Add(achievements[i].achievementId);
            }
        }
        return result;
    }

    /// <summary>
    /// Batch process new achievements
    /// </summary>
    public void ProcessNewAchievements()
    {
        for (int i = 0; i < achievements.Count; i++)
        {
            var achievement = achievements[i];
            if ((achievement.flags & (byte)AchievementFlags.New) != 0)
            {
                // Handle new achievement logic
                Debug.Log($"New Achievement: {achievement.achievementId}");

                // Clear 'New' flag
                achievement.flags &= (byte)~AchievementFlags.New;
                achievements[i] = achievement;
            }
        }
    }

    private void UpdateCategoryMask(AchievementCategory category, int achievementIndex, bool add)
    {
        if (!categoryMasks.ContainsKey(category))
        {
            int maskArraySize = (achievements.Capacity + 31) / 32; // rounded up
            categoryMasks[category] = new uint[maskArraySize];
        }

        uint[] masks = categoryMasks[category];
        int maskIndex = achievementIndex / 32;
        int bitIndex = achievementIndex % 32;

        if (add)
        {
            masks[maskIndex] |= (1u << bitIndex);
        }
        else
        {
            masks[maskIndex] &= ~(1u << bitIndex);
        }
    }

    /// <summary>
    /// Get memory usage stats
    /// </summary>
    public void LogMemoryUsage()
    {
        int achievementDataSize = achievements.Count * 4; // 4 bytes per achievement
        int indexMapSize = achievementIndexMap.Count * 8; // estimated Dictionary overhead
        int categoryMaskSize = 0;

        foreach (var masks in categoryMasks.Values)
        {
            categoryMaskSize += masks.Length * 4; // 4 bytes per uint
        }

        int totalSize = achievementDataSize + indexMapSize + categoryMaskSize;

        Debug.Log($"Achievement system memory usage:");
        Debug.Log($"  Achievement data: {achievementDataSize} bytes ({achievements.Count} achievements)");
        Debug.Log($"  Index map: {indexMapSize} bytes");
        Debug.Log($"  Category masks: {categoryMaskSize} bytes");
        Debug.Log($"  Total: {totalSize} bytes ({totalSize / 1024f:F2} KB)");

        // Compare with traditional approach
        int traditionalSize = achievements.Count * 100; // estimated traditional object size
        float savings = (1f - (float)totalSize / traditionalSize) * 100f;
        Debug.Log($"  Savings compared to traditional approach: {savings:F1}% ({traditionalSize - totalSize} bytes)");
    }

    /// <summary>
    /// Serialize achievement data into a compact format
    /// </summary>
    public byte[] SerializeToBytes()
    {
        var buffer = new byte[achievements.Count * 4];
        int offset = 0;

        foreach (var achievement in achievements)
        {
            // 2 bytes for ID
            buffer[offset] = (byte)(achievement.achievementId & 0xFF);
            buffer[offset + 1] = (byte)(achievement.achievementId >> 8);
            // 1 byte for progress
            buffer[offset + 2] = achievement.progress;
            // 1 byte for flags
            buffer[offset + 3] = achievement.flags;
            offset += 4;
        }

        return buffer;
    }

    /// <summary>
    /// Deserialize from compact format
    /// </summary>
    public void DeserializeFromBytes(byte[] data)
    {
        achievements.Clear();
        achievementIndexMap.Clear();

        for (int i = 0; i < data.Length; i += 4)
        {
            var achievement = new AchievementData
            {
                achievementId = (ushort)(data[i] | (data[i + 1] << 8)),
                progress = data[i + 2],
                flags = data[i + 3]
            };

            int index = achievements.Count;
            achievements.Add(achievement);
            achievementIndexMap[achievement.achievementId] = index;
        }
    }
}

// Usage example and performance test
public class MemoryOptimizationExample : MonoBehaviour
{
    void Start()
    {
        var mapManager = new OptimizedMapManager();
        var achievementManager = new CompactAchievementManager();

        // Create lots of test data
        for (int i = 0; i < 10000; i++)
        {
            // Map regions
            var randomStates = (MapRegionState)(UnityEngine.Random.Range(1, 256));
            mapManager.SetMultipleStates(i, randomStates);

            // Achievements
            if (i < 1000)
            {
                achievementManager.AddAchievement((ushort)i, AchievementCategory.Combat);
                achievementManager.SetAchievementProgress((ushort)i, (byte)UnityEngine.Random.Range(0, 101));
            }
        }

        // Output memory usage stats
        achievementManager.LogMemoryUsage();

        Debug.Log($"Map system: {mapManager.CountRegionsWithState(MapRegionState.Explored)} explored regions");
        Debug.Log($"Achievement system: {achievementManager.GetCompletedCountInCategory(AchievementCategory.Combat)} completed combat achievements");
    }
}
Enter fullscreen mode Exit fullscreen mode

Through these memory optimization examples, we can see the tremendous value of bitwise operations in game development:

  • Map system: from 8 bytes per region down to 1 byte, saving 87.5% memory
  • Achievement system: from ~100 bytes per achievement down to 4 bytes, saving 96% memory
  • Serialization efficiency: more compact data, more efficient transfer and storage
  • Query performance: bitwise operations are faster than accessing object fields

Conclusion

It’s also a reminder to myself to lose my temper less (even if you really run into idiots, just giggle), keep an open mind and strive for excellence, and aim to nail it in the next explanation. Have you encountered any awkward situations during interviews? Feel free to share in the comments!

Top comments (0)