• 猴子请来的逗比项目流水总结


    一、美工相关

      因为2D sprite图片使用rotate翻转之后貌似碰撞器不会随之改变位置,所以使用scale * -1 来实现翻转,这样一来,3渲2出来的图片最好关于中心轴对称,否则翻转之后会出现如下问题:

      从游戏画面角度来说这个人物就好像瞬移旋转一样。

      使用程序初步解决了这个问题,在翻转前先往预定的方向移动一段距离来抵消翻转造成的瞬移。

      

    二、场景相关

      场景部分:

        水果buff 产生两次:因为人物有两个colider component,所以产生两次trigger enter event.

            

      

      人物运动部分  

      人物采用2D rigidbody component 的时候,才冰块组成的Wall上移动的时候,如果使用box colider 2d 碰撞器,由于物理模拟造成的棱角,导致运动的的中端。这个问题原因寻找了好久,因为当时的ice wall 拼接的时候非常严密,最后使用circle colider 2d 碰撞器解决这个问题。

    三、构架相关、

      

      后期重构参考:

        考虑地图,怪物,Boss在多个关卡有着惊人的相似度,如果考虑后期重构可以用一个场景来实现6个关卡,使用脚本动态加载6个关卡的不同个元素,6个关卡之间共享相同的元素。比如相同的怪物与Boss控制脚本等等,只在渲染上做动态加载。

      

          对象池:

          工作流程如下:

         

      注意,如果你采用了对象池来实现一个重复使用对象的方案,那么,当你把对象使用完毕后放入池子里的时候,拿出来时要注意恢复成你最初开始拿出来的样子,也就是恢复对象的变量,恢复对象的状态,断开与其他脚本的联系等等(当然你也可以再放入的时候恢复,这些都是一样的)

         总之一句话,你使用对象池拿出来容易,再放回去没这么容易,注意要恢复成原来的样子,最好自己内部脚本有一个放回的方法,里面实现一些恢复,然后由控制器来调用放回,比如与单例脚本断开链接,恢复子物体的样子。

       同样的,如果你产生也有一些初始化工作,我建议你也在内部脚本中实现,然后由控制器调用。(也就是在对象池产生回收的时候调用)

        

         你必须要记住的是OnEnable,OnDestory ,OnDisable 这些回调函数只是在物体active 变换的时候调用,本质上并不是对象池产生回收的时候调用的。这两者不是等价的。

        举个例子,OnEnable联系单例脚本,OnDisable断开单例脚本,退出编辑器,OnDisable 中访问已经销毁的单例脚本,是不正确的。

         又或者冰块被碰撞 active = false的时候你在单例脚本中记录下其坐标,那么你退出编辑器或者父物体set active false的时候同样也会有这些逻辑操.

          再或者当你生成对象池的时候,你却要在OnEnable OnDisable的时候进行对象初始化,完全没必要,浪费性能。

          本质上也好分析,这些函数也不是为了你回收创建的。

          总之对象池中的对象初始化和销毁工作功能写在对象自己的脚本中,由控制器调用,而不是系统函数调用,当然你也可以设定过一段时间自己回收然后调用,本质也是一样的。

      

        单例脚本:

      单例脚本分为如果自己带有一些资源比如贴图,预设,像BuffManager,或者不带预设,类似人物与怪物交互之间的单例脚本(可以做距离判断)。

      对于不是动态创建的单例脚本,如果访问单例脚本,不要一开始就访问, 因为你不能保证这个单例脚本会先于你之前创建。

      PS:一般第一种脚本我都不是动态创建 ,因为资源不是动态加载。

          如果需要一个跨场景并且带有资源的单例脚本,可以采用如下

    当加载一个新关卡时,所有场景中所有的物体被销毁,然后新关卡中的物体被加载进来。
    为了保持在加载新关卡时物体不被销毁,使用DontDestroyOnLoad保持,
    如果物体是一个组件或游戏物体,它的整个transform层次将不会被销毁,全部保留下来。
    
    // survive when loading a new scene.
    // Make this game object and all its transform children
    //当加载新场景的时候,使游戏物体和它所有的transform子物体存活下来
    function Awake () {
        DontDestroyOnLoad (transform.gameObject);
    }

          多平台信号控制

      考虑到游戏的跨平台性,我们必须要适应不同的输入设备,这个时候,如果输入设备的检测放在逻辑代码里是不现实的,那么如果输入设备改了,我们的逻辑代码也要去改,会很麻烦。那么我们可以把这个输入设备封装出来,不同输入设备有不同输入设备的信号,我们可以用c# 的委托来根据不同输入设备进行监听。

      引发对怪物的AI思考

         怪物的AI可以分为传感器的输入,内部的逻辑处理,最后的对应的表现,我们可以把传感器的输入独立出来,传感器的检测逻辑由怪物的行为逻辑监听,行为逻辑就可以喝检测逻辑分开,松耦合。

    四、脚本

      一:Awake在Enable之前。

      

      二、Time.scale = 0 的时候 游戏的声音不会停止播放,依旧会播放,比如游戏结束场景出来的时候,人物的的战斗声音如果比较长或者设置为loop,那么他就会一直播放。

          三、www 类不能ref 传入

          四、c#的委托对set active false的物体同样有效

      五、DateTime.Parse的日期格式如果要和服务器交互,那么你需要to String后转换成服务器的日期格式 本地默认的格式是不能直接提交的。

        项目中我都是先将本地的默认日期格式变为如下

        string DateTime = String.Format("{0:u}", Globals.Me.VipTime.Value);  //2010-10-04 17:38:01Z 再进行提交

        否则服务器不认识本地默认的日期格式就会变为 0000-00-0 00:00:00 同时DateTime.Parse 不能解析0000-00-0 00:00:00 就会发生异常

        

        日期格式大全如下:

        http://www.cnblogs.com/shaocm/archive/2012/08/15/2639998.html

      六、注意,如果你的按钮按下后会消失或者会加载场景,你这时候在按下的函数中处理服务器数据提交是不明智的,所以我们应该分层,等待数据提交完毕后按钮再消失。

     五、游戏引擎

      一、注意你不要在Animation Event 中试图 this.gameobject.SetActive(false),这会导致引擎奔溃。我花了差不多几个小时才找出来。

          注: 7.12 追加

                 这个BUG在4.5 版本中已经修复

      解决办法如下:

          http://answers.unity3d.com/questions/590945/gameobjectsetactivefalse-crashes-unity.html

         http://answers.unity3d.com/questions/595459/unity-43-crashes-setting-objects-inactive.html

         

        

          Unity apparently does not like it when the gameObject the animationEvent happens on gets disabled in the middle of the event or something similar.

    A workaround could be to use a coroutine to wait for the end of the frame instead of disabling immediately:

    public void OnAnimationEnd() {
       StartCoroutine("Delay");
    }
     
    private IEnumerator Delay() {
       yield return new WaitForEndOfFrame();
     
       foreach (Transform level in levels.transform) {
          level.gameObject.SetActive(false);
       }
    }

    或者Or only disable the SpriteRenderer to hide the Sprite:

    private bool isAnimationCompleted;
     
    public void AnimationCompleted()
    {
        isAnimationCompleted = true;
    }
     
    void Update() 
    {
        if(isAnimationCompleted)
        {
            isAnimationCompleted = false;
            gameObject.SetActive(false);
        }
    }

      二、Sprite Packer 能不用就最好不用,他本身的在编译器运行的时候会计算一次,非常消耗内存,Draw Call 不到100不用搞这个。

          下面这些人也碰到了这些问题

      http://issuetracker.unity3d.com/issues/sprite-pack-crash

          http://forum.unity3d.com/threads/sprite-packer-runs-out-of-memory.229999/

      三、Application.persistentDataPath is empty

      自己多个判断即可

      

    static string FileName
    {
    	
    	get {
    		if (Application.persistentDataPath.Length == 0)
    		{
    			return GameConfig.PresistentDataPath+"/game_9.dat";
    		}
    		else { 
    			
    		}
    		return Application.persistentDataPath + "/game_9.dat"; }
    } 
    

       三、关于Unity3D内存加载

      网上流程很广的那篇有关两篇Unity3D内存加载,看完之后,受益颇多,但是其实有几句话我亲自测试是有问题的,如下:

       

    存在3种加载prefab的方式:
    一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
    二是Resource.Load,Load以后instantiate
    三是AssetBundle.Load,Load以后instantiate 
        三种方式有细节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部assets都加载,
        instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会包含加载引用类assets的操作,
    导致第一次加载的lag。 官方论坛有人说Resources.Load和静态引用是会把所有资源都预先加载的,反复测试的结果,静态引用和Resources.Load也是OnDemand的,用到时才会加载。

     亲自测试,第一种静态引用在场景加载的时候就会把所有的资源加载进内存,然后Instantiate的时候再进行克隆+引用

     

     结果场景一加载就闪退。ProFiler

     第二种说法,我倒是比较纠结的是Resources.load的时候他是把整个目录下的资源打包做一个缺省AssertBundle引入内存load呢?

     还是只是把自己引入内存load呢?

     经过测试,应该是只有自己引入内存。

    写了一段脚本,测试如下

    public class testtest : MonoBehaviour
    {
    
        public float Timer = 0;
        GameObject[] tmp = new GameObject[Enum.GetValues(typeof(EnemyId)).Length];
        void Awake() {
            StartCoroutine(MyUpdate());
        }
    
        IEnumerator MyUpdate() {
            while (true)
            {
                int RandomIndex = UnityEngine.Random.Range((int)EnemyId.YangJian, Enum.GetValues(typeof(EnemyId)).Length);
    
                string MonsterName = "_Monster/" + ((EnemyId)RandomIndex).ToString();
                print(MonsterName);
    
                if (tmp[RandomIndex] == null)
                {
                    tmp[RandomIndex] = Resources.Load(MonsterName) as GameObject;
                    
                }
                yield return new WaitForSeconds(5.0f);
                Debug.Log(Time.time.ToString());
    
                GameObject tmp1 = Instantiate(tmp[RandomIndex]) as GameObject;
    
                yield return new WaitForSeconds(2.0f);
                Debug.Log(Time.time.ToString());
                
            }
        }
        
    
    }
    

     

    从上图可以看出,他的内存是一个峰值接着一个峰值,测试结果是对的。

    六、项目管理

      一些底层的数据比如怪物的属性等等,随着策划的不同而修改,有可能会降低难度,有可能有时候会提高难度,我们可以提供给策划一个excel表格,让策划填写,我们只需要拿着这个excel表格解析出来即可……

  • 相关阅读:
    json页面解析
    map判断
    将页面中所有的checkbox设成单选得
    配置两个环境变量:
    一个input框边输入,另外一个input框中边显示的触发事件
    页面tr和td的的隐藏与显示
    判断声明出来的list为空的时候,list!=null
    从一个表中往另外一个表中插入数据用到的SQL
    final使用方法
    Android学习笔记(23):列表项的容器—AdapterView的子类们
  • 原文地址:https://www.cnblogs.com/chongxin/p/3810892.html
Copyright © 2020-2023  润新知