化石原创文章,转载请注明来源并保留原文链接


glb是web3d中使用频率较高的一种3d格式。Unity原生到2019还没有支持。幸运的是,KhronosGroup官方在github上发布了对应的包,能加载glb、gltf格式。

尝试了一下,2019的版本应该还有问题。2018.4.xx后的版本可以正常处理。我测试的版本是2018.4.21f1。

克隆github的源代码后,先打开GLTFSerialization目录下的GLTFSerialization.sln,利用Visual Studio 2019编译默认的激活项目即可。该项目编译出一系列的dll到工程自带的Unity Demo工程下(UnityGLTF)。

然后就可以使用Unity打开工程目录下的UnityGLTF Unity项目。

工程下有很多的demo,在Samples下,每个场景最重要的就是名字为GLTF的GameObject,上挂一个GLTFComponent脚本,变量GLTF Uri是目标glb或者gltf的路径。

我克隆的项目,对应的目录下是空的。所以知道这个加载路径后,无非就是把glb文件放置到一定的路径下,然后在这里改动对应的变量值即可。


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


因为一些需求,Unity窗口不能成为焦点,但是需要能知道键盘输入,还好是Windows版本,比较简单。不过中间也发生了点crash问题。所以把最后对应的代码放出来,供遇到问题的人参考,对比。

using UnityEngine;
using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine.UI;
using System.Globalization;

public class KBHooks : MonoBehaviour
{
    [DllImport("user32.dll")]
    static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc callback, IntPtr hInstance, uint threadId);

    [DllImport("user32.dll")]
    static extern bool UnhookWindowsHookEx(IntPtr hInstance);

    [DllImport("user32.dll")]
    static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam);

    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);

    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

    const int WH_KEYBOARD_LL = 13;
    const int WM_KEYDOWN = 0x100;

    private LowLevelKeyboardProc _proc = hookProc;

    private static IntPtr hhook = IntPtr.Zero;

    public void SetHook()
    {
        IntPtr hInstance = LoadLibrary("User32");
        hhook = SetWindowsHookEx(WH_KEYBOARD_LL, _proc, hInstance, 0);
    }

    public void UnHook()
    {
        UnhookWindowsHookEx(hhook);
    }

    public static IntPtr hookProc(int code, IntPtr wParam, IntPtr lParam)
    {
        if (code >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);
            if (vkCode.ToString() == "32")
            {
                text.text += "Space bar press.\n";
            }
        }
 
        return CallNextHookEx(hhook, code, (int)wParam, lParam);
    }


    static Text text;

	// Use this for initialization
	void Start()
	{
		Debug.Log("install hook");

		text = GameObject.Find ("Canvas/Text").GetComponent<Text>();
        SetHook();
	}

	void OnDisable()
	{
		Debug.Log("Uninstall hook");
        UnHook();
	}

}

这段代码里,测试按键是键盘的A,Unity场景中放了一个Text组件用于输出。代码在Unity 2019.1上测试。


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


Android上的Unity程序,程序左侧是ugui制作的listview,右边由UniWebview2占据剩余空间。正常情况下没有任何问题。一旦该程序进入后台,再转回前台,发现listview的空间全是黑色,也就是listview看起来消失了。

这个问题的解决方式是:

让webview能hide和show,在进入后台的时候hide,进前台的时候show。

因为Unity有OnApplicationPause,所以我们利用在这个地方动手。

OnApplicationPause的参数是true的时候,我们知道程序会进入后台了,所以调用webview的hide().
OnApplicationPause的参数是false的时候,我们知道程序会进入前台了,所以调用webview的show().

问题解决。


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


Orbit Camera在观察物体,比如模型观察器中使用,这种camera总是以一个模型为目标,观察点围绕该目标,实现:

1、相机远近操控

2、相机水平旋转操控

3、相机垂直方向抬高、压低

总之,这种相机模型还是非常常用的。下面是代码:

using UnityEngine;
using System.Collections;

public class OrbitCamera : MonoBehaviour
{

    public Transform target;
    public float distance = 5.0f;
    public float xSpeed = 120.0f;
    public float ySpeed = 120.0f;

    public float yMinLimit = -20f;
    public float yMaxLimit = 80f;

    public float distanceMin = .5f;
    public float distanceMax = 15f;

    public float mouseTranslateSpeed = 20;

    float x = 0.0f;
    float y = 0.0f;

    void Start()
    {
        Vector3 angles = transform.eulerAngles;
        x = angles.y;
        y = angles.x;

        Quaternion rotation = Quaternion.Euler(y, x, 0);

        RaycastHit hit;
        if (Physics.Linecast(target.position, transform.position, out hit))
        {
            distance -= hit.distance;
        }
        Vector3 negDistance = new Vector3(0.0f, 0.0f, -distance);
        Vector3 position = rotation * negDistance + target.position;

        transform.position = position;
    }

    void LateUpdate()
    {
        distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * mouseTranslateSpeed, distanceMin, distanceMax);

        if (Input.GetMouseButton(0) &amp;&amp; target)
        {
            x += Input.GetAxis("Mouse X") * xSpeed * distance * 0.02f;
            y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;

            y = ClampAngle(y, yMinLimit, yMaxLimit);
        }

        Quaternion rotation = Quaternion.Euler(y, x, 0);
        transform.rotation = rotation;

        RaycastHit hit;
        if (Physics.Linecast(target.position, transform.position, out hit))
        {
            distance -= hit.distance;
        }
        Vector3 negDistance = new Vector3(0.0f, 0.0f, -distance);
        Vector3 position = rotation * negDistance + target.position;

        transform.position = position;
    }

    public static float ClampAngle(float angle, float min, float max)
    {
        while(angle < -360f)
            angle += 360F;

        while(angle > 360f)

            angle -= 360f;
        return Mathf.Clamp(angle, min, max);
    }
}

在Unity面板上出现的参数说明:

1、Target:相机观察目标

2、X Speed:鼠标水平移动控制相机旋转的速度,一般不用太大,个位数即可。

3、Y Speed:鼠标上下控制相机仰角的速度,100左右

4、Y Min (Max)Limit:鼠标仰角的最小最大值,-90到90

5、Distance Min:相机离开目标中心的最小距离

6、Distance Max: 相机离开目标中心的最大距离

7、Mouse Translate Speed:鼠标拉近拉远时的速度


化石原创文章,转载请注明来源并保留原文链接



化石原创文章,转载请注明来源并保留原文链接


写了个蓝牙的server,可以直接编译成aar,供unity使用。技术上利用了service和java反射,因此无需在Unity中注入和改动任何东西。

代码在github,这里

编译出的aar直接放Unity工程的Assets目录下的任何地方即可开始使用。

Unity代码开启和关闭蓝牙服务器:

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

public class NewBehaviourScript : MonoBehaviour {

	AndroidJavaClass blueToothServer;

	void Awake() {
		AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
		AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
		AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");

		blueToothServer = new AndroidJavaClass ("com.huashi.bluetuth.BlueTuthServer");
		blueToothServer.CallStatic ("init", unityContext);
	}

	void Start () {
		GameObject.Find ("Canvas/Start").GetComponent<Button> ().onClick.AddListener (onStartClick);
		GameObject.Find ("Canvas/Stop").GetComponent<Button> ().onClick.AddListener (onStopClick);
	}

	void onStartClick() {
		blueToothServer.CallStatic ("startServer");				
	}
	
	void onStopClick () {
		blueToothServer.CallStatic ("stopServer");				
	}
}

Unity代码接收服务器消息:

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

public class JavaMessageReceiver : MonoBehaviour {

	public void ReceiveJavaMessage(string msg) {
		Debug.Log ("Receive msg.");

		byte[] bytes = Encoding.ASCII.GetBytes (msg);

		if (bytes.Length != 0) {
			switch (bytes [0]) {
			case 1:
				Debug.Log ("CLIENT_CONNECTED");
				string real = msg.Substring (1);
				Debug.Log ("Content is: " + real);
				break;
			case 2:
				Debug.Log ("RECEIVE_CLIENT_DATA");
				for (int i = 1; i < bytes.Length; ++i) {
					Debug.Log ("index = " + i + ":" + bytes[i]);
				}
				break;
			case 3:
				Debug.Log ("REMOTE_SHUTDOWN");
				string realll = msg.Substring (1);
				Debug.Log ("Content is: " + realll);
				break;
			default:
				Debug.Log ("Received from java, tag = " + bytes[0]);
				break;
			}
		}
	}
}

上面的这个代码必须挂在一个名字为JavaMessageReceiver的GameObject上。


化石原创文章,转载请注明来源并保留原文链接