2017-01-19 14:50:51 hemiaoyuan1989 阅读数 3243
  • Unity3D优化技术讲解

    该课程使用的Unity2018,主要内容是关于游戏优化方面的讲解,不同于以往的讲解,该课程首先给读者分享了几个案例:自定义Mesh实现,利用GOU几何绘制实现草的编辑,为了让读者更好的理解优化技术,给读者深入分析了优化涉及到的三个方面:CPU,GPU,内存,以及在产品设计上需要 关注的点。希望通过该课程的学习能够帮助读者在优化方面进一步提升。

    150 人正在学习 去看看 姜雪伟

参考雨松momo 的《Unity3D研究院之IOS自定义游戏摇杆与飞机平滑的移动(十一)》一文

地址:http://www.xuanyusong.com/archives/526

文中的JoyJoystick脚本是Unity自带的比较旧的脚本,我这儿是针对Unity新的脚本进行分析;

Joystick.cs是官方提供的脚本,具体代码如下:

using System;
using UnityEngine;
using UnityEngine.EventSystems;

namespace UnityStandardAssets.CrossPlatformInput
{
	public class Joystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
	{
		public enum AxisOption
		{
			// Options for which axes to use
			Both, // Use both
			OnlyHorizontal, // Only horizontal
			OnlyVertical // Only vertical
		}

		public int MovementRange = 100;
		public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use
		public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input
		public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input

		Vector3 m_StartPos;
		bool m_UseX; // Toggle for using the x axis
		bool m_UseY; // Toggle for using the Y axis
		CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input
		CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input

		void OnEnable()
		{
			CreateVirtualAxes();
		}

        void Start()
        {
            m_StartPos = transform.position;
        }

		void UpdateVirtualAxes(Vector3 value)
		{
			var delta = m_StartPos - value;          
			delta.y = -delta.y;
			delta /= MovementRange;

            //圆形           
            if (Vector3.SqrMagnitude(delta)>1)
            {
                delta = delta.normalized;
            }
           // Debug.Log(Vector3.SqrMagnitude(delta));


            if (m_UseX)
			{
				m_HorizontalVirtualAxis.Update(-delta.x);
			}

			if (m_UseY)
			{
				m_VerticalVirtualAxis.Update(delta.y);
			}
		}

		void CreateVirtualAxes()
		{
			// set axes to use
			m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal);
			m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical);

			// create new axes based on axes to use
			if (m_UseX)
			{
				m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName);
				CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis);
			}
			if (m_UseY)
			{
				m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName);
				CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis);
			}
		}


		public void OnDrag(PointerEventData data)
		{
			Vector3 newPos = Vector3.zero;

			if (m_UseX)
			{
				int delta = (int)(data.position.x - m_StartPos.x);
				delta = Mathf.Clamp(delta, - MovementRange, MovementRange);
				newPos.x = delta;
			}

			if (m_UseY)
			{
				int delta = (int)(data.position.y - m_StartPos.y);
				delta = Mathf.Clamp(delta, -MovementRange, MovementRange);
				newPos.y = delta;
			}
			transform.position = new Vector3(m_StartPos.x + newPos.x, m_StartPos.y + newPos.y, m_StartPos.z + newPos.z);
			UpdateVirtualAxes(transform.position);
		}


		public void OnPointerUp(PointerEventData data)
		{
			transform.position = m_StartPos;
			UpdateVirtualAxes(m_StartPos);
		}


		public void OnPointerDown(PointerEventData data) { }

		void OnDisable()
		{
			// remove the joysticks from the cross platform input
			if (m_UseX)
			{
				m_HorizontalVirtualAxis.Remove();
			}
			if (m_UseY)
			{
				m_VerticalVirtualAxis.Remove();
			}
		}
	}
}

我这里是对原文Main.js 的改编,我这里用的语言是C#:

using UnityEngine;
using System.Collections;
using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.UI;
public class Main : MonoBehaviour {
    public Joystick moveJoystick;
    public Image plan;//飞机贴图
    //飞机在屏幕中的坐标
    float x = 0.0f;
    float y = 0.0f;
    //避免飞机飞出屏幕,分别是X,Y最大坐标 ,最小坐标是0,0
    float cross_x = 0;
    float cross_y = 0;

    float planSpeed = 20.0f;//飞机移动速度

    // Use this for initialization
    void Start()
    {
        x = 200.0f;
        y = 200.0f;

        cross_x = Screen.width - plan.GetComponent<RectTransform>().rect.width * 0.5f;
        cross_y = Screen.height - plan.GetComponent<RectTransform>().rect.height*0.5f;     
    }

    // Update is called once per frame
    void Update()
    {
        //得到游戏摇杆的反馈信息  得到是-1到1之间  
        //官方CrossPlatformInput核心是提出了PC和手机输入的兼容方案
        float touchKey_x = CrossPlatformInputManager.GetAxis(moveJoystick.horizontalAxisName);
        float touchKey_y = CrossPlatformInputManager.GetAxis(moveJoystick.verticalAxisName);
        //摇杆向左
        if (touchKey_x<0)
        {
            x -= planSpeed;
        }
        else if (touchKey_x>0)
        {
            x += planSpeed;
        }
        //摇杆向xia
        if (touchKey_y <0)
        {
            y -= planSpeed;
        }
        else if (touchKey_y >0)
        {
            y+= planSpeed;
        }

        //防止飞机飞出屏幕,出界检测
        if (x < 50)
        {
            x = 50;
        }
        else if (x > cross_x)
        {
            x = cross_x;
        }

        if (y < 150)
        {
            y = 150;
        }
        else if (y > cross_y)
        {
            y = cross_y;
        }
        plan.GetComponent<RectTransform>().position= new Vector3(x,y,0);
    }
}
这里需要注意的几点是:

引用需要增加

using UnityStandardAssets.CrossPlatformInput;
using UnityEngine.UI;

另外就是获取游戏摇杆的反馈信息这儿

 float touchKey_x = CrossPlatformInputManager.GetAxis(moveJoystick.horizontalAxisName);
        float touchKey_y = CrossPlatformInputManager.GetAxis(moveJoystick.verticalAxisName);



2019-02-22 02:26:06 qq_26142201 阅读数 2477
  • Unity3D优化技术讲解

    该课程使用的Unity2018,主要内容是关于游戏优化方面的讲解,不同于以往的讲解,该课程首先给读者分享了几个案例:自定义Mesh实现,利用GOU几何绘制实现草的编辑,为了让读者更好的理解优化技术,给读者深入分析了优化涉及到的三个方面:CPU,GPU,内存,以及在产品设计上需要 关注的点。希望通过该课程的学习能够帮助读者在优化方面进一步提升。

    150 人正在学习 去看看 姜雪伟

**

关于安卓(apk)unity3d游戏汉化简单做一些全面分析

**
先上相关汉化的工具
链接:https://pan.baidu.com/s/1BmZ4dVmsrjVjcL8R82xRHg
提取码:o0vb

汉化的游戏资源(暗影之日):
我自己稍微多加了一些翻译和修复了一个日志界面bug,去除了广告,喜欢这款游戏可以去下载来玩哦,装备汉化不了,还没找到解决方案,这款游戏其实不怎么火,自己比较喜欢吧(汉化是游乐园完成的,尊重版权)。
游戏链接:
链接:https://pan.baidu.com/s/1PGtViFEwphKXVOD9XN5SRA
提取码:t2xk

——————————————————————————————————————————
自己去找需要什么(AssetStudio.v0.12.44自己把软件汉化了,不习惯英语的可以去下载)
我自己是放假没事干,只会点基础的C语言,其他什么都不懂最近突然想起自己喜欢玩的一部游戏(暗影之日)由于汉化不完全自己去网页上找了好多的资料,以为网上都没说得怎么详细,东拼西凑的找了几天网页,才开始找到地方下手。关于安卓unity游戏汉化,如果游戏较大,自己想一个人汉化还是放弃吧,游戏小的话倒是可以自己一个人去动手汉化,看我的教程只是大体思路,需要汉化的就这几样,还有汉化的软件没有加壳很关键,要不然很难下手。Assembly-CSharp.dll 文件,这个文件很多的游戏都是可以跳过这里来进行修改,达到汉化的目的的。这是AssetStudio预览看到的textasset文件这就是unity3d游戏主要汉化的地方,当然有些软件不同汉化的地方可能还要多一点,加入res目录下的xml,Texture 2D图片之类的。
——————————————————————————————————————————

1.apktool工具
工具不说了网上和上面的链接都有,下面我用暗影之日这款游戏进行讲解,其实这款游戏没有汉化完全(游乐园汉化的),自己去想去汉化完全~~

命令
win + R 进入 cmd
cd
d:
cd\Android\apktools
apktool d -r 1.apk(可替换资源)
apktool d 1.apk(不可替换资源)
重新打包
apktool.bat b -o 12.apk save(用的这个命令) 12:新包名 save :反编译得到的文件目录
apktool.bat d -f test.apk -o test(下面这个没用过) apktool -f [待反编译的apk] -o [反编译之后存放文件夹]

请看这个
在这里插入图片描述
这是反编译得到的文件.dll文件,说一下这些文件只要是游戏运行时对资源进行调用和处理的主要文件

2.Dnspy工具
~~使用.Dnspy工具 对Assembly-CSharp.dll 文件进行相关的地方修改 ,看我的截图自己去摸索哪些地方需要汉化, 这里不说了。

" "的内容一般都可以汉化,当然如果不是string类的内容是一般不可以汉化的,当然非string的内容也是可以汉化的只要功夫高,里面.dll全改成中文都没问题,注意汉化错误可能会引起游戏运行出现问题的哦。
汉化完成就可以把dll文件,去替换反编译目录下的文件完成,按上面的cmd命令进行打包。然后,我用的是apk编辑器签名的,cmd运行生产的apk是没有签名的需要签名才能安装,在电脑安一个安卓模拟器方便调试和替换文件,用apk编辑器要这样调才方便替换和修改文件。
安装模拟器和apk编辑器

这是汉化玩dll文件了,当然你可能会问为什么汉化了缺门牙显示中文,这就需要去制作字体文件了,关于dll汉化说到这里。上一下关于字体文件是什么, 自己去百度看一下教程怎么制作吧,字体文件很关键,你汉化完成,如果没做字体文件那都是白做了。
字体文件:
在这里插入图片描述

3. assetstudio工具
关于这个工具,是用来查看.assets文件里面的内容的, unity游戏一般反编译的目录下都会有一个目录下面有这些文件:
asset文件

这些文件跟你说一下sharedassets0.assets.split0这个文件和sharedassets0.assets.split1是用工具切分出来,利用工具可以合并起来就是一个文件,好像这样切分assets文件可以提升游戏的运行速度,其实sharedassets0.assets这个文件切不切分队游戏的运行没有太大影响,不是说如果我吧文件合并了,游戏就不运行了,照样运行。使用assetstudio工具可以打开这个文件,进行提取,不能替换哦,还可以预览很多的内容加入图像啊,声音什么的,文本啊,都可以看到。

4.AssetsBundleExtracto工具
这个工具简称UABE是用来替换assets里面的文件用的,假如你把Textasset导出的文本汉化好了,就可以用这个工具去进行替换了,看一下Textasset:
在这里插入图片描述

说一下,如果你的这个这个游戏没有数据包,也就是obb,比较大的unity游戏一般都会有的,用压缩工具可以进行解包得到一大堆的文件 ,.resS这个是声音的文件,我们看的这是里面的asset文件和level文件就行了,level文件是场景文件,一般比assets少一个。.level,.assets这两个文件包含了游戏基本的我有游戏资源,扎到相关的Textasset汉化替换之后就只剩最后一步工作了,别忘了,提醒一下,.assets文件里含有相关的贴图文件是用来显示字体的也是需要替换的,没替换就显示不了,中文哦。看一下这个文件,跟主题文件类似。这个 贴图是用来3D场景里面的字,上面我们也说过主题文件那个是用来显示菜单的主题文件,这个则是游戏来面需要的字体文件。
自己看着替换,不懂的多百度字体文件的就不说了。
当图片中有英文是:
在这里插入图片描述
举个例子这里并不需要汉化

Tip: 汉化完成用压缩工具打开原来没有汉化的obb文件,之后对你修改的assets文件替换相关的assets文件进行替换,因为用压缩工具把文件压缩回去是用不了的,必须替换才行。替换完成,就看下面。

5.obb—md5工具
每个游戏的数据包,也就是obb文件都有一个识别的代码的,就行我们用的身份证一样,我们就说是md5好了,有些人可能会遇到汉化完成,做好数据包,打开游戏却发现游戏加载一下就不动了,这是你的obb文件的识别码,和apk的setting.xml里的识别码 不一样,游戏就会一直卡在界面上,看图:
setting.xml
打开如下:
apk里面的md5
这里的md5就是apk目录下的那个md5,需要修改。说道修改,先教你怎么知道自己做好的obb的md5,做好了md5,再到apk里面的setting吧md5,替换掉再打包。这样就完事了,游戏就可以运行了,如果你修改的obb文件不对,游戏也可能运行不了,自己应该试一下大概的步骤,看都行不,不要一口气做完,却发现,不行了,整体的思考一下怎么做。
上面提到的安装模拟器,因为要调试游戏所以下一个模拟器比较好,这样能随时了解apk的状况,而且apk编辑器方便签名和替换文件超好用,我说的这个当然可以用来汉化其他软件什么的只要不加壳,外国的软件很少交壳,国内好像加壳较多,当然有高明的手短进行脱壳。

最后,说一下,怎么汉化最好,拿到游戏,先制作字体文件进行替换,然后汉化一两个字看看显示不,再下手,把dll汉化完成了,再汉化assets文件里面的textasset,如果游戏的需要汉化的是图片图片上有英文什么的,上面也有工具,一般是Texture2D贴图文件的类型文件,他的导出来的格式.pvr,汉化完成替换assets里面的相关文件, 再用压缩工具进行替换里面的assets,把你的修改过的asset或者level文件丢到里面去替换,然后改扩展名成obb,用md5工具获取md5替换apk里面的setting的md5apk打包,签名,汉化完成,补充一下,Texture2D 的字体贴图,贴图字体文件是Font 下面是图片。
在这里插入图片描述

顺便请教一下大佬,除了Textasset和贴图,dll汉化,还有什么地方需要汉化吗,这也是我遇到的这款游戏找不到游戏装备的文件来汉化,不知道藏哪里了。

在这里插入图片描述

2017-10-25 09:25:30 mmuu1987 阅读数 1536
  • Unity3D优化技术讲解

    该课程使用的Unity2018,主要内容是关于游戏优化方面的讲解,不同于以往的讲解,该课程首先给读者分享了几个案例:自定义Mesh实现,利用GOU几何绘制实现草的编辑,为了让读者更好的理解优化技术,给读者深入分析了优化涉及到的三个方面:CPU,GPU,内存,以及在产品设计上需要 关注的点。希望通过该课程的学习能够帮助读者在优化方面进一步提升。

    150 人正在学习 去看看 姜雪伟

 需求分析

        因项目需求,我们需要加载自定义的场景,这里的自定义是指,场景不会放在buildSetting里的场景,当然,不放在buildSetting里的场景,不算真的场景,只能是算半个,说白了就跟普通的assetBundle没啥区别,既然跟普通的加载方式没啥区别,那为何不叫加载资源呢,我们分析得出,既然是场景,肯定会有一些不同的地方,比如渲染设置(跟烘焙有关系的),环境设置,等等,这些就可跟一般的加载场景是有所区别的了。所以我们要做的是在加载资源的基础上,还原场景里面的设置。那就差不多等于是我们自己的加载场景了,这里用差不多,这几个字眼,其实我们是不可能百分百还原场景的设定的,因为官方没有在运行时提供相应的场景设置API。如图所示,给我们提供的API少得可怜,我们只能是提供多少就还原多少了。



       按上面的说话,既然场景打包可以这样搞,那我们就可以在加载资源或者叫加载场景的时候,分为两种方式

1,加载下一个场景的时候,可以把上一个场景的资源全部删掉,再重新设置下,下一个场景的场景设置,这样,就相当于切换场景了。

2,加载下一个场景的时候,调用官方提供的API,如图,然后再场景切换完成事件里,再加载下一个场景的资源和设定,听起来很麻烦,执行起来,更麻烦,我们现在就是用这种方法,发现一路都是坑,所以,强烈推荐第一种。下面我就说说我们用第二种遇到的坑(其实没必要往下看了,用第一种是最安全稳妥的,我们稳妥的设置好每个场景的设置就好


    我的做法就是,用Notepad++ 打开保存好的场景文件,细看里面的一些设置。场景烘焙打包,烘焙贴图打包是网上也有流程和方法,这里就不细说。如果有打开场景文件出现乱码的话,选择红线的方式即可。如图该设置可以在菜单Edit>ProjectSetting>Editor找到。可以看到场景文件无非就是记录场景里面的物体信息,每个物体的组件,组件里的参数,这些我们不用可以记下来,打包资源的时候,设定的参数都会随着资源一起打包出去,加载的时候直接加载就好了,比如旋转,缩放,位置等等,都不用我们操心。好了,那我们就好好的看看场景文件里面的带有setting的一些设置,

比如LithtmapSettings,还有RenderSettings等,其实我们按照场景文件所记录的设置,加载的时候设置好相应的参数就可以了,可惜,想法很美好!场景文件记录的参数有些不能够在发布后运行时设置,比如m_RenderSettings,还有LightmapSetting的很多参数,还有LightDataAssets,该资源就算你打包了,也没办法发布后运行时load进来然后解析设置参数,因为官方没有提供相应的API文档,所就如前面所说的,只能算是半个场景的打包,残次品,希望官方以后能够提供更多的关于场景打包方面的API给我们吧。所以,目前只能是API提供多少接口,我们就还原多少。


开发中遇到的坑

       5.6版本的跟5.3的SceneManager对比,有了蛮大的改动,5.6增加了几个事件,

一个是场景改变,一个是场景加载,一个是卸载后的,还有就是不能同步卸载场景了,只能异步卸载场景,不知道unity官方出于什么考虑才这样,感觉用起来麻烦了很多,或许我的这个需求太特别,才会用上去觉得很不爽吧。再者SceneManager.SetActiveScene的时候,先注册监听该事件,我是放在Start里




因为是自定义场景加载,所以我们代码创建一个新的场景,然后激活它,


如果你执行完上面的两句代码后创建物体,都会是在当前场景创建,并不会在激活的场景创建。这个坑应该官方提供了一个API解决,SceneManager.activeSceneChanged。 触发这个事件的条件是当场景切换完成的时候,也就是说,正式进入了激活的场景,所以,我们只能在这个事件触发后才可以在激活的场景里创建物体和卸载上一个场景的资源

      然而又来了一个新坑,卸载上一个场景资源是异步的,也就是当前场景加载完成后,如果你执行上一个场景卸载操作,然后把当前场景的烘焙贴图贴上去,很抱歉,unity也会立马当前场景的烘焙贴图删掉,也算是一个小BUG吧,补救办法就是延迟一帧后执行当前场景的烘焙贴图贴赋予的操作,这个操作并严谨,因为异步加载,并不确定什么时候unity会执行删除烘焙贴图的操作。但当我想到之前看到过文章说资源一般会在帧后面释放掉,我想,要是把当前场景的烘焙贴图放在yield return new WaitForEndOfFrame后面贴上去应该就没事了,料想到果真如此

      目前发现runtime全局光是跟LightDataAssets是紧密结合在一起的,当前unity版本LightDataAssets是无法动态加载,所以,实时全局光也不可能还原到,只能折中的利用灯光烘焙的贴图来代替,这是实时运行的API








      



2017-03-07 14:33:48 lHz76ttw1U 阅读数 586
  • Unity3D优化技术讲解

    该课程使用的Unity2018,主要内容是关于游戏优化方面的讲解,不同于以往的讲解,该课程首先给读者分享了几个案例:自定义Mesh实现,利用GOU几何绘制实现草的编辑,为了让读者更好的理解优化技术,给读者深入分析了优化涉及到的三个方面:CPU,GPU,内存,以及在产品设计上需要 关注的点。希望通过该课程的学习能够帮助读者在优化方面进一步提升。

    150 人正在学习 去看看 姜雪伟

 被人物编辑器折腾了一个月,最终还是交了点成品上去(还要很多优化都还么做)。

    刚接手这项工作时觉得没概念,没想法,不知道。后来就去看<<Unity5.X从入门到精通>>中有关于自定义编辑器(自定义Inspector和自定义Scene或GUI)的一些例子,还包括看了 雨松的编辑器教程 和 自定义结构显示在Inspector的方法 看完之后也实战了一下就算入了门,就分析自己项目的人物对应的数据,如下图:

 

上述数据其实很简单但是对于我这种初学者来说就有点难度,首先因为Actions 和 Frames(动作对应的帧集合) 需要有类似数组或者链表这些数据结构来存储。就去查了一些资料发现 几篇好的关于序列化和反序列化的博文 , 比如 大表哥的博文 提到 哪些数据能够序列化和反序列化 ,其中我们就可以用List<T>的结构来存储数据集合,关于为什么涉及到序列化和反序列化, 因为我们需要将一些数据保存到本地,而不是仅仅的放在内存中,再从本地取回到内存中就需要反序列化了。

  理解了上述的基础知识 ,便自己定义了特定的数据类(主要的):

复制代码
  1     [System.Serializable]
  2     public class CharacterEditorStateData : ISerializationCallbackReceiver
  3     {
  4         public string m_animationName;
  5         public int m_totFrame;
  6         public CharacterStateType m_stateType;
  7         [HideInInspector]
  8         public CharacterStateType m_oldStateType;
  9         [HideInInspector][NonSerialized]
 10         public List<CharacterEditorAttackData> m_attackDatas = new List<CharacterEditorAttackData>();
 11         [HideInInspector][SerializeField]
 12         public List<CharacterEditorBombAttackData> m_attackBmDatas = new List<CharacterEditorBombAttackData>();
 13         [HideInInspector][SerializeField]
 14         public List<CharacterEditorNormalAttackData> m_attackNmDatas = new List<CharacterEditorNormalAttackData>();
 15         public CharacterEditorAttackData IsFrameDataExist(int frame)
 16         {
 17             foreach (CharacterEditorAttackData dt in m_attackDatas)
 18             {
 19                 if (frame == dt.m_iFrame)
 20                     return dt;
 21             }
 22             return null;
 23         }
 24 
 25         public bool AddFrameData(int newFrame)
 26         {
 27             CharacterEditorAttackData dt = CharacterEditorAttackData.CreateData(CharacterAttackType.BOMB);
 28             if (dt == null)
 29                 return false;
 30             dt.m_iFrame = newFrame;
 31             dt.m_attackType = CharacterAttackType.BOMB;
 32             this.m_attackDatas.Add(dt);
 33             return true;
 34         }
 35 
 36         public bool RemoveFrameData(int oldFrame)
 37         {
 38             CharacterEditorAttackData dt = this.IsFrameDataExist(oldFrame);
 39             if (dt == null)
 40                 return false;
 41             this.m_attackDatas.Remove(dt);
 42             return true;
 43         }
 44 
 45         public void ChangeFrameData(int index , CharacterAttackType attType)
 46         {
 47             CharacterEditorAttackData dt = this.m_attackDatas[index];
 48             int iFrame = dt.m_iFrame;
 49             if (attType != dt.m_attackType)
 50             {
 51                 dt = CharacterEditorAttackData.CreateData(attType);
 52                 dt.m_iFrame = iFrame;
 53                 dt.m_attackType = attType;
 54                 this.m_attackDatas[index] = dt;
 55             }
 56         }
 57 
 58         public int GetNewFrame()
 59         {
 60             if (m_attackDatas.Count == 0)
 61                 return 0;
 62             int frame = -1;
 63             foreach (CharacterEditorAttackData dt in m_attackDatas)
 64             {
 65                 if (frame <= dt.m_iFrame)
 66                     frame = dt.m_iFrame;
 67             }
 68             if (frame == this.m_totFrame)
 69                 return -1;
 70             return frame + 1;
 71         }
 72 
 73         public bool IsLegalFrame(int frame)
 74         {
 75             if (IsFrameDataExist(frame) != null || frame < 0 || frame > m_totFrame)
 76                 return false;
 77             return true;
 78         }
 79 
 80         public bool UpdateFramesSz()
 81         {
 82             int count = m_attackDatas.Count;
 83             for (int i = count - 1; i > m_totFrame - 1; i--)
 84             {
 85                 this.m_attackDatas.RemoveAt(i);
 86             }
 87             return true;
 88         }
 89 
 90         public void Init(CharacterStateType state)
 91         {
 92             m_animationName = "11111";
 93             m_totFrame = 12;
 94             m_stateType = state;
 95             m_oldStateType = state;
 96             m_attackDatas.Clear();
 97             m_attackBmDatas.Clear();
 98             m_attackNmDatas.Clear();
 99         }
100 
101         void ISerializationCallbackReceiver.OnBeforeSerialize()
102         {
103             if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null)
104                 return;
105             m_attackBmDatas.Clear();
106             m_attackNmDatas.Clear();
107             foreach(CharacterEditorAttackData item in m_attackDatas)
108             {
109                 switch(item.m_attackType)
110                 {
111                     case CharacterAttackType.BOMB:m_attackBmDatas.Add((CharacterEditorBombAttackData)item); break;
112                     case CharacterAttackType.NORMAL: m_attackNmDatas.Add((CharacterEditorNormalAttackData)item);break;
113                 }
114             }
115         }
116 
117         void ISerializationCallbackReceiver.OnAfterDeserialize()
118         {
119             if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null)
120                 return;
121             m_attackDatas.Clear();
122             foreach (CharacterEditorAttackData item in m_attackBmDatas)
123             {
124                 m_attackDatas.Add(item);
125             }
126             foreach (CharacterEditorAttackData item in m_attackNmDatas)
127             {
128                 m_attackDatas.Add(item);
129             }
130         }
131     }
复制代码

  对于上述的数据结构,可能会有疑问,首先为什么需要实现 ISerializationCallbackReceiver , 和这个接口的作用;为什么需要用到三个List结构。首先,来

理解一下 ISerializationCallbackReceiver 这个接口的作用 。 关于这个 接口介绍的博文 ,其实看完这个博文,我还是没有理解作者想讲的意思,后来自己翻阅

了其他资料,

void ISerializationCallbackReceiver.OnBeforeSerialize()

这个接口的作用就是 序列化快开始了 , 你可以在序列化开始前做些操作 。 比如C#结构中Dict是不能够序列化的,所以在开始前可以将Dict的键和值都保存在List中 这样就达到了序列化的目的。

void ISerializationCallbackReceiver.OnAfterDeserialize()

这个接口的作用就是 反序列化结束了 , 你可以在反序列化后做一些操作 。还是上面的例子,我们可以在反序列化后从list中拿到对应的数据,把List中的键和值存储在对应的Dict中。

  关于两个接口的作用已经讲完了,来解决下为什么要用那么多List的原因,首先先说一下我遇到的问题,之前访问子类和存储都是通过new子类对象后,用父类的指针保存在List上,所以就会存在问题,在序列化时,序列化的是父类而不是子类,在反序列化后,就会出现数据丢失。所以需要在上述的两个接口做一个序列化前和反序列化后的数据操作,保证数据的正确。

  通过上述,我们可以得到可靠的序列化流程,接下来就可以就可以自定义编辑器了,编辑器代码相对简单,由于界面主要在InspectorUI上操作,就写在OnInspector上。其实在写之前对于OnInspectorUI这个函数的调用是有疑问的,后来亲自实践了一下,发现只有有UI发生更改时或者切入切出(调到另一个)显示对象都会调用,代码如下:
复制代码
  1 using UnityEngine;
  2 using UnityEditor;
  3 using System.Collections;
  4 using TKGame;
  5 using System.Collections.Generic;
  6 using System;
  7 
  8 [CustomEditor(typeof(CharacterEditorData))]
  9 public class CharacterEditor : Editor {
 10 
 11     enum AddState { NONE, NEWSTATE, FULLSTATE };
 12     public const string TAG = "[CharacterEditor]";
 13     public CharacterEditorData m_chaEditData = null;
 14     private AddState m_addState;
 15     public void OnEnable(){
 16         m_chaEditData = target as CharacterEditorData;
 17         if (m_chaEditData == null)
 18             return;
 19         m_addState = AddState.NONE;
 20     }
 21 
 22     public override void OnInspectorGUI()
 23     { 
 24         if (m_chaEditData == null)
 25         {
 26             PrintLog("the chaEditorData is null");
 27             return;
 28         }
 29         m_chaEditData.m_id = EditorGUILayout.IntField("PlayerID: ", m_chaEditData.m_id);
 30         m_chaEditData.m_resID = EditorGUILayout.IntField("PlayerResourceID:", m_chaEditData.m_resID);
 31         m_chaEditData.m_defaultName = EditorGUILayout.TextField("PlayerDefaultName: ", m_chaEditData.m_defaultName);
 32         m_chaEditData.m_scale = EditorGUILayout.FloatField("PlayerScale:", m_chaEditData.m_scale);
 33         m_chaEditData.m_walkSpeedX = EditorGUILayout.IntField("PlayerXSpeed: ", m_chaEditData.m_walkSpeedX);
 34         m_chaEditData.m_walkSpeedY = EditorGUILayout.IntField("PlayerYSpeed:", m_chaEditData.m_walkSpeedY);
 35         m_chaEditData.m_hatred = EditorGUILayout.FloatField("PlayerHatred:", m_chaEditData.m_hatred);
 36         m_chaEditData.m_lowFireAngle = EditorGUILayout.FloatField("PlayerLowFireAngle:", m_chaEditData.m_lowFireAngle);
 37         m_chaEditData.m_higFireAngle = EditorGUILayout.FloatField("PlayerHigFireAngle:", m_chaEditData.m_higFireAngle);
 38         m_chaEditData.m_fireRange = EditorGUILayout.IntField("PlayerFireRange:", m_chaEditData.m_fireRange);
 39         m_chaEditData.m_weaponPosition = EditorGUILayout.Vector2Field("PlayerWeaponPos", m_chaEditData.m_weaponPosition);
 40         m_chaEditData.m_beAttackBoxMinX = EditorGUILayout.IntField("PlayerBAtkBoxMinX:", m_chaEditData.m_beAttackBoxMinX);
 41         m_chaEditData.m_beAttackBoxMinY = EditorGUILayout.IntField("PlayerBAtkBoxMinY:", m_chaEditData.m_beAttackBoxMinY);
 42         m_chaEditData.m_beAttackBoxMaxX = EditorGUILayout.IntField("PlayerBAtkBoxMaxX:", m_chaEditData.m_beAttackBoxMaxX);
 43         m_chaEditData.m_beAttackBoxMaxY = EditorGUILayout.IntField("PlayerBAtkBoxMaxY:", m_chaEditData.m_beAttackBoxMaxY);
 44         if (GUILayout.Button("Add New State"))
 45         {
 46             if (m_chaEditData.IsAllStateExist())
 47                 m_addState = AddState.FULLSTATE;
 48             else
 49                 m_addState = AddState.NEWSTATE;
 50         }
 51         EditorGUILayout.Space();
 52         if (m_addState == AddState.FULLSTATE)
 53             EditorGUILayout.LabelField("all states is used");
 54         else if (m_addState == AddState.NEWSTATE)
 55         {
 56             CharacterStateType newestState = m_chaEditData.GetNewestState();
 57             m_chaEditData.AddNewState(newestState);
 58             m_addState = AddState.NONE;
 59         }
 60 
 61         EditorGUILayout.Space();
 62         ///Debug.Log("yes");
 63         for (int index = 0; index < m_chaEditData.m_lsStates.Count; index++)
 64         {
 65             CharacterEditorData.CharacterEditorStateData chaState = m_chaEditData.m_lsStates[index];
 66             //Debug.Log(EditorGUILayout.EnumPopup("state:", chaState.m_newState));
 67             CharacterStateType state = (CharacterStateType)EditorGUILayout.EnumPopup("state:", chaState.m_stateType);
 68             m_chaEditData.ChangeByState(chaState, state);
 69             chaState.m_animationName = EditorGUILayout.TextField("AnimationName:", chaState.m_animationName);
 70             int totFrame = EditorGUILayout.IntField("TotalFrame:", chaState.m_totFrame);
 71             if (totFrame != chaState.m_totFrame)
 72             {
 73                 chaState.m_totFrame = totFrame;
 74                 chaState.UpdateFramesSz();
 75             }
 76             if (chaState.m_stateType == CharacterStateType.ATTACK)
 77             {
 78                 if (GUILayout.Button("Add Frame Data", GUILayout.MaxWidth(130), GUILayout.MaxHeight(20)))
 79                 {
 80                     int newFrame = chaState.GetNewFrame();
 81                     //Debug.Log(newFrame);
 82                     if (newFrame != -1)
 83                     {
 84                         chaState.AddFrameData(newFrame);
 85                     }
 86                 }
 87                 EditorGUILayout.Space();
 88                 for (int i = 0; i < chaState.m_attackDatas.Count; i++)
 89                 {
 90                     CharacterEditorData.CharacterEditorAttackData frameData = chaState.m_attackDatas[i];
 91                     int frame = EditorGUILayout.IntField("Frame:", frameData.m_iFrame);
 92                     if (chaState.IsLegalFrame(frame))
 93                     {
 94                         //Debug.Log(frame);
 95                         frameData.m_iFrame = frame;
 96                     }
 97                     CharacterAttackType attackType = (CharacterAttackType)EditorGUILayout.EnumPopup("AttackType:", frameData.m_attackType);
 98                     chaState.ChangeFrameData(i , attackType);
 99                     EditorGUILayout.Space();
100                     if (frameData.m_attackType == CharacterAttackType.BOMB)
101                     {
102                         CharacterEditorData.CharacterEditorBombAttackData bomb = (CharacterEditorData.CharacterEditorBombAttackData)frameData;
103                         bomb.m_bombCofigID = EditorGUILayout.IntField("BombConfigID:", bomb.m_bombCofigID);
104                         bomb.m_damage = EditorGUILayout.IntField("Damge:", bomb.m_damage);
105                         bomb.m_centerDamage = EditorGUILayout.IntField("CenterDamage:", bomb.m_centerDamage);
106                     }
107                     if (GUILayout.Button("Remove this Frame"))
108                     {
109                         chaState.RemoveFrameData(frameData.m_iFrame);
110                     }
111                     EditorGUILayout.Space();
112                     EditorGUILayout.Space();
113                 }
114             }
115             if (GUILayout.Button("remove this state"))
116             {
117                 m_chaEditData.RemoveOldState(index);
118             }
119             EditorGUILayout.Space();
120             EditorGUILayout.Space();
121         }
122         EditorUtility.SetDirty(m_chaEditData);
123     }
124 
125     private void PrintLog(string str)
126     {
127         Debug.Log(TAG+" "+ str);
128     }
129 }
复制代码
  最后一句的SetDirty表示当前的数据对象有更改,可以通知UI刷新。
2014-10-08 11:36:03 zihao2012 阅读数 3316
  • Unity3D优化技术讲解

    该课程使用的Unity2018,主要内容是关于游戏优化方面的讲解,不同于以往的讲解,该课程首先给读者分享了几个案例:自定义Mesh实现,利用GOU几何绘制实现草的编辑,为了让读者更好的理解优化技术,给读者深入分析了优化涉及到的三个方面:CPU,GPU,内存,以及在产品设计上需要 关注的点。希望通过该课程的学习能够帮助读者在优化方面进一步提升。

    150 人正在学习 去看看 姜雪伟
Coco2d-x引擎是相对于Unity3D的又一实力派引擎,虽然随着3D游戏的热门,更多的厂商偏向于Unity3D,但是Coco2d-x的普及量也不容小觑,特别是一些比较大的手游公司,比如触控科技仍然一直沿用着Coco2d-x的东西。我们从三个热门游戏来说说Coco2d-x游戏的一般文件验证方法。
1、捕鱼达人2:将Smali文件中获取包路径的参数转向我们自定义的。
该种方法最早见于:http://bbs.csdn.net/topics/390598569?page=2#post-395954285
竟然闪退那必然会在主Activity的oncreate中启动相关代码。下面是流程:
public class FishingJoy extends FishingJoyWrapper-->public class FishingJoyWrapper extends Cocos2dxActivity-->public abstract class Cocos2dxActivity extends Activity;
注意:class A extends B 表示A类承接B类,即B是A的相对父类。从而知 Cocos2dxActivity 是程序启动时的根类,查看 oncreate 方法:
  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    init();
    Cocos2dxHelper.init(this, this);
  }
又转向至 Cocos2dxHelper 类的 init 方法中,下面是其主要的smali代码:
<pre name="code" class="javascript">    //“参考文章:http://android.tgbus.com/Android/tutorial/201108/362210.shtml”
    //通过ApplicationInfo类中的packageName方法获取程序名称并保存在sPackageName字符串中
    iget-object v1, v0, Landroid/content/pm/ApplicationInfo;->packageName:Ljava/lang/String;
    sput-object v1, Lorg/cocos2dx/lib/Cocos2dxHelper;->sPackageName:Ljava/lang/String;
    //通过ApplicationInfo类中的sourceDir方法获取APK程序的路径,并传递给native层
    iget-object v1, v0, Landroid/content/pm/ApplicationInfo;->sourceDir:Ljava/lang/String;
    invoke-static {v1}, Lorg/cocos2dx/lib/Cocos2dxHelper;->nativeSetApkPath(Ljava/lang/String;)V

通过以上分析我们很容易得出,只需将获取APK路径的v1转向原版的APK即可绕过程序的校验。代码修改为:
    iget-object v1, v0,Landroid/content/pm/ApplicationInfo;->sourceDir:Ljava/lang/String;
    const-string v1, "/sdcard/buyudaren.apk"
    invoke-static {v1}, Lorg/cocos2dx/lib/Cocos2dxHelper;->nativeSetApkPath(Ljava/lang/String;)V
需要的操作是将原版apk更名为buyudaren.apk放在sdcard的根目录即可。
2、我是车神 Ver1.2.6:将Smali文件中获取包路径的参数转向其他的即可。

分析的流程是于捕鱼达人2一样的,知道这样我们可以直接搜索关键词“->sourceDir:”,出现两个结果,如下图:


其实,经过以外的测试,发现原来只需要将路径转向就行,不必要真的在指定位置放个原版。推测的判断是文件不存在时,走的流程还是判断正确的逻辑。不得不说这是开发者的一大失误啊。
3、保卫萝卜 Ver1.2.0:
这个游戏整体反编译apk的时候 会出现错误,貌似是资源文件的问题,但是我们可以直接拖出dex反编译。

同样是直接搜索“->sourceDir:”,然后将路径指定就行。

总结

就像以前在博客中说的,如果不对apk进行加壳或者利用第三方进行防护的话,apk本身的校验貌似真的只有签名校验和对文件的合法性进行验证。

无论是在smali、so还是dll,签名关键词“signature”或者“publickey”,pubkey 关键词的会获取一串MD5的值;

.method public static getSign(Landroid/content/Context;)Ljava/lang/String;
    .locals 6
    .param p0, "context"    # Landroid/content/Context;
   //########################下面是签名校验的部分###########################
    .prologue
    .line 374
    //初始化v4为空
    const-string v4, ""
    //读取com/txbnx/torrentsearcher/Utils方法中的mPublicKey字符串 赋给v5
    sget-object v5, Lcom/txbnx/torrentsearcher/Utils;->mPublicKey:Ljava/lang/String;
    //判断v4,v5是否相等 并返回为v4布尔值
    invoke-virtual {v4, v5}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v4

    if-nez v4, :cond_0

    .line 375
    sget-object v4, Lcom/txbnx/torrentsearcher/Utils;->mPublicKey:Ljava/lang/String;

    .line 385
    :goto_0
    return-object v4

    .line 376
    :cond_0
    //引用获取已安装包信息的方法getPackageManager()
    invoke-virtual {p0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;

    move-result-object v2

    .line 378
    .local v2, "pm":Landroid/content/pm/PackageManager;
    :try_start_0
    //获取包名字符串并保存在v4中
    invoke-virtual {p0}, Landroid/content/Context;->getPackageName()Ljava/lang/String;

    move-result-object v4

    const/16 v5, 0x40

    invoke-virtual {v2, v4, v5}, Landroid/content/pm/PackageManager;->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;

    move-result-object v1

    .line 380
    .local v1, "packageinfo":Landroid/content/pm/PackageInfo;
    //获取包信息里面的签名
    iget-object v4, v1, Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature;

    const/4 v5, 0x0

    aget-object v3, v4, v5

    .line 382
    .local v3, "sign":Landroid/content/pm/Signature;
    //将签名信息转换为字符串 并保存在v4寄存器中
    invoke-virtual {v3}, Landroid/content/pm/Signature;->toCharsString()Ljava/lang/String;

    move-result-object v4

    sput-object v4, Lcom/txbnx/torrentsearcher/Utils;->mPublicKey:Ljava/lang/String;

    .line 383
    sget-object v4, Lcom/txbnx/torrentsearcher/Utils;->mPublicKey:Ljava/lang/String;
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

    goto :goto_0

    .line 384
    .end local v1    # "packageinfo":Landroid/content/pm/PackageInfo;
    .end local v3    # "sign":Landroid/content/pm/Signature;
    :catch_0
    move-exception v0

    .line 385
    .local v0, "e":Ljava/lang/Exception;
    const/4 v4, 0x0

    goto :goto_0
.end method
    .local v0, "pubkeyMd5":Ljava/lang/String;
    const-string v1, "SigntureUtil"
   //##########################################下面是利用pubkey关键词校验的代码########################################
    new-instance v2, Ljava/lang/StringBuilder;

    invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V

    const-string v3, "pubkeyMd5:"

    invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v2

    invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v2

    invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v2

    invoke-static {v1, v2}, Lcn/com/cmbc/mbank/Log;->i(Ljava/lang/String;Ljava/lang/String;)V

    .line 68
    if-eqz v0, :cond_0

    const-string v1, "e6a81c9a3c88040678ae615f063d14d0"

    invoke-virtual {v1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

    move-result v1

	const/4 v1,0x1
文件检验首先是获取文件的路径,或者直接文件名,关键词“classes.dex”或者“->sourceDir:”,后者获取路径以后会传入其他的方法进行验证,如so方法。

    iget-object v7, v13, Landroid/content/pm/ActivityInfo;->packageName:Ljava/lang/String;
    //获取文件路径
    iget-object v8, v14, Landroid/content/pm/ApplicationInfo;->sourceDir:Ljava/lang/String;
	  const-string v8, "/sdcard/woshicheshen.apk"   //路径转向
    sget v9, Landroid/os/Build$VERSION;->SDK_INT:I
里面里面会有很多的错误之处,望大牛们不吝指出啊!不让让我以讹传讹啊 我要进步..奋斗

SurfaceOutput

阅读数 0

没有更多推荐了,返回首页