位运算的总结以及在游戏开发中的应用

本文总结了位运算的概念及运算方式,并通过趣味练习加深理解。重点讲述了位运算在Unity游戏开发中的实用案例,如多条件筛选、多状态记录,特别是在奖励系统和智能体状态管理中的高效应用。通过使用位运算,可以简化代码逻辑,提高客户端数据处理效率。
摘要由CSDN通过智能技术生成

定义:

原码: 正数的二进制即为原码,负数的二级制为正数的反码再补码
反码:将二进制数按位取反(10, 01)
补码: 对反码加1
eg : -5
原码:00000000 00000000 00000000 00000101-> 5
反码:11111111 11111111 11111111 11111010
补码:11111111 11111111 11111111 11111011-> - 5

运算:

1.<< 左移运算符:各二进位全部左移若干位,高位丢弃,低位补0
eg : 6 << 2 = 24-> 2 ^ 2 + 2 ^ 1 = 2 ^ 4 + 2 ^ 3-> 2 << n = 2 ^ n
0000 0000 0000 0000 0000 0000 0000 0110-> 6
0000 0000 0000 0000 0000 0000 0001 1000-> 6 << 2 = 24
数学意义:在数字没有溢出的前提下,对于正数和负数,二进制左移n位就相当于乘以2的n次方
2.>> 右移运算符:各二进位全部右移若干位,正数高位补0,负数高位补1,低位丢弃。
eg: 12 >> 2 = 3
0000 0000 0000 0000 0000 0000 0000 1100-> 12
0000 0000 0000 0000 0000 0000 0000 0011-> 12 >> 2 = 3
eg: -12 >> 2 = -3
1111 1111 1111 1111 1111 1111 1111 0100-> - 12
1111 1111 1111 1111 1111 1111 1111 1101-> - 12 >> 2 = -3
数学意义:在数字没有溢出的前提下,对于正数和负数,二进制右移n位就相当于除以2的n次方
3.& 位与:当运算符两边相同位置都是1时,结果返回1,其他情况都返回0。
eg: 3 & 5 = 1
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0001-> 3 & 5 = 1
4.| 位或: 当运算符两边相同位置都是0时,结果返回0,其他情况都返回1。
eg: 3 | 5 = 7
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0111-> 3 | 5 = 7
5.~位非: 将运算符后二进制数反转,0110
eg: ~3 = -4
0000 0000 0000 0000 0000 0000 0000 0011-> 3
1111 1111 1111 1111 1111 1111 1111 1100-> ~3 = -4
6.^ 位异或: 当运算符两边相同位置都是相同,结果返回0,不相同时返回1。
eg: 3 ^ 5 = 6
0000 0000 0000 0000 0000 0000 0000 0011-> 3
0000 0000 0000 0000 0000 0000 0000 0101-> 5
0000 0000 0000 0000 0000 0000 0000 0110-> 3 ^ 5 = 6
Exercise:
判断奇偶
交换两个数
取余
相反数
绝对值
平均数
...

趣味练习:

1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。
现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?
答:
把所有的瓶子按照二进制标号,10个老鼠对应二进制10个位,将所有瓶子位的值为1的液体喂给对应位的老鼠,最后将死亡的老鼠组合起二进制数就可以判断哪一瓶是毒药
0 1 2 3 4 5 6 7 8 9 
0000 0000 0000 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0011
.
.
.
0000 0000 0000 0000 0000 0011 1110 1000

不知道为什么?数学中最简单的解决方法就是先看简单的例子:

假设有8个瓶子,里面只有一瓶是毒药,外观与其他7瓶一致,老鼠吃下毒药后1个小时会死亡,
问要多少只老鼠才能够才一个小时内找出哪一瓶是毒药?具体怎么操作?

一只老鼠有两种状态:1.生存、2.死亡
2 ^ 3 = 8,所以用3只老鼠就可以
操作:
瓶子:
000(0)
001(1)
010(2)
011(3)
100(4)
101(5)
110(6)
111(7)
老鼠:
321
编号1老鼠(1 - 3 - 5 - 7)、编号2老鼠(2 - 3 - 6 - 7),编号3老鼠(4 - 5 - 6 - 7)

假设编号为5101)的瓶子有毒
下面分析结果:
如果1号老鼠死了(老鼠1),则表明毒药存在于编号二进制中第一位为1的瓶子,那么毒瓶子的编号为(XX1),X表示尚未确认。
然后分析第二只老鼠,如果第二只老鼠(老鼠2)没死,那就说明毒药瓶子不存在于编号二进制中第二位为1的瓶子,即毒药编号为(X01)。
最后分析第三只老鼠,如果老鼠3死了,那就表名毒药存在于编号二进制位中第三位为1的的瓶子之中,即编号为(101)。
最后得出结论:编号为101的瓶子有毒。
假如三只老鼠都没有死亡,则表明有毒的瓶子是0号瓶子

游戏开发中的应用:

1.在Unity中内置的应用:


public class LayerOperator : MonoBehaviour
{
    public LayerMask layer;

    public void Open()
    {
        Camera.main.cullingMask |= layer; //开启某层级的渲染
    }

    public void Close()
    {
        Camera.main.cullingMask &= ~layer; //关闭某层级的渲染
    }
}

2.用于多条件的筛选:


public struct MyLayerMask
{
    public int value { get; private set; }
    public string bitStr { get { return System.Convert.ToString(value, 2); } }

    public MyLayerMask(int defaultValue = 0) 
    {
        value = defaultValue;
    }

    public void Add(int value)
    {
        this.value |= value;
    }

    public void Remove(int value)
    {
        this.value &= ~value;
    }

    public bool Contains(int index) 
    {
        return bitStr.Length > index && bitStr[bitStr.Length - 1 - index] == '1';
    }

    public static List<int> GetContainLayer(int value, int enumCount) 
    {
        string bitStr = string.Empty;
        if (value == -1) 
        {
            bitStr = System.Convert.ToString(GetLayerMaxValue(enumCount), 2);
        }
        bitStr = System.Convert.ToString(value, 2);
        List<int> layers = new List<int>();
        for (int i = 0; i < bitStr.Length; i++) 
        {
            if(bitStr[i] == '1') 
            {
                layers.Add((int)Mathf.Pow(2, bitStr.Length - 1 - i));
            }
        }
        return layers;
    }

    public static int GetLayerMaxValue(int count) 
    {
        int value = 0;
        for (int i = 0; i < count; i++) 
        {
            value += (int)Mathf.Pow(2, i);
        }
        return value;
    }
}
public enum NumScreen
{
    能整除3的数 = 1,
    能整除4的数 = 2,
    能整除5的数 = 4,
    能整除6的数 = 8,
}

public class ScreenOperator : MonoBehaviour
{
    private MyLayerMask myLayer = new MyLayerMask(0);

    private void Screen(int index) 
    {
        if (IsPress) //伪码
        {
            myLayer.Remove((int)Mathf.Pow(2, index));
        }
        else 
        {
            myLayer.Add((int)Mathf.Pow(2, index));
        }

        ScreenNum();
    }
	
	private void ScreenNum() 
    {
        for (int i = 1; i <= 100; i++)
        {
            nums[i - 1].gameObject.SetActive(CanDivide(i)); //伪码
        }
    }

    private bool CanDivide(int value) 
    {
        int length = Enum.GetNames(typeof(NumScreen)).Length;
        bool canDivide = true;
        for (int i = 0; i < length; i++)
        {
            if (myLayer.Contains(i))
            {
                if (value % (i + 3) != 0)
                {
                    canDivide = false;
                    break;
                }
            }
        }
        return canDivide;
    }
}

3.用于多状态的记录:具体代码的实现原理和多条件筛选一样

比如在游戏中的奖励系统,游戏中有很多种奖励,每日登录,排位赛,竞技场,公会奖励,在线奖励,签到奖励等,策划需要每一种奖励可以领取的时候,客户端在相应的功能按钮都需要光效表现来引起玩家注意,让玩家知道某某奖励现在是可以领取的。我看到很多开发人员都是客户端把所有系统的数据都拿到了,然后再根据数据的相应情况来决定是否让这个按钮开启光效。其实我们只需要用一个整数,在服务器端算好每一种奖励是否可以被领取,客户端收到这个数据后,根据每一个状态的情况来开启相应的光效,让玩家点击进入相应的系统的时候,才去拿相应的数据。

同样在做智能体的状态时,对于多状态的完全可以用位运算来记录,和上面的原理是一样的。这样相当于做了一个简单的分层状态机。例如:

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

public enum EnemyState
{
    LosePlayer = 1,
    SeePlayer = 2,
    NearFromPlayer = 16,
    FarFromPlayer = 32,
    HighHealth = 4,
    LowHealth = 8,
    Happy = 64,
    Angry = 128,
}

public class StateOperator : MonoBehaviour
{
    [EnumMaskField("EnemyStates")]
    public EnemyState enemyStates;
    public float currentDistance;

    public Transform player;
    public Transform enemy;

    private MyLayerMask myLayerMask;

    private void OnDrawGizmos()
    {
        if (player == null || enemy == null) return;

        currentDistance = Vector3.Distance(player.position, enemy.transform.position);

        myLayerMask = new MyLayerMask();
        if (currentDistance < 1f)
        {
            myLayerMask.Add((int)EnemyState.SeePlayer);           
        }
        if (currentDistance > 1f)
        {
            myLayerMask.Add((int)EnemyState.LosePlayer);
        }
        if (currentDistance < 0.5f)
        {
            myLayerMask.Add((int)EnemyState.NearFromPlayer);
            myLayerMask.Add((int)EnemyState.Angry);
        }
        if (currentDistance > 3f)
        {
            myLayerMask.Add((int)EnemyState.FarFromPlayer);
            myLayerMask.Add((int)EnemyState.Happy);
        }
        enemyStates = (EnemyState)myLayerMask.value;
    }
}


EnumMaskField是一个自定义特性,可以像LayerMask一样查看多选的枚举:

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Enum, Inherited = true)]
public class EnumMaskFieldAttribute : PropertyAttribute
{
	public string propertyName;

	public EnumMaskFieldAttribute(string propertyName)
	{
		this.propertyName = propertyName; 
	}

}

[CustomPropertyDrawer(typeof(EnumMaskFieldAttribute))]
public class EnumMaskFieldAttributeDrawer : PropertyDrawer
{
	public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _lable)
	{
		EnumMaskFieldAttribute enumMask = attribute as EnumMaskFieldAttribute;
		_property.intValue = EditorGUI.MaskField(_position, enumMask.propertyName, _property.intValue, _property.enumDisplayNames);
	}
}

在C#内部有很多地方都用到了位运算:
[AttributeUsage(AttributeTargets.Field, Inherited = true)] 可以看看AttributeTargets

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值