• Unity3D教程:制作简单小汽车游戏 OnGUI的视觉化编辑 unity3d角色血条 Unity3D格斗游戏运动特效 MenuItem自定义菜单项 Unity3D脚本:导弹追踪代码 Unity3D教程:UI自动适合屏幕分辨率 中慢动作与加速动作的实现 ...
  • Unity3d C# 游戏学习例子 GameArchitectureKill5 ,这个 列子很合适新人学习unity3d,特别是对C#不怎么熟悉的。亲身体验
  • unity3d 一个特别牛的游戏例子
  • Unity3d 集成Android 完整例子,附源码
  • Unity3d使用sqlite例子

    2020-07-04 16:32:51
    Unity3d使用sqlite例子,下载后可直接使用,还有封装类
  • 各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/...比如博主在写《[Unity3D]Unity3D游戏开发之自由视角下的角色控制》和《[Unity3D]Unity3D游戏开发之角色控制漫谈》这两篇

            各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。今天我们来做点简单的东西,做个什么呢?答案就是截屏。作为一名热爱单机游戏的玩家,博主每次在玩游戏的同时截取游戏中比较喜欢的画面,特别是学习了Unity3D以后玩游戏的时候更多地是从一个游戏设计者的角度来看待游戏,换句话说,可能关注技术的成分更多一点吧。比如博主在写[Unity3D]Unity3D游戏开发之自由视角下的角色控制》[Unity3D]Unity3D游戏开发之角色控制漫谈》这两篇文章时,碰到博主无法把握的问题的时候就是通过玩游戏来体会的,在玩游戏的过程中博主总是会抓取博主比较喜欢的画面,如图,下面是博主收集的部分截图:




           好了,欣赏完美丽的风景,下面我们就来一起学习在Unity3D实现截屏,先给出实现截屏的三种实现方式:

    /// <summary>
    	/// 使用Application类下的CaptureScreenshot()方法实现截图
    	/// 优点:简单,可以快速地截取某一帧的画面、全屏截图
    	/// 缺点:不能针对摄像机截图,无法进行局部截图
    	/// </summary>
    	/// <param name="mFileName">M file name.</param>
    	private void CaptureByUnity(string mFileName)
    	{
    		Application.CaptureScreenshot(mFileName,0);
    	}
    
    	/// <summary>
    	/// 根据一个Rect类型来截取指定范围的屏幕
    	/// 左下角为(0,0)
    	/// </summary>
    	/// <param name="mRect">M rect.</param>
    	/// <param name="mFileName">M file name.</param>
    	private IEnumerator CaptureByRect(Rect mRect,string mFileName)
    	{
    		//等待渲染线程结束
    		yield return new WaitForEndOfFrame();
    		//初始化Texture2D
    		Texture2D mTexture=new Texture2D((int)mRect.width,(int)mRect.height,TextureFormat.RGB24,false);
    		//读取屏幕像素信息并存储为纹理数据
    		mTexture.ReadPixels(mRect,0,0);
    		//应用
    		mTexture.Apply();
    		
    		
    		//将图片信息编码为字节信息
    		byte[] bytes = mTexture.EncodeToPNG();  
    		//保存
    		System.IO.File.WriteAllBytes(mFileName, bytes);
    		
    		//如果需要可以返回截图
    		//return mTexture;
    	}
    
    	private IEnumerator  CaptureByCamera(Camera mCamera,Rect mRect,string mFileName)
    	{
    		//等待渲染线程结束
    		yield return new WaitForEndOfFrame();
    
    		//初始化RenderTexture
    		RenderTexture mRender=new RenderTexture((int)mRect.width,(int)mRect.height,0);
    		//设置相机的渲染目标
    		mCamera.targetTexture=mRender;
    		//开始渲染
    		mCamera.Render();
    		
    		//激活渲染贴图读取信息
    		RenderTexture.active=mRender;
    		
    		Texture2D mTexture=new Texture2D((int)mRect.width,(int)mRect.height,TextureFormat.RGB24,false);
    		//读取屏幕像素信息并存储为纹理数据
    		mTexture.ReadPixels(mRect,0,0);
    		//应用
    		mTexture.Apply();
    		
    		//释放相机,销毁渲染贴图
    		mCamera.targetTexture = null;   
    		RenderTexture.active = null; 
    		GameObject.Destroy(mRender);  
    		
    		//将图片信息编码为字节信息
    		byte[] bytes = mTexture.EncodeToPNG();  
    		//保存
    		System.IO.File.WriteAllBytes(mFileName,bytes);
    		
    		//如果需要可以返回截图
    		//return mTexture;
    	}
    
    }
    

          接下来,我们来调用这三个方法实现一个简单的截图的例子:

    //定义图片保存路径
    	private string mPath1;
    	private string mPath2;
    	private string mPath3;
    
    	//相机
    	public Transform CameraTrans;
    
    	void Start()
    	{
    		//初始化路径
    		mPath1=Application.dataPath+"\\ScreenShot\\ScreenShot1.png";
    		mPath2=Application.dataPath+"\\ScreenShot\\ScreenShot2.png";
    		mPath3=Application.dataPath+"\\ScreenShot\\ScreenShot3.png";
    	}
    
    	//主方法,使用UGUI实现
    	void OnGUI()
    	{
    		if(GUILayout.Button("截图方式1",GUILayout.Height(30))){
    			CaptureByUnity(mPath1);
    		}
    		if(GUILayout.Button("截图方式2",GUILayout.Height(30))){
    			StartCoroutine(CaptureByRect(new Rect(0,0,1024,768),mPath2));
    		}
    		if(GUILayout.Button("截图方式3",GUILayout.Height(30))){
    			//启用顶视图相机
    			CameraTrans.camera.enabled=true;
    			//禁用主相机
    			Camera.main.enabled=false;
    			StartCoroutine(CaptureByCamera(CameraTrans.camera,new Rect(0,0,1024,768),mPath3));
    		}
    	}
    
    
    	
           在第三中截图方式中,博主在场景里放了一个名为TopCamera的摄像机,它垂直向下投影到游戏场景里,这样可以使玩家看到场景的顶视图。这里我们用这个相机来测试第三个方法,此时需要先激活该相机。场景设置如图:


            我们下面来看三种方法截图的效果:





            从截图的效果来看,第一种方法的效果是最好的,不过定制化是个问题。第二种方法效果一般吧,感觉这里TextureFormat没有选好吧。第三种效果基本达到了博主想要的要求,不过摄像机的投影范围似乎没有设计好。这里我们发现第二张截图会把编辑器的窗口渲染到里面,博主认为是程序运行的时候,即使将Game窗口放到最大,仍然会受到窗口的影响,后来博主就把程序编译成可执行文件,不过程序运行完之后,博主却没有找到对应的截图。后来查找了官方的API才知道原因是这样的:

    Description

    Contains the path to the game data folder (Read Only).

    The value depends on which platform you are running on:

    Unity Editor: <path to project folder>/Assets

    Mac player: <path to player app bundle>/Contents

    iPhone player: <path to player app bundle>/<AppName.app>/Data

    Win player: <path to executablename_Data folder>

    Web player: The absolute url to the player data file folder (without the actual data file name)

    Flash: The absolute url to the player data file folder (without the actual data file name)

    Note that the string returned on a PC will use a forward slash as a folder separator
     

            显然,我们从这里可以知道Application.datapath在不同的平台上对应的位置。对于可执行(.exe,Windows平台)的文件,它对应在和应用程序对应的一个文件夹里,例如可执行文件的名字叫做UnityGame,那么对应的位置就是UnityGame_Data这个文件啦。所以博主的问题应该是出在没有在这里建一个ScreenShot的文件夹,希望大家以后做相关项目的时候注意一下吧。好了,这就是今天的内容了,希望大家喜欢啊。


    每日箴言:无所谓好或不好,人生一场虚空大梦,韶华白首,不过转瞬。惟有天道恒
    在,往复循环,不曾更改——慕容紫英。




       喜欢我的博客请记住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei。
       转载请注明出处,本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/39185195




    展开全文
  • 今天我们来说说通过反编译Unity3D的AssetBundle来提取游戏资源,博主写这篇文章的目的并非是要教大家如何去破解一款基于Unity3D引擎开发的游戏,而是想通过今天这篇文章来告诉大家如何在开发Unity3D游戏的过程中保护...

    各位朋友,大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是http://www.qinyuanpei.com。今天我们来说说通过反编译Unity3D的AssetBundle来提取游戏资源,博主写这篇文章的目的并非是要教大家如何去破解一款基于Unity3D引擎开发的游戏,而是想通过今天这篇文章来告诉大家如何在开发Unity3D游戏的过程中保护自己的游戏资源。

    漫话Unity3D的AssetBundle

    对于AssetBundle,其实博主是在以前的文章中是有提到的。不知道大家还记不记得,博主曾经在写游戏开发和Lua的不解之缘这个系列文章的时候,提到并且使用过AssetBundle这种技术。具体来说呢,AssetBundle在Unity3D中是一种用于资源打包盒资源动态加载的解决方法,比如我们平时玩的单机游戏容量一般都比较大,这是因为制作人员在制作游戏的时候将所有的项目资源都整合到了一起。可是如果我们用AssetBundle来做这个游戏的话,我们就可以只在发布的游戏中提供支持游戏功能的核心部分,而将游戏当中的场景、模型等资源以AssetBundle的形式打包然后放到服务器上,这样当游戏客户端处于联网的时候就可以从服务器上下载这些资源,从而实现游戏资源的动态加载,由此可见AssetBundle可以帮助我们减少游戏的容量。如果是在需要安装包的场合下,那么游戏包容量的大小无疑会为游戏加些印象分。

    为什么这幅图总让我想起仙剑四里四人在即墨那晚的时光呢?

    比如最近《轩辕剑6外传穹之扉》这部单机游戏发布了,从各大游戏网站的评测到和一样我喜欢单机游戏的各位朋友们的亲身体验,大家一致的认为这部游戏整体表现还不错,应该考虑玩一玩。这样难免让博主有些心动,可是看到17个G的游戏容量时还是犹豫了下。DOMO小组从《轩辕剑6》就开始使用Unity3D引擎,在经历了第一部游戏的失败后,或许此次DOMO小组会将游戏优化的比较好吧。这里如果有喜欢单机游戏的朋友不妨去玩玩看,毕竟我们学习游戏开发的初衷就是做出好游戏,如果不热爱游戏又怎么能做出好游戏呢?好了,扯得有点远了,这里我们注意到一个重要的因素就是游戏容量,如果DOMO采用AeestBundle的话,游戏的容量肯定会减少很多。可是这样一来,它就不是单机游戏了嘛,对吧!

    在Unity3D中AssetBundle是专业版中的一个功能,在免费版的Unity3D中是无法使用这个功能的,不知道在Unity5中这个功能是不是划分到了个人版中。好了,下面我们来看看如何使用AssetBundle。我们主要从使用AssetBundle打包和加载AssetBundle这两个方面来说:

    使用Assetbundle打包

    使用AssetBundle打包主要通过BuildPipeline.BuildAssetBundle()这个方法来实现,该方法原型为:

    bool BuildAssetBundle (Object mainAsset,Object[] assets,string pathName, BuildAssetBundleOptions 
    optionsBuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets,
    BuildTarget targetPlatform= BuildTarget.WebPlayer)  

    在这个方法中,第一个参数是一个Object类型,表示一个激活的物体;第二个参数是一个Object[]类型,表示所有选中的物体;第三个参数是一个string类型,表示要导出的资源包的路径,资源包的扩展名可以是assetbundle或者unity3d;第四个参数表示的是打包选项,默认是完全打包和依赖打包。这里重点解释下这两个概念,完全打包是指所有资源都参与打包,比如说一个模型带有贴图和动画,那么打包模型的时候贴图和动画都会被作为资源打包。而依赖打包是相对于Prefab来说的,比如说PrefabA中引用了PrefabB这个对象,那么打包的时候这两个对象都会被打包,并且它们之间的这种依赖关系会在打包后继续保持;第五个参数是平台的选择,因为Unity3D是一个跨平台的游戏引擎,而各个平台现在的情况又不尽相同,因此现在Unity3D采取的方案是各个平台只能使用自己平台对应的AssetBundle,这一点希望大家在使用的时候注意啊。好了,现在我们来看一个简单的例子:

        /// <summary>
        /// 输出AssetBundle
        /// </summary>
        /// <param name="type">平台类型</param>
        static void ExportToAssetBundle(ExportType type,BuildTarget target)
        {
            //获取存储路径
            string savePath=EditorUtility.SaveFilePanel("输出为AssetBundle","","New Resource","unity3d");
            if(savePath==string.Empty) return;
            //获取选中的对象
            Object[] selection=Selection.GetFiltered(typeof(Object),SelectionMode.DeepAssets);
            if(selection.Length==0) return;
            //打包
            if(type==ExportType.All){
              BuildPipeline.BuildAssetBundle(null,selection,savePath,BuildAssetBundleOptions.CollectDependencies,target);
            }else{
                BuildPipeline.BuildAssetBundle(obj,null,savePath,BuildAssetBundleOptions.CollectDependencies,target);
            }
        }

    这是一个简单的导出AssetBundle资源包的方法,它有两个参数,第一个参数表示是一个枚举类型,定义为ExportType,取Single时表示打包一个特定的激活物体,比如说一个模型、一个场景等等;取All时表示打包所有选中的物体,比如一个场景。第二个参数表示打包的平台,这个不用多说了。因为博主的免费版的Unity3D不支持AssetBundle,所以这里没法给大家演示了,具体效果请自行测试,有问题的话给博主留言就是了。

    加载AssetBundle

    加载AssetBundle是一个从网络中下载资源的过程,因此需要使用Unity3D的WWW功能,这是一个简单的网络协议的封装,可以像浏览器一样访问某个URL地址或者是本地地址,访问WEB地址需要使用HTTP协议,访问本地地址需要使用File协议。我们来看一个具体的例子:

        /// <summary>
        /// 加载一个unity3d格式的文件
        /// WEB地址——http://server.com/xxx.unity3d
        /// 本地地址——file://.unity3d文件的绝对路径
        /// </summary>
        IEnumerator LoadUnity3DFile(string url)
        {
            WWW www=new WWW(url);
            yield return www;
            if(www.error!=null){
                Debug.Log(www.error);
            }else{
               AssetBundle bundle=www.assetBundle;
              Instantiate(bundle.mainAsset,Vector3.zero,Quaternion.identity);
            }
        }

    在这里我们直接使用bundle.assetBundle获取了全部的资源,如果只需要获取资源中的一部分,则只需要通过bundle.Load()方法就可以了,这里需要传入资源的名称。当我们使用完资源后可以通过bundle.Unload()方法来卸载资源,达到释放内存的目的。

    从反编译《新仙剑OL》看AssetBundle打包

    好了,下面我们以《新仙剑OL》这款游戏的AssetBundle的反编译来探索下在使用AssetBundle打包应该注意哪些问题。《新仙剑OL》这款游戏呢,是采用Unity3D引擎开发的一款横跨客户端游戏和网页游戏的网络游戏,游戏以《仙剑奇侠传》初代游戏剧情为主,玩家将第三人称视角再次跟随主人公展开一段荡气回肠的感人故事。这款游戏总体来说还不错吧,因为毕竟是网游,我们不能用单机游戏的视角去评价,具体的原因大家都是知道的。

    好了,为什么我们要选择这款游戏呢?
    * 第一,这款游戏的客户端只有30余M,体积小适合拿来研究(这就是AssetBundle的好处啊)* 第二,博主是一位仙剑玩家,一直希望有一天《仙剑奇侠传1》能够用3D技术重现,这个游戏满足了博主的好奇心
    * 第三,网络上已经有朋友对这个游戏的打包进行了研究,这里感谢网友朋友提供部分.unity3d文件及相关文件。

    我们选择的解包工具是一款叫做disunity的命令行工具,经过博主的尝试,这个工具真心强悍啊,可以解开.unity3d文件和.assets文件,可以拿到的数据形式有贴图、声音、模型等。具体的情况大家可以在稍后看到。

    首先我们找到《新仙剑OL》的安装目录,然后我们就能发现一个叫做assetbundles的文件夹,这是怕大家不知道吗?这太明显了吧!我们打开文件夹会发现Charachers、NPC、Scene等等文件夹,继续往下找我们发现了好多的.unity3d文件,不过这些文件都是以.unity3d然后跟些随机字符串的形式存在的。根据网友朋友们的提示,这些文件就是.unity3d文件,不过游戏制作组为了干扰我们故意接了下随机字符在后面(呵呵,还有比这更弱的加密方式吗?)。博主看到这里的第一感觉就是想先用加载AssetBundle的方式来看看能不能将这些AssetBundle读取出来,因此果断改了文件扩展名,然后开始在Unity3D中读取,结果程序报错看来是我们想的简单了啊。没办法的办法,强行解包吧!在命令行中输入:

    disunity extract C:\Users\Robin\Desktop\s049.unity3d

    接下来程序会在桌面上生成一个上s049的文件夹,打开文件夹一看,尼玛,竟然直接拿到了模型的网格数据(.obj)和贴图数据(.dds)以及相关的Shader。这让我突然间有点不能接受啊,马上打开Blender将网格数据导入,结果童年的林月如就出现在了我们的面前:

    林月如灰模

    因为博主不会在Blender中给模型贴图,所以我们到Unity3D中完成贴图,首先需要将模型导出为FBX格式。好了,将模型导入Unity3D后,将贴图赋给模型,童年的林月如就闪亮登场了,哈哈!

    林月如贴图效果

    好了,再来一张,不过这张没有贴图,需要大家自己来辨别这是谁啊,哈哈!

    柳梦璃灰模

    通过disunity这个工具我们还能获取更多的资源,剩下的内容就由大家自己去探索吧。通过这部分的研究,我们可以总结出以下观点,希望大家在使用AsssetBundle这项技术时注意:
    * 尽量在一个AssetBundle中打包多个资源,这样做的好处是别人没法通过加载AssetBundle拿到你做好的Prefab。
    * 尽量将一个预制件分割成不同的部分分别存放,这样做的好处是即使别人拿到了你的预制件却是不完整的。
    * 尽量利用动态脚本来加载场景而不是将整个场景打包,即使将整个场景打包,要把贴图和模型分开放置(因此如此,我虽然拿到了游戏的场景贴图,可是没有用啊)
    * 尽量利用加密的方法来隐藏本地的AssetBundle或者使用不易察觉的存储位置作为AssetBundle的存储位置,不要用明文数据进行存储。

    好了,今天的内容就是这样了,希望大家喜欢,AssetBundle打包是一个值得去深入研究的问题,今天博主提出的这些观点不过是对《新仙剑OL》这个游戏的打包提出de一些看法,如果大家有不同的看法,欢迎一起来交流!

    展开全文
  • unity3d调用jar类中的方法和属性的例子
  • Unity3d实战之Unity3d网络游戏实战篇(10):玩家类Player 学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社 本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师...

    Unity3d实战之Unity3d网络游戏实战篇(10):玩家类Player

    学习书籍《Unity3d网络游戏实战》 罗培羽著 机械工业出版社
    本文是作者在学习过程中遇到的认为值得记录的点,因此引用的代码等资源基本出资罗培羽老师的书籍,如有侵权请联系,必删。

     玩家在登录游戏到退出游戏时,有以下流程:
    这里写图片描述
     我们给Player类设计如下方法:
     Send: 提供一个Player的Send方法,该方法调用ServNet的Send方法;
     KickOff:将当前角色踢下线;
     Logout:该方法将触发HandlePlayerEvent中的OnLogout事件。

     Player类的Property:

    public string id;       // user_name
    public Conn conn;       // the conn connect with user[id]'s client
    public PlayerData data; // player's data
    public PlayerTempData tempData; // player's tempdata

     KickOff:
     当用户在两个Client上登录同一账号时,把前一个角色踢下线。

    public static bool KickOff(string id, ProtocolBase protoBase)
    {
        Conn[] conns = ServNet.instance.conns;      // get client list.
        for (int i = 0; i < conns.Length; i++) {
            Conn conn = conns [i];
            if (conn == null || conn.isUse == false)
                continue;
            if (conn.player == null)
                continue;
    
            if (conn.player.id == id) {             // find the user who named [id].
                lock (conn.player) {
                    if (protoBase != null) {        // send the logout protocol to user.
                        ProtocolBytes protocol = (ProtocolBytes)protoBase;
                        protocol.AddInt32 (-1);
                        conn.player.Send ((ProtocolBase)protocol);
                    }
    
                    return conn.player.Logout ();   // execute logout function
                }
            }
        }
        return true;
    }

     Logout:
     Player类的Logout方法,调用Logout方法将会触发HandlePlayerEvent中的OnLogout事件处理相关逻辑。

    public bool Logout()
    {       
        ServNet.instance.handlePlayerEvent.OnLogout (this);     // trigger OnLogout event
    
        if (!DataMgr.instance.SavePlayer (this)) {              // save player's data before logout
            return false;
        }
    
        conn.player = null;
        conn.Close ();
        return true;
    }
    展开全文
  • 原创文章如需转载请注明:转载自 脱莫柔Unity3D学习之旅 QQ群:【Unity3D(AR/VR) 334163814】【Unity3D(游戏) 119706192】 本文链接地址: Unity3D 一些基础的方向、距离算法 最近做一款一款3D“跑酷”游戏终于要...

    原创文章如需转载请注明:转载自 脱莫柔Unity3D学习之旅 QQ群:【Unity3D(AR/VR) 334163814】【Unity3D(游戏) 119706192】 本文链接地址:Unity3D 一些基础的3D图形学算法

    最近做一款一款3D“跑酷”游戏终于要上线了,很开森,忙里偷闲分享点常用的一些基础算法。

    另外感兴趣的朋友可以去上网下载下来玩玩 ,游戏叫《让小编飞》,嘿嘿、

    ps:最近使用中文编程~强迫症们,来咬我啊!

    1..A点正前方,10米位置的坐标点


    代码:
    public Transform A;
    public Transform Z;
    
    
    void Start () 
    {
        正前方();
        Debug.Log("Distance:" + Vector3.Distance(A.transform.position, Z.transform.position));
    }
    
    void 正前方()
    {
        Z.transform.position = A.transform.position + Vector3.forward * 10f;
    }
    运行结果:

    2.A点前方Y轴45°,10米位置的坐标点

    直接上代码:
    void 斜前方()
    {
        Quaternion q = Quaternion.Euler(0, 45, 0);
        Z.transform.position = A.transform.position + q * Vector3.forward * 10f;
    }

    运行结果:

    3.A点到B点方向,10米位置的坐标点


    柱子是B点。
    这个例子用3D视角来做。
    代码:
    void 到B方向正前方()
    {
        Vector3 方向 = (B.transform.position - A.transform.position).normalized;
        Quaternion q = Quaternion.LookRotation(方向);
        Z.transform.position = A.transform.position + q * Vector3.forward * 10f;
    
        Debug.Log("Distance A-B:" + Vector3.Distance(A.transform.position, B.transform.position));
    }
    运行结果:

    4.A点到B点方向Y轴偏移45°,10米位置的坐标点

    这个例子将B点与A点放在了同一高度,继续使用2D视角。
    代码:
    void 到B方向斜前方()
    {
        Vector3 方向 = (B.transform.position - A.transform.position).normalized;
        Quaternion q = Quaternion.LookRotation(方向) * Quaternion.Euler(0,45,0);
        Z.transform.position = A.transform.position + q * Vector3.forward * 10f;
    
        Debug.Log("Distance A-B:" + Vector3.Distance(A.transform.position, B.transform.position));
    }
    运行结果:


    到此展示完毕~
    谢谢参观~!

    展开全文
  • 已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。 CSDN视频网址:http://edu.csdn.net/lecturer/144 现在竞技类网络游戏比较火,市面上也出现了很多这种...

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。

    CSDN视频网址:http://edu.csdn.net/lecturer/144

    现在竞技类网络游戏比较火,市面上也出现了很多这种类型的游戏竞赛,提到网络游戏就回避不了一个问题:同步技术,多个人在一个游戏场景围攻一个怪物或者说多人组队战斗等等。现在在移动端的游戏由于带宽的限制,一般采用实时同步的方式是状态同步,也就是说角色的状态发生改变,才会去发送消息。举个例子:

    3D角色一般的动作状态有:Idle,walk,run,attack等,玩家操作键盘或者触摸屏按钮,会触发这些动作,一个游戏场景中会有多个角色,每个角色都有自己的动作状态,为了让玩家能够看到其他玩家在做什么,需要同步,玩家默认状态是idle,玩家刚出现时是idle状态,这个时候,客户端会把玩家的状态,位置,方向传送给服务器,其他玩家也是一样的,服务器接收到信息后,会把这些信息发送给除了它本人之外的其它玩家,这样我们就可以看到其他玩家的状态了。如果玩家从idle状态转化到walk状态,这表明玩家的动作状态发生了变化,这也需要将信息发给服务器,服务器进行群发给其他玩家,这样其他玩家就可以看到角色开始walk了。接下来如果玩家继续走,客户端就不发送消息给服务器了,因为状态没发生变化,等状态再变化时才会发送消息给服务器,然后服务器再群发消息,在此过程中,其他客户端会通过插值的方式把两个状态之间的距离实现出来,以此类推。。。。。。这就是所说的状态同步模式。

    下面介绍一下帧同步模式,帧同步含义游戏客户端接受来自网络的多个客户端的操作,如果这些操作在各个客户端是一样的,那么多个客户端的显示也就一样了,这就带来了“同步”的效果。所以在这种情况下,各个客户端的运算要绝对一致,不能依赖诸如本地时间、本地随机数等等“输入”,而要一切以网络来的操作数据为主。

    一般来说,大多数的游戏客户端引擎,都会定时调用一个接口函数,这个函数由用户填写内容,用来修改和控制游戏中各种需要显示的内容。比如在Unity里面叫Update(),这类函数通常会在每帧画面渲染前调用,当用户修改了游戏中的各个角色的位置、大小后,就在下一帧画面中显示出来。而在帧同步的游戏中,这个Update()函数依然是存在,只不过里面大部分的内容,需要挪到另外一个类似的函数中,我们可以称之为UpdateNet()函数——由网络层不断的接收服务器发来的“网络帧”数据包,每收到一个这样的数据包,就调用一次这个UpdateNet()函数,这样游戏就从通过本地CPUUpdate()函数的驱动,改为根据网络来的UpdateNet()函数驱动了。显然,网络发过来的同步帧速度会明显比本地CPU要慢的多,这里就对我们的游戏逻辑开发提出了更高的要求——如何同步的同时,还能保证流畅?

    实现UpdateNet函数内容,其实就是定义一个堆栈用于存放网络发过来的消息,通过帧监测将其数据拿出来使用,因为Update函数明显比UpdateNet快的多,这就需要我们定义一个时间间隔用于消息的发送,比如50毫秒或者100毫米等。

     

    private float AccumilatedTime = 0f;
     
    private float FrameLength = 0.05f; //50 miliseconds 
    //called once per unity frame 
    public void Update() {
        //Basically same logic as FixedUpdate, but we can scale it by adjusting FrameLength 
        AccumilatedTime = AccumilatedTime + Time.deltaTime;
     
        //in case the FPS is too slow, we may need to update the game multiple times a frame 
        while(AccumilatedTime > FrameLength) {
            GameFrameTurn ();
            AccumilatedTime = AccumilatedTime - FrameLength;
        }
    }

     

     

     

     

    private void GameFrameTurn() {
        //first frame is used to process actions 
        if(GameFrame == 0) {
            if(LockStepTurn()) {
                GameFrame++;
            }
        } else {
            //update game 
            SceneManager.Manager.TwoDPhysics.Update (GameFramesPerSecond);
             
            List<IHasGameFrame> finished = new List<IHasGameFrame>();
            foreach(IHasGameFrame obj in SceneManager.Manager.GameFrameObjects) {
                obj.GameFrameTurn(GameFramesPerSecond);
                if(obj.Finished) {
                    finished.Add (obj);
                }
            }
             
            foreach(IHasGameFrame obj in finished) {
                SceneManager.Manager.GameFrameObjects.Remove (obj);
            }
             
            GameFrame++;
            if(GameFrame == GameFramesPerLocksetpTurn) {
                GameFrame = 0;
            }
        }
    }

     

     

     

     

    帧同步游戏中,由于需要“每一帧”都要广播数据,所以广播的频率非常高,这就要求每次广播的数据要足够的小。最好每一个网络帧,能在一个MTU以下,这样才能有效降低底层网络的延迟。同样的理由,我们为了提高实时性,一般也倾向于使用UDP而不是TCP协议,这样底层的处理会更高效。但是,这样也会带来了丢包、乱序的可能性。因此我们常常会以冗余的方式——比如每个帧数据包,实际上是包含了过去2帧的数据,也就是每次发3帧的数据,来对抗丢包。也就是说三个包里面只要有一个包没丢,就不影响游戏。

    帧同步实现的过程有个很重要的地方就是逻辑层和表现层一定要分开,表现层先行,逻辑层等发到服务端的指令再处理。帧与帧之间的播放频率,则由服务器统一控制,但由于网络抖动等影响,帧的频率并不是太稳定,为避免播放抖动,帧数控制器需要进行一定的平滑处理。

     

     

     

    网络抖动的产生原因:在网络游戏中,各个客户端的运行条件和环境往往千差万别,有的硬件好一些,有的差一些,各方的网络情况也不一致;时不时玩家的网络还会在游戏过程中,发生临时的拥堵,我们称之为“网络抖动”。可能导致客户端收到“过去时间”里的一堆网络帧,客户端需要拿出一定的时间去处理这些堆积的网络帧,因此,客户端必须要有处理这些堆积起来的网络数据的能力。

    实时同步游戏最重要的是流畅,然而影响游戏流畅的因素很多,网络带宽的限制,CPU运算和渲染效率的限制。一般玩家控制的角色的动作,包括当前客户端控制的角色,还是应该从网络帧里面获得行为数据,因为如果玩家爱控制角色不一致的太多,整个游戏场面就会差更多。很多游戏中的怪物AI都是根据玩家角色来设定的,所以一旦玩家角色的行为是同步的,那么大多数的怪物的表现还是一致的。

     

    帧同步游戏技术,并不存在一种可以让游戏流畅的通用做法,而是需要和游戏具体做很多结合,在减少数据包,优化游戏快进体验,控制发包速度上尽量调优。同时还需要和游戏产品策划一起,平衡一致性、实时性、公平性的策略,才能真正达到流畅游戏的目的。

     

    Demo下载地址:链接:https://pan.baidu.com/s/1jAV-4h8QAncxnMDVtNeO4g 
    提取码:n7ia 
     

    重新更换链接

     

     

     

     

    展开全文
  • 大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。 在《仙剑奇侠传》、《古剑奇谭》等游戏中...那么,这一功能如何在Unity3D中实现呢?首先我们可以将问题分解为两个子问题:第一
  • Unity3D实战】零基础一步一步教你制作跑酷类游戏(填坑完整版) - Unity3D知识库
  • 大家晚上好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/...所以,今天的博客的主题就是《Unity3D游戏开发之跑酷游戏项目讲解》。从博主自身来考虑这件事情,当你选择做自己热爱的事情的时
  • Unity3D常见面试题

    2017-08-11 20:20:00
    Unity3D常见面试题
  • 最近刚刚开始接触unity3d,因此想把自己的学习路线记录下来,方便自己以后总结。由于毕业论文的关系,需要使用Unity3D开发,做虚拟现实的应用,使用的设备是HTC vive pro产品。初始学习,由于没有基础,因此一团乱,...
  • Unity3D 打开access MDB数据库例子unity3d4编写。 有分的多给点,折腾了一下午才弄完。
  • 现在网上虽然讲解Unity3D动画的文章不少,但是却少很少带实际例子的。刚好我之前给美术做了个简单的例子,所以整理一下就共享给大家了。主要是做了两种动画,分别有例子,按空格可以改变动画。具体的制作过程不会...
  • 赛车游戏 unity3d

    2020-06-04 23:30:56
    unity3d 开发赛车的一个例子,可供新手参考。
  • 今天想和大家交流的是解析obj模型并将其加载到Unity3D场景中,虽然我们知道Unity3D是可以直接导入OBJ模型的,可是有时候我们并不能保证我们目标客户知道如何使用Unity3D的这套制作流程,可能对方最终提供给我们的...
1 2 3 4 5 ... 20
收藏数 12,820
精华内容 5,128
热门标签