Unity实现一些小功能(持续更新)

检查是否是2的整数幂

苹果手机, 图片长宽均为2的整数幂, 且是正方形, 才能用pvrtc4压缩
检查图片是否为POT:

static bool IsPowerOfTwo(int x)
{
	return x > 0 && (x & (x - 1)) == 0;
}
或者
static bool IsPowerOfTwo(int x)
{
	return (x & -x) == x;
}

原理看这里: https://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2

新手引导: 射线穿透

新手引导, 遮罩镂空, 整个遮罩不可点击, 只有镂空处能点击

把这个脚本挂在全屏遮罩上

public class RayCastHollow : MonoBehaviour, ICanvasRaycastFilter
{
    public RectTransform rect; //镂空图片
    bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera)
    {
        // 将镂空图范围内的事件镂空
        return !RectTransformUtility.RectangleContainsScreenPoint(rect, screenPos, eventCamera);
    }
}

点击2D精灵图的射线检测

本来用的是3D盒子碰撞体做的, 但这样做有个问题: 点空白区域的时候, 会触发检测
由于2D的Sprite没法用MashCollider, 所以用PolygonCollider2D
此处有个坑点: PolygonCollider2D只能站着, 不能平躺下, 所以相机必须以Z轴为正方向, 不能用Y轴

2D的射线检测和3D不一样, 要用Physics2D.Raycast, 不用Ray

	void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePos = Input.mousePosition;
            mousePos.z = -Camera.main.transform.localPosition.z;
            Vector3 worldPos = Camera.main.ScreenToWorldPoint(mousePos);
            RaycastHit2D hit = Physics2D.Raycast(worldPos, Vector2.zero);
            if (hit.transform != null)
            {
                print(hit.transform.name);
            }
        }
    }

知道方向向量, 计算角度

2d游戏, Vector3.up为前方向, 不能用LookAt或者LookRotation
已知从我指向敌人的方向向量, 计算子弹的rotation

计算角度, 角度小于90度则是向前发射, 大于90度则是向后发射
点乘, 看敌人在我的左边还是右边
用Quaternion.Euler设置角度, 只需旋转Z轴即可

-- dir是从我指向敌人的方向向量
function SetRot(dir)
    -- 夹角必定小于180
    local angle = Vector3.Angle(Vector3.up, dir)
    local dot = Vector3.Dot(Vector3.right, dir)
    -- dot>0, 说明往左发射, 所以角度为负数
    if dot > 0 then
        angle = -angle
    end
    transform.rotation = Quaternion.Euler(Vector3(0, 0, angle))
end

递归深度查找子孙

    public static GameObject FindChild(GameObject parent, string childName)
    {
        if (parent.name == childName)
        {
            return parent;
        }
        if (parent.transform.childCount < 1)
        {
            return null;
        }
        GameObject obj = null;
        for (int i = 0; i < parent.transform.childCount; i++)
        {
            GameObject go = parent.transform.GetChild(i).gameObject;
            obj = FindChild(go, childName);
            if (obj != null)
            {
                break;
            }
        }
        return obj;
    }

第一人称视角, 键盘控制移动, 鼠标控制视角

直接拿去挂在相机上就能跑了

using UnityEngine;

public class FPS : MonoBehaviour
{
    float moveSpeed = 1;
    Vector3 moveDir = Vector3.zero;

    float rotSpeed = 50;
    Vector3 rotDir = Vector3.zero;
    float minY = -45;
    float maxY = 45;

    void Update()
    {
        Move();
        Rot();
    }

    private void Move()
    {
        moveDir.x = Input.GetAxis("Horizontal");
        moveDir.z = Input.GetAxis("Vertical");
        transform.Translate(moveDir * Time.deltaTime * moveSpeed, Space.Self);
    }

    private void Rot()
    {
        rotDir.y += Input.GetAxis("Mouse X") * Time.deltaTime * rotSpeed;
        rotDir.x -= Input.GetAxis("Mouse Y") * Time.deltaTime * rotSpeed;
        rotDir.x = Mathf.Clamp(rotDir.x, minY, maxY);
        transform.localEulerAngles = rotDir;
    }
}

按顺序更换地图:

  1. 每打4局更换地图
  2. 按顺序更换3张地图
  3. 显示剩余局数

lua代码:

interval = 4
map = {1, 2, 3}
for playCount = 0, 13 do
    local index = playCount // interval % #map + 1
    local remain = interval - playCount % interval
    print('当前地图为: ' .. map[index] .. '   剩余刷新数: ' .. remain)
end

输出: 
当前地图为: 1   剩余刷新数: 4
当前地图为: 1   剩余刷新数: 3
当前地图为: 1   剩余刷新数: 2
当前地图为: 1   剩余刷新数: 1
当前地图为: 2   剩余刷新数: 4
当前地图为: 2   剩余刷新数: 3
当前地图为: 2   剩余刷新数: 2
当前地图为: 2   剩余刷新数: 1
当前地图为: 3   剩余刷新数: 4
当前地图为: 3   剩余刷新数: 3
当前地图为: 3   剩余刷新数: 2
当前地图为: 3   剩余刷新数: 1
当前地图为: 1   剩余刷新数: 4
当前地图为: 1   剩余刷新数: 3

随机问题:

规则:

  1. 权重比为 a: b: c = 3: 4: 3
  2. 不一定abc都有, 可能会没有a, 也可能会没有c, 也可能会没有bc, 也可能一个都没有
  3. 按权重, 随机取一个

以下是伪代码
我的方法:

x = a ? 3 : 0
y = b ? 4 : 0
z = c ? 3 : 0
-- 放一堆小球进袋子
for x { all.add(q1) }
for y { all.add(q2) }
for z { all.add(q3) }
-- 从袋子里拿出一个
r = random( all.count )
if all[r] == q1   --> a
if all[r] == q2   --> b
if all[r] == q3   --> c

同事的方法:

x = a ? 3 : 0
y = b ? 4 : 0
z = c ? 3 : 0
-- 这一步是精髓, 分段
all[3] = { x, x+y, x+y+z }
r = random( x+y+z )
i = 0
for i<all.count, i++
{
    -- 看随机数落在哪个段里
    if r<all[i]
    break
}
if i == 0   --> a
if i == 1   --> b
if i == 2   --> c

RSA加密解密

公钥密钥可以去https://www.bejson.com/enc/rsa/自己生一个

public class Web : MonoBehaviour
{
    public static string publicKey = "";
    public static string privateKey = "";

    void Start()
    {
        CreateKeys(out privateKey, out publicKey);

        string passwordOriginal = "123456";
        
        string passswordEncrypt = RSAEncrypt(passwordOriginal);
        Debug.LogError("加密后的密码为: " + passswordEncrypt);

        string passwordDecrypt = RSADecrypt(passswordEncrypt);
        Debug.LogError("解密后的密码为: " + passwordDecrypt);
    }

    //生成Xml形式的Key
    public static void CreateKeys(out string PrivateKey, out string PublicKey)
    {
        try
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            PrivateKey = rsa.ToXmlString(true);//true是包含公钥和私钥
            PublicKey = rsa.ToXmlString(false);//false是只包含公钥
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    //解密
    public static string RSADecrypt(string content)
    {
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        rsa.FromXmlString(privateKey);

        byte[] cipherbytes = rsa.Decrypt(Convert.FromBase64String(content), false);
        return Encoding.UTF8.GetString(cipherbytes);
    }

    //加密
    public static string RSAEncrypt(string content)
    {
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        rsa.FromXmlString(publicKey);

        byte[] cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false);
        return Convert.ToBase64String(cipherbytes);
    }
}

数据改变通知

继承 INotifyPropertyChanged

using System.ComponentModel;
using System.Runtime.CompilerServices;

	class NotifyObject : INotifyPropertyChanged
    {
        private int number1;
        public int Number1
        {
            get { return number1; }
            set
            {
                if (number1 != value)
                {
                    number1 = value;
                    OnPropertyChanged();
                }
            }
        }
        //private int number2;
        //private int number3;
        
        public event PropertyChangedEventHandler PropertyChanged;
        
        //[CallerMemberName]的作用:自动添加属性名
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

PropertyChanged += Do, 就能在属性改变时执行Do方法

    void Do(object sender, PropertyChangedEventArgs property)
    {
        switch (property.PropertyName)
        {
            case "Number1":
    	        Debug.LogError(1);
	            break;
	        case "Number2":
    	        Debug.LogError(2);
	            break;
        }
    }

很详细的文章, 甚至写了泛型:
https://www.cnblogs.com/TianFang/p/3381484.html

序号同步移动

需求是这样的:
17个问题, 对应17个小灯, 能显示红色或绿色来判断回答的正误, 17个小灯横着排列在屏幕上方;
因为17个都排出来不好看, 所以用了Scroll View只显示9个;
小灯要跟着我的答题序号同步移动, 比如我答第8题, 第8个小灯要居中显示;
后面有可能还要加更多题, 不止17个题.
在这里插入图片描述

	//先把灯显示出来, 不管是17个还是1万个
	private void InitJudgeLight()
    {
        judgeLightsList = new List<JudgeLight>();
        for (int i = 0; i < Data.allQuestionsList.Count; i++)
        {
            JudgeLight judge = Instantiate(Resources.Load<JudgeLight>("Prefabs/ChildJudgeLight"), parentJudgeLight);
            judgeLightsList.Add(judge);

			//数字显示为i+1
            Text[] texts = judge.transform.GetComponentsInChildren<Text>(true);
            Array.ConvertAll(texts, p => p.text = (i + 1).ToString());
        }
    }
    
    //答题的时候, 小灯移动
    public void ShowQuestion()
    {
        //更新bar, 4 表示前5个题不用动, 9 是一共只能显示9个题
        int nowQues = Data.GetCurrentQuestionIndex() - 4;//小于0也没关系的
        //需移动的次数 = 灯总数 - 灯显示数, 比如说共有10个题, 框内仅能显示8个, 即需要移动2次
        int maxQues = Data.allQuestionsList.Count - 9;
        barJudgeLight.value = (float)nowQues / maxQues;
        ...//后面写显示问题
	}

↓后面又加了些动画

    public void ShowQuestion()
    {
        int nowQues = Data.GetCurrentQuestionIndex() - 4;
        int maxQues = Data.allQuestionsList.Count - 9;
        //滚动条会往前挪动一下
        DOTween.To(() => barJudgeLight.value, x => barJudgeLight.value = x, (float)nowQues / maxQues, 1f).SetEase(Ease.OutBack);
        //当前正在回答的问题会呼吸
        int index = Data.GetCurrentQuestionIndex();
        tweenJudgeLight = judgeLightsList[index].transform.DOScale(1.1f, 0.5f).SetLoops(-1, LoopType.Yoyo);
		...//后面写显示问题
	}

额, 如果太多了就不要全加载出来了, 如果太多就写个循环显示, 网上很多, 一搜就有;

简单的相机Lerp跟随

当人物快跑时, 镜头会被拉远, 会有"跑太快, 镜头都跟不上了"的感觉
太近了的话有可能会穿模, 所以相机的最近距离给个0.01;

using UnityEngine;

public class LerpFlollow : MonoBehaviour
{
    public Transform target;
    private Vector3 offset;
    public float moveSpeed;

    void Start()
    {
        offset = transform.position - target.position;
    }

    void Update()
    {
        transform.position = Vector3.Lerp(transform.position, target.position + offset, Time.deltaTime * moveSpeed);
    }
}

UI图标闪烁

主要是Mathf.PingPong(value, max);
会从0到max, 以value增加的速度, 做来回运动

using UnityEngine;
using UnityEngine.UI;

public class ImageSparkle : MonoBehaviour
{
    private Image image;
    
    private void Start()
    {
        image = GetComponent<Image>();
    }

    private void Update()
    {
    	//回头看看, 这里的Color应该做一个全局的, 不要每帧都new
        image.color = new Color(1, 0, 0, Mathf.PingPong(Time.time, 1));
    }

    private void OnDestroy()
    {
        image.color = new Color(0, 0, 0, 0);
    }
}

↓要完善的话再加上这一堆东西

 	public static ImageSparkle Get<T>(T t) where T : Component
    {
        ImageSparkle sparkle = t.gameObject.GetComponent<ImageSparkle>();
        if (sparkle == null)
        {
            sparkle = t.gameObject.AddComponent<ImageSparkle>();
        }
        return sparkle;
    }

    public static ImageSparkle Get(GameObject obj)
    {
        ImageSparkle sparkle = obj.GetComponent<ImageSparkle>();
        if (sparkle == null)
        {
            sparkle = obj.AddComponent<ImageSparkle>();
        }
        return sparkle;
    }
        
    public static void Remove(GameObject obj)
    {
        ImageSparkle sparkle = obj.GetComponent<ImageSparkle>();
        if (sparkle != null)
        {
            Destroy(sparkle);
        }
    }

更简单的方法是用DOTween, 但是如果多次激活这个方法的话就不大好用了

image.DOFade(0,1).SetLoops(-1,LoopType.Yoyo);

搜索工具栏

主要是 InputField.onValueChanged.AddListener();

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

public class SearchTool : MonoBehaviour
{
    private List<GameObject> toolsList;
    public InputField input;

    private void Awake()
    {
        input.onValueChanged.AddListener(str => Scerch(str));

        toolsList = new List<GameObject>();
        foreach (Transform item in transform)
        {
            toolsList.Add(item.gameObject);
        }
    }

    private void Scerch(string str)
    {
        for (int i = 0; i < toolsList.Count; i++)
        {
            toolsList[i].SetActive(false);
        }

        List<GameObject> resultsList = toolsList.FindAll(p => p.name.Contains(str));
        for (int i = 0; i < resultsList.Count; i++)
        {
            resultsList[i].SetActive(true);
        }
    }
}

↓改了一版, 节省掉了2个遍历1个列表

	private void ScerchTools(string str)
    {
        for (int i = 0; i < toolsList.Count; i++)
        {
            GameObject tool = toolsList[i];
            if (tool.name.Contains(str))
            {
                tool.SetActive(true);
            }
            else
            {
                tool.SetActive(false);
            }
        }
    }

↓甚至可以简化成这样 (但没必要, 因为别人可能不好理解, 也不便于修改)

	private void ScerchTools(string str)
    {
        toolsList.ForEach(p => p.SetActive(p.name.Contains(str)));
    }

按钮控制Scroll View(这里只用Horizontal Scroll Bar)

如果不希望"鼠标可以在View里拖动View", 那就把Scroll Rect上的Horizontal取消勾选;

using UnityEngine;
using UnityEngine.UI;

public class SliderCtrl : MonoBehaviour
{
    public Scrollbar bar;
    public Button btnRight;
    public Button btnLeft;

    private void Awake()
    {
        btnRight.onClick.AddListener(() => clickBtnRight());
        btnLeft.onClick.AddListener(() => clickBtnLeft());
    }
    
    private void clickBtnRight()
    {
        bar.value += 0.5f;
    }
    private void clickBtnLeft()
    {
        bar.value -= 0.5f;
    }
}

鼠标滑轮控制视野(带有平滑效果)

主要是用的Mathf.Lerp();

	private float nextView;
    private float nowView = 60;
    
    private float minView = 12;
    private float maxView = 80;
    
    private void Update()
    {
        if (cameraOutUI.activeInHierarchy)
        {
            float speed = Input.GetAxis("Mouse ScrollWheel");
            //当在滑
            if (Mathf.Abs(speed) > Mathf.Abs(nextView))
            {
                nextView = speed;
            }
            //减速到0
            nextView = Mathf.Lerp(nextView, 0, 5 * Time.deltaTime);
            //乘法改变view
            nowView *= (1 - nextView * 0.15f);
            nowView = Mathf.Clamp(nowView, minView, maxView);//限制视野

            camera.fieldOfView = nowView;
        }
    }

镜头随鼠标轻微转动

用的是eulerAngles
FPSController是身体, 只负责绕Y轴转动
FPSHead是头(相机), 挂在身体上, 只负责绕X轴转动

    /// <summary>
    /// 禁用第一人称的时候, 我觉得静止的镜头很无聊, 就加了镜头随鼠标轻微转动
    /// </summary>
    private void CameraRotateLittle()
    {
        if (!isFPS)
        {
        	//当鼠标左右滑动的时候, 我们希望视角是绕Y轴旋转的
            float rotY = Mathf.Clamp(FPSController.eulerAngles.y + Input.GetAxis("Mouse X") * Time.deltaTime, 120, 150);
            FPSController.eulerAngles = new Vector3(0, rotY, 0);
            //当鼠标上下滑动的时候, 我们希望视角是绕X轴旋转的, 注意这里↓是减法
            float rotX = Mathf.Clamp(FPSHead.localEulerAngles.x - Input.GetAxis("Mouse Y") * Time.deltaTime, 5, 30);
            FPSHead.localEulerAngles = new Vector3(rotX, 0, 0);
        }
    }

需要注意的是eulerAngles.x只能是0到180的值, 不能为负数

用图片的fillAmount实现渐隐渐出效果

精髓是: 等下一帧 yield return new WaitForEndOfFrame();

public IEnumerator ShowChoosePanel()
{
	float filledNum = 0;
	while (filledNum < 1)
	{
		filledNum += Time.deltaTime;
		imageFill.fillAmount = filledNum;
        yield return new WaitForEndOfFrame();
    }
}

注意: 写while的时候, 要么像上面↑这样在循环内有延时, 要么像下面↓这样保证能够跳出循环

while (a < 10)
{
	a++;
}
//或者
while (true)
{
	a++;
	if(a >= 10) return;
}

如果写成下面↓这样, 编辑器会卡死

while (true){}

模型展示

按住鼠标右键控制上下左右旋转, 滑轮放大缩小

if (Input.GetMouseButton(1))
{
	bagPos.Rotate(new Vector3(Input.GetAxis("Mouse Y")*2, -Input.GetAxis("Mouse X"))*2, Space.World);
}
if (Input.GetAxis("Mouse ScrollWheel") != 0)
{
	bagPos.position -= Vector3.forward * Input.GetAxis("Mouse ScrollWheel") * 50;
	//限制放大
	bagPos.localPosition = new Vector3(bagPos.localPosition.x, bagPos.localPosition.y, Mathf.Clamp(bagPos.localPosition.z, -300, 0));
}

禁用鼠标

//禁用鼠标
Cursor.lockState = CursorLockMode.Locked;//锁定鼠标, 禁止鼠标移动到游戏窗口以外
Cursor.visible = false;//鼠标不可见(仅在游戏窗口内), 如果鼠标移动到游戏视窗外, 鼠标还是可见的
//启用鼠标
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;

更改鼠标图标

API是这样的: 
public static void SetCursor(Texture2D texture, Vector2 hotspot, CursorMode cursorMode);
参数1是鼠标样式图, 参数2是点击响应点, 一般是(0,0), 即左上角, 参数3选Auto

//用法
Cursor.SetCursor(normalCursor, new Vector2(0, 0), CursorMode.Auto);
//鼠标图的中心点
Cursor.SetCursor(normalCursor, normalCursor.texelSize / 2, CursorMode.Auto);

洗牌算法

    public static void RandomList<T>(List<T> list)
    {
        for (int i = 0; i < list.Count; i++)
        {
            int randomIndex = UnityEngine.Random.Range(i, list.Count);
            T temp = list[i];
            list[i] = list[randomIndex];
            list[randomIndex] = temp;
        }
    }
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值