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


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) && 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上。


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



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


Unity侧:

AAR文件可以放在assets目录的任何地方

AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");     

AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");     

AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");

这三步能拿到大多数aar需要的context,activity

AndroidJavaClass.CallStatic(methodName, parameter0, parameter1, pameter2);

使用最上面的方法得到Android Class后,内部的static方法,用这个方法调用。多个参数依次排列在方法名字后面即可(官方文档没有例子,使用了数组形式,没测过)

AndroidJavaClass.Call(methodName, parameter0, parameter1, pameter2);

调用实例的方法。

Java侧:

com.unity3d.player包中,有类

UnityPlayer,提供方法UnitySendMessage(GameObjectName, MethodName, parameter)

该类的反编译结果可以在这里看到。截取方法的定义如下:

public static native void UnitySendMessage(String var0, String var1, String var2); 

写插件的过程中,如果不想让java侧代码一定跟Unity3d绑定,那么可以使用java的反射来取得上面的方法。

Class playerClass = Class.forName("com.unity3d.player.UnityPlayer");

Log.d("MyTag", playerClass.toString());

Method sendMessageMethod = playerClass.getMethod("UnitySendMessage", String.class, String.class, String.class);

Log.d("MyTag", sendMessageMethod.getName() + ", " + sendMessageMethod.toString());


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



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


	string path;

	void Start () {
		path = "/storage/emulated/0/Android/data/test.apk";
		Debug.Log (path);

		GameObject.Find ("Canvas/Button").GetComponent<Button> ().onClick.AddListener (OnClick);
	}
	
	// Update is called once per frame
	void OnClick () {
		installApp (path);
	}

	public bool installApp(string apkPath)
	{
		try
		{
			AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
			string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
			int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
			AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);

			AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
			AndroidJavaClass uriObj = new AndroidJavaClass("android.net.Uri");
			AndroidJavaObject uri = uriObj.CallStatic<AndroidJavaObject>("fromFile", fileObj);

			intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
			intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
			intent.Call<AndroidJavaObject>("setClassName", "com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity");

			AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
			AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
			currentActivity.Call("startActivity", intent);

			GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
			return true;
		}
		catch (System.Exception e)
		{
			GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
			return false;
		}
	}

方法installApp能通过android安装指定路径下的apk。跑起来后,如果apk存在,会弹出对话框,让用户确认安装;安装完毕,会弹出对话框让用户选择“完成”还是“打开”。

API 24以上:

//For API 24 and above
private bool installApp(string apkPath)
{
    bool success = true;
    GameObject.Find("TextDebug").GetComponent<Text>().text = "Installing App";

    try
    {
        //Get Activity then Context
        AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");

        //Get the package Name
        string packageName = unityContext.Call<string>("getPackageName");
        string authority = packageName + ".fileprovider";

        AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
        string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
        AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);


        int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
        int FLAG_GRANT_READ_URI_PERMISSION = intentObj.GetStatic<int>("FLAG_GRANT_READ_URI_PERMISSION");

        //File fileObj = new File(String pathname);
        AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
        //FileProvider object that will be used to call it static function
        AndroidJavaClass fileProvider = new AndroidJavaClass("android.support.v4.content.FileProvider");
        //getUriForFile(Context context, String authority, File file)
        AndroidJavaObject uri = fileProvider.CallStatic<AndroidJavaObject>("getUriForFile", unityContext, authority, fileObj);

        intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
        intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
        intent.Call<AndroidJavaObject>("addFlags", FLAG_GRANT_READ_URI_PERMISSION);
        currentActivity.Call("startActivity", intent);

        GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
    }
    catch (System.Exception e)
    {
        GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
        success = false;
    }

    return success;
}

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