01 Unity WWW类Unknown Error
以前同样的代码,现在发现安卓手机上报错了,经人提醒才知道,安卓9上面不是https的链接会出问题。具体可以看这个:
https://forum.unity.com/threads/www-request-not-working-on-android-p.544465/
所以为了能像原来一样用WWW,出包的时候要简单设置下,就是将Target API Level设置为不是Automatic,例如:
此时认为出的包是适配于8.0的,不强制要求https,从而解决了报错问题。
02 Google Cardboard VR模式与普通3D模式切换
建立一个新的工程,连Google VR SDK都不导入,只是在编译的时候开启VR支持,并添加两个SDK,一个None,一个Cardboard,编译出来的包可以进行普通模式和3D模式切换。当None位于列表第一位置的时候,程序运行起来自然就是普通3D模式;当Cardboard位于第一位置的时候,运行起来则直接进入VR模式。
那么如何切换呢?这种需求还是有的,比如VR视频有时候就不带头盔,通过普通3D模式播放,旋转手机改变观看角度。
这里说句题外话,我有个专门的测试工程,里面有较多的脚本和资源,用该工程开辟新的空场景编译得到的包较大。新建一个工程空场景,编译的包很小,也就22M(开启Cardboard和None)。这里我看了Resources和StreamingAssets目录都没,所以说Unity编译的时候某些依赖可能会不准确。
按照前面说的,开启俩SDK,Cardboard在前,看下输出:
private void Awake()
{
vrSDKList = UnityEngine.XR.XRSettings.supportedDevices;
Debug.Log(vrSDKList[0]);
Debug.Log(vrSDKList[1]);
}
得到:
cardboard
None
两个字符串,其中选择SDK的时候Cardboard首字母大写,这里输出的时候是小写了,None则一样。
由于Unity编译的时候选择了Cardboard,所以切换的时候不用再:
UnityEngine.XR.XRSettings.enabled = true;
UnityEngine.XR.XRSettings.LoadDeviceByName("Cardboard");
否则会报错:
Invalid request to load VR Device Cardboard that is already loaded.
这里LoadDeviceByName里面的大小写都行,它都认为是Cardboard。另外要提醒的一点是,如果用:
UnityEngine.XR.XRSettings.loadedDeviceName
来输出下当前加载的平台,那么None对应的是空字符串,Cardboard对应的是cardboard。那么对应的代码可能是:
bool isVRMode = false;
string currentLoadedDevice = UnityEngine.XR.XRSettings.loadedDeviceName;
if(currentLoadedDevice == string.Empty)
{
isVRMode = false;
}
所以现在切换平台总结来说就是,开启Cardboard和None,二者要求Cardboard在前,即一开始就进VR模式,然后用下面代码切换:
using UnityEngine;
public class TestSwitch : MonoBehaviour
{
private string[] vrSDKList;
private void Awake()
{
vrSDKList = UnityEngine.XR.XRSettings.supportedDevices;
Debug.Log(vrSDKList[0]);
Debug.Log(vrSDKList[1]);
}
void Start()
{
}
void Update()
{
if (Application.platform == RuntimePlatform.WindowsEditor)
{
if (Input.GetKeyDown(KeyCode.Space))
{
SwitchVRMode();
}
}
else if (Application.platform == RuntimePlatform.Android)
{
if (Input.touchCount == 1)
{
if (Input.GetTouch(0).phase == TouchPhase.Ended)
{
SwitchVRMode();
}
}
}
}
void SwitchVRMode()
{
bool isVRMode = false;
if(UnityEngine.XR.XRSettings.enabled)
{
if (UnityEngine.XR.XRSettings.loadedDeviceName.ToLower() == "cardboard")
{
isVRMode = true;
}
}
if(isVRMode)
{
UnityEngine.XR.XRSettings.enabled = false;
}
else
{
UnityEngine.XR.XRSettings.enabled = true;
}
}
}
这里None是不能去掉的,否则无法切换。
另外补充,就是前面提到的,非VR模式下自己控制手机镜头选旋转,如:
transform.rotation = Quaternion.Slerp(transform.rotation,
UnityEngine.XR.InputTracking.GetLocalRotation(
UnityEngine.XR.XRNode.Head), speed);
这里在XRsettings的enabled设置为false的时候也是可以用的,而且是不用单独开陀螺仪的:
Input.gyro.enabled = true;
因为这行代码我花了很长时间才找到应用后台仍占用陀螺仪导致功耗测试不通过的原因,还写了博客记录。
03 Unity Video Player
一直用AVPro播放视频,在处理在线视频的时候,发现不是很好用,遇到的问题包括是否在播放状态判断不准确,播放进度增加画面无更新等。决定尝试一下Unity自带的视频播放,这个有许多的博客介绍,我这里不介绍用法了,而是记录下问题。
我是做VR视频播放的,一般要较大的分辨率,比如这里就用了3840*3840上下格式的mp4视频进行测试。
首先直接拖到项目中形成video clip,新建一个Render Texture,并设置其大小为3840*3840。创建video player组件并播放,选择对应的video clip和render texture,播放没问题。该视频是h364的,unity中播放使用了cpu解码,12线程E5-1650 v4的占用率高达60%。播放流畅,没有问题,预计这么高的cpu占用到移动端会比较卡。
接着将该视频放在服务器上,用http方式请求,发现video player的time在增加,也就是它已经在播放了,不过网络请求却基本没有,画面黑色无内容。多次尝试都没有成功播放,直到播放完成后渲染了最后一帧停住。unity应该是支持较低视频质量的http播放的,因为用:
http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4
则可以正常播放,一开始video player的time保持0,它缓冲得到一定数据后就开始播放,time也跟着增加。由于该视频较小,测试几次也没有出现中间缓冲的情况。
去看video player的api,比如缓冲进度或者类似AVPro的播放中触发缓冲暂停播放的事件也没有。吃cpu很多,貌似硬件加速没用上。同样的视频AVPro播放,cpu使用率10%都没有,GPU用了40%,这对于VR视频来说是很重要的条件。当前版本的Unity是2017.4,AVPro是1.8.7。
总结来说,AVPro还是比unity自带的播放组件强很多,尤其在高分辨率视频、在线视频支持和视频格式等方面,所以还是要继续关注和学习AVPro。
05 C# lock
搞Unity的时候做的功能比较简单,很少用到多线程和锁,这里学习下。参考:
https://www.cnblogs.com/fangyz/p/5628268.html
这里不要把下面的代码放在Unity中测试:
void Start ()
{
MyClass myClass = new MyClass();
Thread t = new Thread(myClass.LockFunc);
t.Start();
lock(myClass.objB)
{
Debug.Log("我是主线程,已对objB加锁,马上加锁objA");
lock(myClass.objA)
{
Debug.Log("我是主线程,已对objA、objB都加锁");
}
}
}
public class MyClass
{
public object objA = new object();
public object objB = new object();
public void LockFunc()
{
lock(objA)
{
Debug.Log("我是线程t,已对objA加锁,马上加锁objB");
lock(objB)
{
Debug.Log("我是线程t,已对objA、objB都加锁");
}
}
}
}
根据该博客介绍,给lock传递参数时首先要避免使用public对象,因为有可能外部程序也在对这个对象加锁,也就是上面这段代码在Unity运行的时候就是死锁,Unity无响应,只能强关。死锁的原因是,主线程执行到lock(objA)时,正好线程t执行到lock(objB)。此时objA被t锁住,objB又被主线程锁住,死锁就这样发生了。
下面看下最简单的lock关键字使用演示,和前面的也差不多,就是锁对象不用public了:
// 实现线程同步的第一种方式是我们经常使用的lock关键字,
// 它将包围的语句块标记为临界区,
// 这样一次只有一个线程进入临界区并执行代码
public void MyLock()
{
int i = 0;
object o = new object();
// 在使用lock关键字时必须使用一个引用类型的参数
// 如果将i字段放入lock会提示报错
// 可见使用lock无法将i进行装箱
// new了一个o对象,这样锁的范围只包括lock语句块
// 如果lock中传进来的参数是一个外部对象,那么锁的范围将扩展到这个对象
// 选择引用参数时,应该选择多个线程需要操作的共享对象
lock (o)
{
i = 5;
}
}
这里我直接把博客中的讲解写在注释里面了,主要就是临界区概念,lock使用引用类型参数,lock范围等。
最后,从MSDN上搬运下lock的使用示例:
void Start ()
{
var account = new Account(1000);
var tasks = new Task[100];
for(int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => RandomlyUpdate(account));
}
Task.WaitAll(tasks);
}
public class Account
{
private readonly object balanceLock = new object();
private decimal balance;
public Account(decimal initialBalance)
{
balance = initialBalance;
}
public decimal Debit(decimal amount)
{
lock(balanceLock)
{
if(balance >= amount)
{
Debug.Log($"Balance before debit: {balance,5}");
Debug.Log($"Amount to remove: {amount,5}");
balance -= amount;
Debug.Log($"Balance after debit: {balance,5}");
return amount;
}
else
{
return 0;
}
}
}
public void Credit(decimal amount)
{
lock(balanceLock)
{
Debug.Log($"Balance before credit: {balance,5}");
Debug.Log($"Amount to add: {amount,5}");
balance += amount;
Debug.Log($"Balance after credit: {balance,5}");
}
}
}
static void RandomlyUpdate(Account account)
{
var rnd = new System.Random();
for(int i = 0; i < 10; i++)
{
var amount = rnd.Next(1, 100);
bool doCredit = rnd.NextDouble() < 0.5;
if(doCredit)
{
account.Credit(amount);
}
else
{
account.Debit(amount);
}
}
}
多用美元符号替代string format方法,好像挺好用的。NextDouble返回一个大于或等于 0.0 且小于 1.0 的随机浮点数,Next(1, 100)则返回的是大于等于1小于100的整数。使用decimal表示 128 位数据类型,和浮点数相比,具有更高的精度和更小的范围,适合于财务和货币计算。最后运行效果如下: