Unity + ZXing + 螢幕旋轉自動自適應 + 自定義掃碼介面

耳朵裡有隻風發表於2017-03-28

由於使用 ZXing 在unity進行二維碼識別功能的人較多,這邊我也試著使用了一下ZXing。發現如下幾個問題:

  • ZXing僅僅只提供了二維碼和條形碼等的解碼,並沒有提供在unity中的預製件(也就是prefab)。這樣就會導致很多第一次使用zxing的盆友,需要自己寫一個自適應螢幕大小和根據螢幕旋轉自動縮放的攝像機。
  • 需要自行編寫一個能夠自適應螢幕大小和根據螢幕旋轉自動縮放的攝像機,以保證渲染出來的WebCamTexture(就是攝像頭獲得過來的影象)不會拉伸。
  • Zxing的解碼操作並不能放在update裡進行操作,不然會計算量過大導致嚴重卡頓甚至卡死。
  • 也是由於zxing的解碼操作計算容易導致卡頓,從攝像頭獲得過來的texture(也就是WebCamTexture),不能使用高解析度,不然也會卡死。

因此,我這邊放出我的解決方案,供廣大unity碼農使用。下載包裡面提供了預製件和使用示例。至於自定義掃碼介面,因為我這邊是unity的實現方式,並不是android或者ios等其他平臺的原生掃碼實現方式,所以直接在我的prefab的基礎上加介面就可以了。

下載地址:ScanQRCoder.unitypackage

主要原始碼如下:

using UnityEngine;
using System.Collections;
using ZXing;
using System;
using System.Runtime.InteropServices;

public class ScanQRCoder : MonoBehaviour
{
    public static event Action<string> OnDecoderMessage;

    private Camera renderCam;
    private MeshRenderer meshRender;
    private WebCamTexture webCamTexture;
    private DeviceOrientation lastDeviceOrientation;

    public void ReInitRect()
    {
        StopCoroutine(DecodeQR());
        webCamTexture.Stop();
        Start();
    }

    private void Start()
    {
        lastDeviceOrientation = Input.deviceOrientation;
        Init();
        meshRender.material.mainTexture = webCamTexture;
        webCamTexture.Play();
        StartCoroutine(DecodeQR());
    }

    private void Init()
    {
        renderCam = GetComponentInChildren<Camera>();

        int webCamDeviceIndex = -1;
        for (int i = 0; i < WebCamTexture.devices.Length; i++)
        {
            if (!WebCamTexture.devices[i].isFrontFacing)
            {
                webCamDeviceIndex = i;
                break;
            }
        }
        if (webCamDeviceIndex == -1 && WebCamTexture.devices.Length > 0) webCamDeviceIndex = 0;

        Vector3[] camfov = GetCameraFovPositionByDistance(renderCam, renderCam.transform.localPosition.y);
        float w = Vector3.Distance(camfov[1], camfov[0]);
        float h = Vector3.Distance(camfov[2], camfov[0]);

        if(Screen.width >= Screen.height)
        {
            h = w;
        }
        else
        {
            w = h;
        }

        meshRender = GetComponentInChildren<MeshRenderer>();
        meshRender.transform.localScale = new Vector3(w / 10, 1, h / 10);

        webCamTexture = new WebCamTexture(WebCamTexture.devices[webCamDeviceIndex].name, 200, 200);//這裡的第二第三個引數可以根據機子的效能而調高,也就是解析度越高

    }

    void onDecoderMessage(string code)
    {
        if (OnDecoderMessage != null)
        {
            OnDecoderMessage(code);
            Debug.Log("code:" + code);
        }
    }
    private IEnumerator DecodeQR()
    {
        yield return new WaitForSeconds(1f);
        try
        {
            var barcodeReader = new BarcodeReader { AutoRotate = true, TryHarder = true };
            Result result = barcodeReader.Decode(webCamTexture.GetPixels32(), webCamTexture.width, webCamTexture.height);
            //Debug.Log("QR Code w h: " + webCamTexture.width + " " + webCamTexture.height);
            if (result != null)
            {
                onDecoderMessage(result.Text);
                //Debug.Log("QR Code: " + result.Text);
            }
        }
        catch { }
        if (Input.deviceOrientation != lastDeviceOrientation)
        {
            ReInitRect();
        }
        else
        {
            if (webCamTexture.isPlaying)
                StartCoroutine(DecodeQR());
        }
    }

    private static byte[] ColorArrayToByteArray(Color[] colors)
    {
        if (colors == null || colors.Length == 0)
            return null;

        int lengthOfColor32 = Marshal.SizeOf(typeof(Color));
        int length = lengthOfColor32 * colors.Length;
        byte[] bytes = new byte[length];

        GCHandle handle = default(GCHandle);
        try
        {
            handle = GCHandle.Alloc(colors, GCHandleType.Pinned);
            IntPtr ptr = handle.AddrOfPinnedObject();
            Marshal.Copy(ptr, bytes, 0, length);
        }
        finally
        {
            if (handle != default(GCHandle))
                handle.Free();
        }

        return bytes;
    }


    /// <summary>
    /// 獲取指定距離下相機視口四個角的座標
    /// </summary>
    private Vector3[] GetCameraFovPositionByDistance(Camera cam, float distance)
    {
        Vector3[] corners = new Vector3[4];
        Array.Resize(ref corners, 4);
        // Top left
        corners[0] = cam.ViewportToWorldPoint(new Vector3(0, 1, distance));
        // Top right
        corners[1] = cam.ViewportToWorldPoint(new Vector3(1, 1, distance));
        // Bottom left
        corners[2] = cam.ViewportToWorldPoint(new Vector3(0, 0, distance));
        // Bottom right
        corners[3] = cam.ViewportToWorldPoint(new Vector3(1, 0, distance));
        return corners;
    }

}

使用示例:

using UnityEngine;
using System.Collections;

public class ScanQRCoderDemo : MonoBehaviour
{
    public ScanQRCoder scanner;

    private void OnEnable()
    {
        ScanQRCoder.OnDecoderMessage += OnDecoderMessage;
    }

    private void OnDisable()
    {
        ScanQRCoder.OnDecoderMessage -= OnDecoderMessage;
    }

    public void OnDecoderMessage(string msg)
    {
        Debug.Log("二維碼資訊:" + msg);
    }
}

2017.04.05更新

現發現了新的實現方式,幀率和解析度都非常滿意!強烈推薦使用。
裡面有示例場景。

下載地址:ScanQRCoder2


【個人廣告】
希望大家可以支援我的個人微訊號“小遊戲情報局

這裡寫圖片描述

相關文章