前面一段时间学习了一些AI的东西,目测接下来的一年要围绕Unity和C#做事情了。由于以前Unity与C#学习的就只是入门水平,且一年多没碰,基本都忘了,而现在突然又要承担起Unity开发的重任,Unity引擎和C#编程语言就需要多多学习了。由于我自己一个人处理这方面的事情,所以预计会记录学习各种情况,比较繁杂或者基础的,毕竟不是资深程序员带着我,基本导师靠学习网上教程等。
学习一些就会记录更新一些,并不会像书籍一样按照各个系统进行详细学习介绍,主要还是依据工作中的需要和解决问题过程进行学习和总结。有新的要记录积累的东西我会更新文章,如果太长了就重新开一篇继续。下面就准备进入正题吧。
01 协程中调用异步方法
如我有一个异步方法,需要下载一些东西比较费时,如下:
async Task LoadAudio()
{
InitSDK();
await GetDataFromUrl(url);
}
那么在协程中调用可以用如下方法:
IEnumerator LoadPrefab()
{
var task = LoadAudio();
yield return new WaitUntil(() => task.IsCompleted || task.IsFaulted || task.IsCanceled);
yield return new WaitForSeconds(0.3f);
}
如果该任务调用混乱,可是试着用:
yield return new WaitUntil(() => isLoadAudioFinished == true);
这里LoadAudio完成后设置标志位为true,保证完成后才执行后面的内容。
02 音频加密
音频加密的需求很多时候会遇到,如果用C#的加密供应商进行流加密会耗费较多时间。尝试验证后,发现对音频混入一些byte即可对音频加密,如:
byte[] tmp = File.ReadAllBytes(path);
byte[] enc = new byte[tmp.Length + 64];
for(int i = 0; i < 64; i++)
{
enc[i] = tmp[i];
}
for(int i = 64; i < 128; i++)
{
enc[i] = byte.MinValue;
}
Array.Copy(tmp, 64, enc, 128, tmp.Length - 64);
File.WriteAllBytes(pathNew, enc);
这里对ogg格式音频,拷贝前64个字节后自己加入64个字节,然后再加入剩余内容。这样即可实现简单加密,不过要注意不同格式音频的解析不同,这种方法对wav格式音频却无法加密,wav格式加密可以在文件头直接加两个字节。
03 AVPro播放流媒体
Unity上视频播放插件可能有不少,AVPro算是大名鼎鼎了,我也是主要用到这个。在播放流媒体的时候,遇到一个问题。PC平台上,AVPro可以边缓冲边播放,IsBuffering和IsPlaying可以都是true。安卓平台由于没有选择ExoPlayer,默认用了Media Player这套视频播放API,出现的问题是,第一次加载视频,可以较快从http服务器获取视频数据,缓冲好了就播放。但是其播放过程中无法同时缓冲,导致播放完成一段后会停止,然后继续缓冲。这还不是主要的,主要问题是第二次缓冲加载视频的时候很快速度降到0,GetBufferingProgress也就停止了。这里我用的AVPro是最新的1.9.4,安卓是8.1版本,视频来自http协议mp4格式文件。
在后续学习中得知,ExoPlayer这套API更合适:
实际使用中,换成了ExoPlayer后解决了以上问题。也就是,播放过程中,仍然可以进行缓冲。如果速度慢,视频停止播放,后续缓冲不会像Media Player的那样没速度而一直卡着。
另外,AVPro播放在线mp4文件获取缓冲进度是正确的,但是m3u8文件在线播放却无法获取正确的缓冲进度。虽然都缓冲好播放了,但是GetBufferingProgress却是0,IsBuffering也是true。
关于ExoPlayer和Media Player的比较如下:
04 C# 委托Action等
C#有个委托是Action,该委托是没有返回值的。那么其参数呢,可以有也可以没有。
比如这样一段代码:
public void ResetTransform<T>(List<T> animList, Action endAction) where T : AnimNode
{
if (animList.Count == 0) return;
animList.ForEach((a) =>
{
a.ResetTransform();
});
if (endAction != null)
endAction();
}
这里Action是无参的,我们直接执行endAction()即可。where在这里就起到了基类约束的作用,也就是泛型T必须是以AnimNode为基类才行。
Action委托可以有参数的,如:
public List<Action<bool, PointerEventData>> onClickEventList = new List<Action<bool, PointerEventData>>();
这里就是有参数的委托,而且是俩参数,其实最多可以传16个参数。那么用的时候就是这样的:
onClickEventList.ForEach(e => e(IsActived, eventData));
ForEach()本身需要传一个委托,比如有一个void函数Print()和一个List<String> names,可以用names.ForEach(Print)来输出该List的内容。如果不想写Print,那就用匿名方法(来自C#2.0),如:
names.ForEach(delegate(String name){Console.WriteLine(name);});
这里delegate部分可以简写为 (x) => { ... },这个也就是lambda表达式了,括号里面的是参数。没有参数就是 () => 的形式,多个参数就是 (x, y, z) => 的形式。前面的代码中出现的e是个参数,单个参数可以写成括号包含的形式,也可以不加括号。所以前面的 e => 就是 (e) => 。另外由于e这个参数是个委托,所以直接就是e(x, y)这样的方式使用。
普通使用委托,如:
public delegate void MyDelegate(string name);
public MyDelegate myDelegate;
就和类差不多,先定义后实例化。Action就会方便一些,如:
void Start(){Action action = test; action();}
void test(){Debug.Log("test");}
这里直接用Action替代了前面的定义,也就是系统已经帮我们定义好的委托了。
基于Action或者委托,就可以做事件Handler了,如:
public event Action<SwipeDir> SwipeEventHandler;
那么别的类可以订阅该事件:
Test_SceneMgr.Instance.SwipeEventHandler += Instance_SwipeEventHandler;
private void Instance_SwipeEventHandler(Test_SceneMgr.SwipeDir obj){}