读一读

在手机上,Unity的主线程好像并不能够后台运行的,但是其他线程却可以的。例如接收socket网络包的线程是正常运作的,它把包一个一个的加到队列中,当切换回来游戏的时候,主线程运作就会把一个个网络包取出然后运行起来,这些包一股脑的一下子运行起来,如果有很多个协成特别是会冲突的协成就会一股脑的运行起来,从而造成各种图形错乱逻辑错误的问题。

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

public class TestBackRunning : MonoBehaviour {

    Transform go;
    //模拟网络收包
    Queue<Vector3> q = new Queue<Vector3>();

    void Awake () {
        go = GameObject.Find("Cube").transform;
        Application.runInBackground = false; //设置为后台不运行,模拟手机上的情况
        Thread t = new Thread(RunThread);
        t.Start();
    }

    void RunThread()
    {
        //在一个线程中往队列里添加网路包
        q.Enqueue( Vector3.up );
        Thread.Sleep(5000);
        q.Enqueue( Vector3.down);
        Thread.Sleep(3000);
        q.Enqueue( Vector3.left );
        Thread.Sleep(1000);
        q.Enqueue(Vector3.up);
    }

    IEnumerator Up()
    {
        Debug.LogError("运行上");
        int frame = 10;
        for (int i = 0; i < frame; i++)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y + 0.1f, transform.position.z);
            yield return new WaitForEndOfFrame();
        }
    }

    IEnumerator Down()
    {
        Debug.LogError("运行下");
        int frame = 20;
        for (int i = 0; i < frame; i++)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y - 0.1f, transform.position.z);
            yield return new WaitForEndOfFrame();
        }
    }

    IEnumerator Left()
    {
        Debug.LogError("运行左");
        int frame = 10;
        for (int i = 0; i < frame; i++)
        {
            transform.position = new Vector3(transform.position.x + 0.1f, transform.position.y, transform.position.z);
            yield return new WaitForEndOfFrame();
        }
    }

    private void Update()
    {
        while (q.Count > 0)
        {
            Vector3 dir = q.Dequeue();
            if (dir == Vector3.up)
            {
                StartCoroutine(Up());
            }
            else if (dir == Vector3.down)
            {
                StartCoroutine(Down());
            }
            else if (dir == Vector3.left)
            {
                StartCoroutine(Left());
            }
        }
    }
}

在这个测试脚本中,使用一个线程往一个队列里面添加一些命令,然后在Update(主线程中)去取出包来执行,每一个命令都会运行一个协成并且是操作的同一个对象。运行游戏,然后切换到其他窗口,等几秒钟回来之后就会一下子调用三个协成,物体就会往左上方向走,这其实就是已经错乱了,本意是一个协成接一个协成运行的,现在一下子全部运行起来就不行了。


我所想到的方法就是,在后台运行时所需要执行的协成都不执行,都直接运行到协成的结束状态。回来的时候才允许调用协成动画(注意要延迟一帧设置这个允许状态,因为在回来那一帧中,所有的协成(后台运行时需要执行的)都是不允许调用的,一股脑太多的异步会造成混乱,所有都是直接到目标状态的话就变成一个队列的线性执行了,这样就不会造成混乱了)。当然,这样子做的话会造成时间上的不对应,但是也比回来时逻辑错乱的强得多。

private void OnApplicationPause(bool pause)
{
    if (pause)
    {
        isShowCoroutine = false;
        //将当前播放的协成到最终状态,并停止协成的运作
        switch (currentCorState)
        {
            case CoroutineState.Start:
                EndStartCoroutine();
                break;
            case CoroutineState.HSZ_CHANGE:
                EndHSZChangCoroutine();
                break;
        }
    }
    else
    {
        //可能是暂停了,回来的时候就继续执行,所以延迟设为true
        StartCoroutine(EnableShowCoroutine());
    }
}

//恢复可以播放动画了
IEnumerator EnableShowCoroutine()
{
    yield return null;//一帧之后在将允许播放协成切换回来
    isShowCoroutine = true;
    ChangeCoroutineState( CoroutineState.OTHER );
}

if (isShowCoroutine)//允许播放协成的情况下
{
    StartCoroutine(GiveCardAni(MyCardCount, cbSiceFirst, cbSiceSecond));
    ChangeCoroutineState(CoroutineState.Start);
}
else
{
    //不然直接设置到目标
    EndStartCoroutine(false);
}

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class ShapeFactory : MonoBehaviour {

    Scene poolScene;
    int loadedLevelBuildIndex = 0;

    void CreatePools()
    {
        //Scene是结构体
        if (Application.isEditor)
        {
            //通过名字获取场景
            poolScene = SceneManager.GetSceneByName(name);
            if (poolScene.isLoaded)//判断场景是否加载了
            {
                //获取场景中所有的root物体
                GameObject[] rootObjects = poolScene.GetRootGameObjects();
                return;
            }
        }
        
        //动态创建一个新场景
        poolScene = SceneManager.CreateScene(name);
    }

    IEnumerator LoadLevel(int levelBuildIndex)
    {
        enabled = false;
        if (loadedLevelBuildIndex > 0)
        {
            //异步卸载index场景 index为buildsetting中右边的数字
            yield return SceneManager.UnloadSceneAsync(loadedLevelBuildIndex);
        }

        //异步以叠加的方式加载场景
        yield return SceneManager.LoadSceneAsync(levelBuildIndex, LoadSceneMode.Additive);
        //设置这个场景为激活状态,表示实例化的物体放到这个场景下和使用这个场景的灯光设置
        SceneManager.SetActiveScene( 
            SceneManager.GetSceneByBuildIndex(levelBuildIndex)
            );
        loadedLevelBuildIndex = levelBuildIndex;
        enabled = true;
    }

    public void SetInScene(GameObject go)
    {

        if (poolScene.isLoaded)
        {
            //将物体放到某个场景中
            SceneManager.MoveGameObjectToScene(go, poolScene);
        }
    }
}

TextMesh的3D文字会穿透过3D模型是因为使用的是默认的Font Material造成的,因为这个材质使用的是GUI/Text Shader所以会显示在上面,解决办法就是换一个材质和Shader了。

创建一个Shader:

//一个固定管线Shader 会编译为顶点着色Shader
Shader "Custom/3D Text Shader" {
	Properties{
		_MainTex("Font Texture", 2D) = "white" {}
		_Color("Text Color", Color) = (1,1,1,1)
	}


	SubShader{
			Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
			//只要没有关闭ZTest深度测试,TestMesh就不会出现穿透现象
			Lighting Off Cull Off ZWrite On Fog{ Mode Off }
			Blend SrcAlpha OneMinusSrcAlpha
			Pass{
			Color[_Color]
			SetTexture[_MainTex]{
			combine primary, texture * primary
			}
		}
	}
}

创建一个材质使用这个Shader,将材质拖给Mesh Renderer使用,在材质上赋值正确的Font Texture就可以了。Font Texture可以将ttf字体的Character设置为Unicode得到


今天遇到一个加载Streamed AssetBundle(关卡场景)后加载该场景,加载完成后,调用AssetBundle.Unload(false)卸载镜像文件时,声音文件也被卸载掉了。寻求各种办法都解决不了,最后的办法就是在场景还在是不卸载AssetBundle,在退出场景的时候再调用Assetbundle.Unload(true)卸载全部。

2018.png


有些情况我们需要使用自定义的宏来做一些东西的开关,例如测试AssetBundle的加载等

private void Awake()
{
    #if TEST_HONG  //如果定义了这个宏,就会调用
            Debug.LogError("hahaha");
    #endif
}


定义这个宏,打开File/Build Settings/PlayerSettings/Other Settings/Scripting Define Symbols下的输入框输入宏,用;好区分多个控,按回车键确定,注意你要在哪个平台定义

blob.png


UNITY_EDITOR  编辑器下执行的代码
UNITY_EDITOR_WIN  Windows平台下编辑器执行
UNITY_EDITOR_OSX  OSX平台编辑器执行
UNITY_STANDALONE_OSX  Mac主机平台
UNITY_STANDALONE_WIN  Win主机平台
UNITY_STANDALONE_LINUX  Linux主机平台
UNITY_IOS  苹果手机
UNITY_IPHONE  弃用. 使用UNITY_IOS替代
UNITY_ANDROID   安卓平台调用
UNITY_WEBGL  WEBGL平台

  

UNITY_5  Unity5++版本,所有5.x.x版本
UNITY_5_0  Unity5.0+版本,所有5.0.x版本
UNITY_5_0_1  Unity 5.0.1版本

有时候,我们需要public一个二维数组来赋值,就例如很多类型的声音,每种类型又有很多的子声音,这时候就需要二维数组来解决了,可是public的二维数组并不会显示在Inspector中,就不能拖拉式的赋值了。剩下就只能Resource了,或则远程了。

其实我们可以用一个类来充当数据结构,然后声明为可序列化的,里面的数据为数组,而在客户端中声明这个数据结构类为数组,这样就完美解决了。

[Serializable]
public class MajiangSAudio {
    public AudioClip[] audios;
    public AudioClip[] localAudios;
}

//使用
public MajiangSAudio[] _ManSound = new MajiangSAudio[40];


blob.png


using UnityEditor;
using System.IO;

public class TipDataAssetBundleName  {

    [MenuItem("Resource/2.Tip AssetBundleName")]
    public static void TipAssetBundleName()
    {
        DirectoryInfo directoryInfo = new DirectoryInfo(EditorConstConfig.TipDataPath);
        if (!directoryInfo.Exists)
        {
            UnityEngine.Debug.LogError("Tip DIR not exists");
            return;
        }

        string[] dirs = Directory.GetDirectories(EditorConstConfig.TipDataPath);
        foreach (string dir in dirs)
        {
            string name = Path.GetFileName(dir);
            TipAssetBundleName(dir, name);
        }

        AssetDatabase.Refresh();
    }

    private static void TipAssetBundleName(string directory, string name)
    {
        if (name.IndexOf("/") == -1)
        {
            //再深一层
            string[] dirs = Directory.GetDirectories(directory);
            foreach (string dir in dirs)
            {
                name = name + "/" + Path.GetFileName(dir);
                TipAssetBundleName(dir, name);
            }
        }
        else
        {
            string[] files = Directory.GetFiles(directory);
            foreach (string file in files)
            {
                if (file.EndsWith(".meta")) continue;
                string path = file.Substring(file.IndexOf("Assets")).Replace('\\','/');
                AssetImporter importer = AssetImporter.GetAtPath(path);
                importer.assetBundleName = name;
                importer.assetBundleVariant = "assetbundle";
            }
        }
    }
}


这里的菜单是2,其实在我写的资源管理中还有一步就是将资源复制到专门用来标志Assetbundle名字的单独文件夹中,然后调用这个会遍历这个文件夹,按照文件夹的名字来给资源添加AssetBundle名字,第一层目录是资源的大分类,第二层目录才是装资源的,例如Lua/login文件夹,login下的资源文件的AssetBundle名字就是lua/login。Lua文件夹下不保存任何资源,只做分类,这只是规定而已,也可以标志为lua,个人爱好。


1.创建事件类型,继承自IEventSystemHandler

using UnityEngine.EventSystems;
public interface IMyEvent : IEventSystemHandler {
    void MyMessage();
}

2.实现这个自定义的接口表示监听这个事件类型

using UnityEngine;

public class EventTesta : MonoBehaviour,IMyEvent {

    public void MyMessage()
    {
        Debug.LogError("接收到我的消息");
    }
}

3.触发事件

using UnityEngine;
using UnityEngine.EventSystems;

public class OnCubeClick : MonoBehaviour,IPointerClickHandler {

    //监听了这个事件的目标
    private GameObject target;

    public void OnPointerClick(PointerEventData eventData)
    {
        //触发事件,目标,参数,执行
        ExecuteEvents.Execute<IMyEvent>(target, null, (x, y) => x.MyMessage());
    }

    private void Awake()
    {
        target = GameObject.Find("Main Camera");
    }
}

using UnityEngine;
using System.Collections;
public class LogTEST : MonoBehaviour { 

    void Start () {        
        //在一个日志信息上注册一个委托来被调用
        Application.RegisterLogCallback (MyLogCallback);
    }

    
    /// log callback check
    /// </summary>
    /// <param name="condition">log内容.</param>
    /// <param name="stackTrace">log产生的栈数据追踪</param>
    /// <param name="type">log的类型.</param>
    void  MyLogCallback (string condition, string stackTrace, LogType type){}
}


在MyLogCallback里面会接受到所有的Debug输出的日志,我们可以在这里整合信息,保存起来,然后使用GUI显示出来,实现一个运行时的Console。也可以使用WWWForm来发送错误报告,在服务端生成多用户的日志报告,分析情况等。