我觉得世界上最丑陋的女人所有的游戏公司都应该放弃研究游戏把精力放在有意义的研究方向上。另外,除了苹果以外,其它

用户研究需要注意哪些问题呢?怎样做好用户研究呢? - 知乎2705被浏览145391分享邀请回答16327 条评论分享收藏感谢收起47添加评论分享收藏感谢收起查看更多回答研究生生活是怎样的? - 知乎2422被浏览646935分享邀请回答259130 条评论分享收藏感谢收起4230 条评论分享收藏感谢收起查看更多回答游戏研究院游戏研究院研究游戏技术关注专栏更多最新文章{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&Unity3D热更新LuaFramework入门实战(7)——PureMVC&,&author&:&pyluo&,&content&:&\u003Cp\u003ELuaFramework使用了PureMVC框架。百度百科上说:“PureMVC是在基于模型、视图和控制器MVC模式建立的一个轻量级的应用框架”。PureMVC框架可以做到较好的解耦,减少游戏代码的相互调用。然而LuaFramework整合PureMVC属于“杀鸡用牛刀”,实质上只用到了事件分发(也可能是我理解得不够透彻)。如果单纯写一套事件分发系统,可能不到100行代码就能完成。\u003C\u002Fp\u003E\u003Cp\u003Eby 罗培羽\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E1、\u003Cb\u003E解耦的好处\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E如果没有很好的解耦设计,游戏功能越多,代码就越乱,最后没人敢改动。举个例子,假如游戏中背包(item)和成就(Achieve)两项功能,各用一个类实现。当玩家获得100个经验豆(一种道具)时,会获得“拥有100个经验豆”的成就;当成就点数达到300时,会获得道具奖励。一种常见的实现方法是调用对方的public函数,代码如下所示。然而如果一款游戏有几百上千个类,之间又相互调用,如果某些功能需要大改(例如删掉成就功能),那其他的类也得改动。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EClass Item\n{\n
public AddItem()\n
if(经验豆 & 100)\n
achieve.AddAchieve(“拥有100个经验豆”)\n
}\n}\n \nClass Achieve\n{\n
public AddAchieve()\n
成就点数 + 10\n
if(成就点数 & 300)\n
item.AddItem(宝石)\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E如果使用事件分发,各个类之间的联系就减弱了。如下所示的代码中背包类(Item)监听了消息“添加道具”,成就类(Achieve)监听了消息“添加成就”。如果达成成就需要添加奖励,只需派发“添加道具”这条消息,由背包类去执行。这样类与类之间不存在相互调用,就算大改功能甚至删掉功能,其他类都受到的影响比较小。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EClass Item\n{\n
监听(“添加道具”,AddItem)\n
private AddItem()\n
if(经验豆 & 100)\n
分发(“添加成就”,“拥有100个经验豆”)\n
}\n}\n \nClass Achieve\n{\n
监听(“添加成就”,AddAchieve)\n
private AddAchieve()\n
成就点数 + 10\n
If(成就点数 & 300)\n
分发(“添加道具”, 宝石)\n
}\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Ch2\u003E\u003Cb\u003E2、MVC的使用方法\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003ELuaFramework中的Framwork目录存放着PureMVC框架的代码,个人认为在LuaFramework中属于过度设计(毕竟从其他地方拷过来的)。它的原理并不复杂,用一个列表把监听信息保存起来,在派发消息时,查找对应的监听表,找到需要回调的对象。\u003C\u002Fp\u003E\u003Cimg src=\&8da3418eedcdde84f471d.png\& data-rawwidth=\&409\& data-rawheight=\&249\&\u003E\u003Cp\u003EPureMVC框架便是实现了“注册\u002F分发”模式(发布\u002F订阅、观察者模式),可以调用RegisterCommand注册消息(命令),调用SendMessageCommand方法分发消息。RegisterCommand方法可以把某个继承ControllerCommand 的类注册到指定的消息下,在事件分发时调用该类的Execute方法。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E例如新建一个名为TestCommand的类,让它继承ControllerCommand,然后编写Execute方法处理具体事务。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\n \npublic class TestCommand : ControllerCommand \n{\n\tpublic override void Execute(IMessage message) \n\t{\n\t\tDebug.Log(\&name=\& + message.Name);\n\t\tDebug.Log(\&type=\& + message.Type);\n\t}\n}\u003C\u002Fcode\u003E\u003Cp\u003E接着,编写另一个类来处理消息。这个类先调用AppFacade.Instance.RegisterCommand()将TestCommand类注册到“TestMessage”消息下。然后使用SendMessageCommand()派发“TestMessage”消息。框架将会创建一个TestCommand实例,并调用它的Execute方法。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Epublic class Main : MonoBehaviour \n{\n
void Start() \n\t{\n\t\tAppFacade.Instance.RegisterCommand (\&TestMessage\&, \n\t\t\t\t\t\t\ttypeof(TestCommand));\n\t\tAppFacade.Instance.SendMessageCommand (\&TestMessage\&);\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E运行结果如下所示,可以看到分发消息后,TestCommand的Execute方法被调用。\u003C\u002Fp\u003E\u003Cimg src=\&407d897f1bd4d2ce8402877.png\& data-rawwidth=\&320\& data-rawheight=\&128\&\u003E\u003Cp\u003EExecute方法的参数message包含了Name,Body,Type三个成员(如下图所示)。其中Name是命令名,Body是一个任意类型的参数。\u003C\u002Fp\u003E\u003Cimg src=\&86c1aaa05182f14caf0dbd79.png\& data-rawwidth=\&355\& data-rawheight=\&348\&\u003E\u003Cp\u003E如下代码所示,在SendMessageCommand中可以给消息的Body传值,相应的Execute方法便可以获取它。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evoid Start() \n{\n\tAppFacade.Instance.RegisterCommand (\&TestMessage\&, \n\t\t\t\t\t\t\ttypeof(TestCommand));\n\tAppFacade.Instance.SendMessageCommand (\&TestMessage\&, \&这是字符串\&);\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cp\u003E运行结果如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&0c5ba3c4c40fb2e3acb03eebe4d1893b.png\& data-rawwidth=\&297\& data-rawheight=\&178\&\u003E\u003Cp\u003E总而言之,LuaFramework中所谓的pureMVC只是一套“注册\u002F分发”机制,完全可以用c#的事件来实现。另《Unity3D网络游戏实战》中的客户端网络模块部分也使用的“注册\u002F分发”机制,有兴趣的读者可以看看。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E\u003Cb\u003E3、MVC与Unity3D组件的结合\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003EpureMVC与Unity3D组件之间有一些封装,只要让组件继承View类(View类继承MonoBehavior),即使用pureMVC框架的RegisterMessage和SendMessageComman方法实现“注册\u002F分发”机制。\u003C\u002Fp\u003E\u003Cp\u003E例如,新建一个继承自View的TestManage组件,在Start 方法中它注册了“msg1”、“msg2”、“msg3”三个消息的监听。在Update方法中,当按下空格键时,分发消息“msg1”。\u003C\u002Fp\u003E\u003Cp\u003E当接收到消息后,指定对象(这里指定this)的OnMessage方法会被调用,参数message里面包含了命令名、Body等信息。代码如下所示。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\nusing System.Collections.G\n \npublic class TestManage : View \n{\n \n\t\u002F\u002F Use this for initialization\n\tvoid Start () \n\t{\n\t\tList&string& regList = new List&string&();\n\t\tregList.Add(\&msg1\&);\n\t\tregList.Add(\&msg2\&);\n\t\tregList.Add(\&msg3\&);\n \n\t\tRegisterMessage(this,regList);\n\t}\n\t\n\t\u002F\u002F Update is called once per frame\n\tvoid Update () \n\t{\n\t\tif (Input.GetKeyUp (KeyCode.Space)) \n\t\t{\n\t\t\tfacade.SendMessageCommand(\&msg1\&, null);\n\t\t}\n\t}\n \n\tpublic override void OnMessage(IMessage message) \n\t{\n\t\tDebug.Log (\&OnMessage \& + message.Name);\n\t}\n}\u003C\u002Fcode\u003E\u003Cp\u003E此外LuaFramework的各个Manager(如GameManager,LuaManager,SoundManager等)也都继承自View类,可以使用“注册\u002F分发”机制。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E后记\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E也许我对pureMVC的设计理解不足,但在LuaFramework中,我依然认为它过度设计。最后依然是广告时间。\u003C\u002Fp\u003E\u003Cp\u003E笔者即将出版的一本Unity3D实战类书籍《Unity3D网络游戏实战》。通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。下图为《Unity3D网络游戏实战》的示例游戏。\u003C\u002Fp\u003E\u003Cimg src=\&5cca46ec68caf433eba29ee.png\& data-rawwidth=\&692\& data-rawheight=\&390\&\u003E\u003Cp\u003E其他一些文章的连接:\u003C\u002Fp\u003E\u003Cp\u003EUnity3D热更新LuaFramework入门实战(1)——代码热更新\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002Fp\u002F\&\u003E\u003Cu\u003Ehttp:\u002F\\u002Fp\u002F3C\u002Fu\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003EUnity3D热更新LuaFramework入门实战(2)——资源热更新\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002Fp\u002F\&\u003E\u003Cu\u003Ehttp:\u002F\\u002Fp\u002F3C\u002Fu\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003EUnity3D热更新LuaFramework入门实战(3)——编写Lua逻辑\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002Fp\u002F\&\u003E\u003Cu\u003Ehttp:\u002F\\u002Fp\u002F3C\u002Fu\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003EAR仙剑:一款手机应用,配合手机或pad使用,能将二次元动漫人物零距离展现在你的身旁!\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\\u002Farpal\u002F\&\u003E\u003Cu\u003Ehttp:\\u002Farpal\u002F\u003C\u002Fu\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E《不会电脑也汇编》笔者N多年前写的汇编教程\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002Fs\u002F1pJBs1mz\&\u003E\u003Cu\u003Ehttp:\u002F\\u002Fs\u002F1pJBs1mz\u003C\u002Fu\u003E\u003C\u002Fa\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T13:09:57.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:10,&likeCount&:5,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T21:09:57+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002F8da3418eedcdde84f471d_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:10,&likesCount&:5},&&:{&title&:&Unity3D热更新LuaFramework入门实战(8)——声音管理器&,&author&:&pyluo&,&content&:&\u003Cp\u003ELuaFramework内置的管理器包括GameManager(处理热更新)、luaManager(lua脚本管理器)、PanelManager(界面管理器)、NetworkManager(网络管理器)、ResourceManager(资源管理器)、TimerManager(时间管理器)、线程管理器(ThreadManager)和SoundManager(声音管理器)。其中GameManager、luaManager、PanelManager、NetworkManager、ResourceManager在前面的文章中已经有过介绍,这一篇讲讲讲播放声音相关的SoundManager。\u003C\u002Fp\u003E\u003Cp\u003Eby 罗培羽\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E1、使用方法\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003ESoundManager估计是从其他地方拷过来的,并不能很好的与框架结合,这里我们先看看原来的SoundManager的使用方法,再介绍它的不足之处及改进方法。虽然SoundManager定义了好几个方法,但能直接在lua中使用的只有用于播放背景音乐的PlayBacksound。\u003C\u002Fp\u003E\u003Cp\u003E编写播放声音的代码前,需要在GameManager上挂载AudioSource组件,以播放背景音乐。\u003C\u002Fp\u003E\u003Cimg src=\&ae823f297d76c1db82450f.png\& data-rawwidth=\&474\& data-rawheight=\&277\&\u003E\u003Cp\u003E把声音文件放到Resource目录下,由于SoundManager使用Resources.Load加载声音文件,声音文件必须放到这个目录下。如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&f1acac3ddb9f1259aaf6d77.png\& data-rawwidth=\&172\& data-rawheight=\&90\&\u003E\u003Cp\u003E然后编写lua代码,调用soundMgr:PlayBacksound即可,它的第一个参数指明Resource目录下的文件名,第二个参数为true表示开始播放,false表示停止播放。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--主入口函数。从这里开始lua逻辑\nfunction Main()\t\t\t\t\t\n\tLuaHelper = LuaFramework.LuaH\n\tsoundMgr = LuaHelper.GetSoundManager();\n\tsoundMgr:PlayBacksound(\&motor\&, true)\nend\u003C\u002Fcode\u003E\u003Cp\u003E运行游戏,即可听到音效。关于Unity3D播放声音的内容,大家也可以参考《Unity3D网络游戏实战》哦!\u003C\u002Fp\u003E\u003Ch2\u003E2、\u003Cb\u003E代码解析\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003ESoundManager的示意代码如下,实际上是使用Resources.Load来加载资源的,所以声音文件必须放在Resources目录下。\u003C\u002Fp\u003E\u003Cimg src=\&aa4d7bd7bec7e9eca27eb.png\& data-rawwidth=\&679\& data-rawheight=\&557\&\u003E\u003Cp\u003E这种用法违背了热更新框架的设计,因为在Resources目录下的文件并不能热更。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E3、改进的声音管理器\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E这一部分我们需要改进声音管理器,实现这么几个功能:\u003C\u002Fp\u003E\u003Cp\u003E1)从本地“数据目录”读取打包后的声音文件,使它支持热更新;\u003C\u002Fp\u003E\u003Cp\u003E2)添加播放\u002F停止背景音乐的PlayBackSound\u002FStopBackSound和播放音效的PlaySound方法;\u003C\u002Fp\u003E\u003Cp\u003E3)使用缓存存储加载后的声音文件,以提高运行效率。\u003C\u002Fp\u003E\u003Cp\u003E为了支持加载AudioClip的加载,在ResourceManager中添加LoadAudioClip方法,该方法将会加载资源包abName的资源assetName,加载完AudioClip资源后调用回调函数func,代码如下。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F载入音效资源\npublic void LoadAudioClip(string abName, \n
string assetName, Action&UObject[]& func) \n{\n\tLoadAsset&AudioClip&(abName, new string[] { assetName }, func);\n}\u003C\u002Fcode\u003E\u003Cp\u003E修改SoundManager,使用Hashtable类型的sounds存储加载后的声音,包含PlayBackSound、StopBackSound和PlaySound三个API。代码如下所示。\u003C\u002Fp\u003E\u003Cp\u003E先是整体kuan\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\nusing System.Collections.G\n \nnamespace LuaFramework \n{\n
public class SoundManager : Manager \n
{\n\tprivate AudioS\n\tprivate Hashtable sounds = new Hashtable();\n\tstring backSoundKey = \&\&;\n \n\tvoid Start() \n\t{\n\t\taudio = GetComponent&AudioSource&();\n\t\tif (audio == null)\n\t\t\tgameObject.AddComponent&AudioSource& ();\n\t}\n \n\t\u002F\u002F回调函数原型\n\tprivate delegate void GetBack(AudioClip clip, string key);\n \n\t\u002F\u002F获取声音资源\n\tprivate void Get(string abName, string assetName, GetBack cb)\n\t{\n\t\tstring key = abName + \&.\& + assetN\n\t\tif(sounds [key] == null) \n\t\t{\n\t\t\tResManager.LoadAudioClip(abName, assetName, (objs)=&\n\t\t\t{\n\t\t\t\tif(objs == null || objs[0] == null)\n\t\t\t\t{\n\t\t\t\t\tDebug.Log(\&PlayBackSound fail);\n\t\t\t\t\tcb(null,key);\n\t\t\t\t\\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tsounds.Add(key, objs[0]);\n\t\t\t\t\tcb(objs[0] as AudioClip ,key);\n\t\t\t\t\\n\t\t\t\t}\n\t\t\t});\n\t\t} \n\t\telse \n\t\t{\n\t\t\tcb(sounds [key] as AudioClip,key);\n\t\t\\n\t\t}\n\t}\n
}\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cp\u003EPlayBackSound:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\t\u002F\u002F播放背景音乐\n\tpublic void PlayBackSound(string abName, string assetName)\n\t{\n\t\tbackSoundKey = abName + \&.\& + assetN\n\t\tGet(abName, assetName,(clip, key)=&\n\t\t{\n\t\t\tif(clip == null)\n\t\t\t\\n\t\t\tif(key != backSoundKey)\n\t\t\t\\n \n\t\t\taudio.loop =\n\t\t\taudio.clip =\n\t\t\taudio.Play();\n\t\t});\n\t}\n\u003C\u002Fcode\u003E\u003Cp\u003EStopBackSound:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\t\u002F\u002F停止背景音乐\n\tpublic void StopBackSound()\n\t{\n\t\tbackSoundKey = \&\&;\n\t\taudio.Stop ();\n\t}\n\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Ccode lang=\&text\&\u003E \t\u002F\u002F播放音效\n\tpublic void PlaySound(string abName, string assetName)\n\t{\n\t\tGet(abName, assetName,(clip, key)=&\n\t\t{\n \n\t\t\tif(clip == null)\n\t\t\t\\n\t\t\tif(Camera.main == null)\n\t\t\t\\n\t\tAudioSource.PlayClipAtPoint(clip, \n\t\t\t\t\tCamera.main.transform.position); \n\t\t});\n\t}\n\u003C\u002Fcode\u003E\u003Cp\u003E修改代码后,需要重新生成wrap文件(点击菜单栏的Lua→Clear wrap files和Lua→Generate All)。然后编写lua代码调试它吧!这里演示的是先播放背景音乐,3秒后停止播放,每隔0.3秒播放一次音效的功能。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--主入口函数。从这里开始lua逻辑\nfunction Main()\t\t\t\t\t\n\tLuaHelper = LuaFramework.LuaH\n\tsoundMgr = LuaHelper.GetSoundManager();\n\tsoundMgr:PlayBackSound(\&sound\&, \&motor\&)\n\tUpdateBeat:Add(Update, self)\nend\n \n \nlocal lastTime = 0\n \nfunction Update()\n\tif Time.time & 3 then\n\t\tsoundMgr:StopBackSound();\n\tend\n\t\n\t\n\tif Time.time - lastTime & 0.3 then\n\t\tsoundMgr:PlaySound(\&sound\&, \&shoot\&)\n\t\tlastTime = Time.time\n\tend\nend\u003C\u002Fcode\u003E\u003Cp\u003E读者还可以使用类似 LoadAudioClip的方法加载其他资源,笔者也是刚刚接触LuaFramework不久,文章错误之处在所难免,请大家多加包涵。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E最后是广告时间:\u003C\u002Fp\u003E\u003Cp\u003E《16年的长度 记录中国独立游戏》从十多年前程序员写的小玩儿,到如今使用游戏引擎开发的炫酷产品,独立游戏作者的故事见证了我国游戏业的发展历程。来看看国产单机的造梦者们,在这十几年中做出怎么的作品,现在又过得如何。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\.cn\u002Fzl\u002Fduanpian\u002F\u002F1538151.shtml\& data-editable=\&true\& data-title=\&16年的长度 记录中国独立游戏\&\u003E16年的长度 记录中国独立游戏\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E《仙剑5前传之心愿》是笔者两年前发起的一款仙剑同人游戏,使用Unity3D制作,是市面上第一款能够完成的3D仙剑同人游戏。如今我们各自踏上游戏开发一途,似乎要做点什么以致敬国产经典之作,在追求商业成就的同时,勿忘初心。可以在\u003Ca href=\&http:\u002F\\& data-editable=\&true\& data-title=\&仙剑5前传之心愿\&\u003E仙剑5前传之心愿\u003C\u002Fa\u003E下载该游戏。《仙剑5前传之心愿》截图如下。\u003C\u002Fp\u003E\u003Cimg src=\&89c22952d3fee3b8e37b.png\& data-rawwidth=\&625\& data-rawheight=\&338\&\u003E\u003Cbr\u003E&,&updated&:new Date(&T13:29:28.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:1,&likeCount&:11,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T21:29:28+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Faa4d7bd7bec7e9eca27eb_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:1,&likesCount&:11},&&:{&title&:&Unity3D热更新LuaFramework入门实战(9)——线程管理器&,&author&:&pyluo&,&content&:&\u003Cp\u003ELuaFramework内置了线程管理器ThreadManager,一开始我以为这是个创建线程、终止线程等方法的封装。然而不是,它是热更新时使用线程下载资源的具体实现。那让我们来看看线程管理器的工作原理吧。\u003C\u002Fp\u003E\u003Cp\u003E@罗培羽\u003C\u002Fp\u003E\u003Ch2\u003E1、GameManager的调用\u003C\u002Fh2\u003E\u003Cp\u003E那么先看看在热更新过程中哪些地方调用到ThreadManager。热更新由GameManager执行(相关代码如下图所示),它在对比本地文件和网络资源的差异后,将需要下载的文件名存放到列表中,然后遍历列表,调用BeginDownload下载。从代码可以看出,它通过ISDownOK判断该文件是否下载完成,然后下载下一个文件,一个个的下载文件。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9d2ec6aa2bda6504025a.png\& data-rawwidth=\&692\& data-rawheight=\&347\&\u003E\u003Cp\u003EBeginDownload(代码如下所示)便调用ThreadManager的AddEvent方法。ThreadManager并不是真正意义上的线程管理器,它只管理一条“下载线程”,通过AddEvent将要下载的文件名放到“代办列表”中,该线程依次下载它们。其中的OnThreadCompleted是“回调函数”,在下载该文件后,会通过消息的方式回调它。\u003C\u002Fp\u003E\u003Cimg src=\&v2-8bcf6b58df897c65d2f8.png\& data-rawwidth=\&663\& data-rawheight=\&188\&\u003E\u003Cp\u003E在“下载线程”下载完一个文件后,它以通知的形式调用“回调函数”OnThreadCompleted(代码如下所示),该方法将会设置“下载完成列表”downloadFiles。\u003C\u002Fp\u003E\u003Cimg src=\&v2-bc9da8e0c52fb864c71f7d2.png\& data-rawwidth=\&604\& data-rawheight=\&322\&\u003E\u003Cp\u003E再看看IsDownOk(代码如下所示)方法,当“下载完成列表”包含该文件时,说明下载已经完成,可以进行下一个文件的下载。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c5246dcbef061ee5be76bef416ef31f7.png\& data-rawwidth=\&429\& data-rawheight=\&155\&\u003E\u003Ch2\u003E2、ThreadManager的启动\u003C\u002Fh2\u003E\u003Cp\u003EThreadManager启动时,开启一个线程“下载线程”,相关代码如下所示。由此ThreadManager仅仅是管理一条线程,而不是真正意义的线程管理器。\u003C\u002Fp\u003E\u003Cimg src=\&v2-ca7ca068f4bd3b9db078d86c94c6a35c.png\& data-rawwidth=\&437\& data-rawheight=\&227\&\u003E\u003Cimg src=\&v2-8f440fdb7b832b13c1b6f2cfd85a49ba.png\& data-rawwidth=\&610\& data-rawheight=\&27\&\u003E\u003Cbr\u003E\u003Ch2\u003E3、AddEvent方法\u003C\u002Fh2\u003E\u003Cp\u003EAddEvent是给线程添加任务的方法,代码如下,其实就是给events队列添加一个值。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9c070f5ecfffe553c27ef51fdace5300.png\& data-rawwidth=\&639\& data-rawheight=\&205\&\u003E\u003Cp\u003EEvents的定义如下所示:\u003C\u002Fp\u003E\u003Cimg src=\&v2-8f440fdb7b832b13c1b6f2cfd85a49ba.png\& data-rawwidth=\&610\& data-rawheight=\&27\&\u003E\u003Cp\u003EThreadEvent包含事件名key和参数evParams,代码如下所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-6eeb42027aff6c71cd301bbed0da3651.png\& data-rawwidth=\&558\& data-rawheight=\&100\&\u003E\u003Ch2\u003E3、下载过程\u003C\u002Fh2\u003E\u003Cp\u003E“下载线程”执行了OnUpdate方法(代码如下所示),它调用OnDownloadFile下载文件。\u003C\u002Fp\u003E\u003Cimg src=\&v2-2fbd6f850e.png\& data-rawwidth=\&692\& data-rawheight=\&478\&\u003E\u003Cp\u003EOnDownloadFile(代码如下所示)又调用了DownloadFileAsync下载文件,下载文件过程中ProgressChanged方法会被调用。\u003C\u002Fp\u003E\u003Cimg src=\&v2-bf56ebec48.png\& data-rawwidth=\&692\& data-rawheight=\&193\&\u003E\u003Cp\u003EProgressChanged方法记录了下载进度,当进度为100%时,使用m_SyncEvent发送通知,相当于调用“回调函数”OnThreadCompleted。\u003C\u002Fp\u003E\u003Cimg src=\&v2-582eacd240acad3e08aca6fc9fbc7502.png\& data-rawwidth=\&692\& data-rawheight=\&271\&\u003E\u003Cbr\u003E\u003Ch2\u003E4、改进\u003C\u002Fh2\u003E\u003Cp\u003E这套线程管理器依然有“杀鸡用牛刀”之嫌,“任务列表”并没有实际作用(因为GameManager控制了下载进度,一个个下载),消息分发部分也太复杂,实际上只用回调函数之类的方法便能够实现。\u003C\u002Fp\u003E\u003Cp\u003E个人认为线程管理器应当提供线程调度的方法,具体的下载逻辑可在GameManager中实现。而且下载功能不一定非要用线程,协程也能够解决,而且更简单。代码如下所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-eabafd8932.png\& data-rawwidth=\&545\& data-rawheight=\&178\&\u003E\u003Cp\u003E由于热更新需要下载不少的文件,一个个下载实在太慢。如果能开启多个线程,同时下载,可在一定程度上提高下载速度。\u003C\u002Fp\u003E\u003Cp\u003E框架并没有处理下载失败的情况,一般情况下,当一个文件下载失败,应当重试,在重试多次依然无法下载时,才弹出错误。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E最后是广告时间:\u003C\u002Fh2\u003E\u003Cp\u003E《16年的长度 记录中国独立游戏》从十多年前程序员写的小玩儿,到如今使用游戏引擎开发的炫酷产品,独立游戏作者的故事见证了我国游戏业的发展历程。来看看国产单机的造梦者们,在这十几年中做出怎么的作品,现在又过得如何。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\.cn\u002Fzl\u002Fduanpian\u002F\u002F1538151.shtml\& data-editable=\&true\& data-title=\&16年的长度 记录中国独立游戏\& class=\&\&\u003E16年的长度 记录中国独立游戏\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E《仙剑5前传之心愿》是笔者两年前发起的一款仙剑同人游戏,使用Unity3D制作,是市面上第一款能够完成的3D仙剑同人游戏。如今我们各自踏上游戏开发一途,似乎要做点什么以致敬国产经典之作,在追求商业成就的同时,勿忘初心。可以在\u003Ca href=\&http:\u002F\\u002F\& data-editable=\&true\& data-title=\&仙剑5前传之心愿\& class=\&\&\\u003C\u002Fa\u003E下载该游戏。《仙剑5前传之心愿》截图如下。\u003C\u002Fp\u003E\u003Cimg src=\&v2-4b12ef4dbc4aa0af3dbb89.png\& data-rawwidth=\&625\& data-rawheight=\&338\&\u003E&,&updated&:new Date(&T13:54:21.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:5,&likeCount&:3,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T21:54:21+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:5,&likesCount&:3},&&:{&title&:&Unity3D热更新LuaFramework入门实战(10)——示例程序&,&author&:&pyluo&,&content&:&\u003Cp\u003E终于到了本系列完结的时候了。\u003C\u002Fp\u003E\u003Cp\u003E现在,大家对LuaFramework有个全方位的理解了吧!接下来通过一个例子总结ulua,作为“lua逻辑”的延伸,说明lua的写法。这个例子中玩家能够控制2D游戏角色走动,并且发射炮弹。\u003C\u002Fp\u003E\u003Ch2\u003E1、目标\u003C\u002Fh2\u003E\u003Cp\u003E制作如图所示的游戏,玩家可以通过键盘控制角色上下左右移动,角色有4个面向,走动过程中会播放行走动画。当玩家点击鼠标左键,角色会发射一颗炮弹。\u003C\u002Fp\u003E\u003Cimg src=\&v2-0bba3766f7cec9f313c7ab33f7884ba1.png\& data-rawwidth=\&564\& data-rawheight=\&367\&\u003E\u003Ch2\u003E2、游戏资源\u003C\u002Fh2\u003E\u003Cp\u003E使用下图所示的图片作为游戏角色(该图片来自rpg maker),在导入Unity后将它切割成12张小图。\u003C\u002Fp\u003E\u003Cimg src=\&v2-afd34cc68c372bee35b3ab.png\& data-rawwidth=\&180\& data-rawheight=\&240\&\u003E\u003Cimg src=\&v2-0ff319fc86b1c5c2e9e5b8.png\& data-rawwidth=\&692\& data-rawheight=\&77\&\u003E\u003Cbr\u003E\u003Cp\u003E使用如下图所示的图片作为炮弹。\u003C\u002Fp\u003E\u003Cimg src=\&v2-b4c8d9ee317d96d6f2ec9b3.png\& data-rawwidth=\&62\& data-rawheight=\&62\&\u003E\u003Cp\u003E在游戏场景中新建画布,画布下摆放一个名为Panel的面板,代表游戏场景。面板下有res和map两个子物体,map(Image)为一张场景图,role(Image)为游戏中的角色,bullet(Image)为游戏中的子弹(同一时间只能发射一颗子弹)。\u003C\u002Fp\u003E\u003Cimg src=\&v2-bc89b5f2240.png\& data-rawwidth=\&692\& data-rawheight=\&325\&\u003E\u003Cp\u003ERes子物体存放12张角色图片(Image),之后会使用这些资源替换map.role的图片,以实现动画效果。\u003C\u002Fp\u003E\u003Cimg src=\&v2-f5e4fd51fb7.png\& data-rawwidth=\&692\& data-rawheight=\&340\&\u003E\u003Cp\u003E然后将Panel做成预设,存放到SimpleGame目录下。并且在GameManager空物体上添加Game组件,以启动框架。\u003C\u002Fp\u003E\u003Cimg src=\&v2-8ab54e8ea13bd4ac254b4.png\& data-rawwidth=\&693\& data-rawheight=\&596\&\u003E\u003Cp\u003E修改Packager.cs,在HandleExampleBundle添加如下代码,将SimpleGame目录下的预设打包。然后点击LuaFramework→Build Windows Resource打包。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F小游戏\nAddBuildMap(\&SimpleGame\& + AppConst.ExtName, \&*.prefab\&, \&Assets\u002FSimpleGame\&);\u003C\u002Fcode\u003E\u003Cp\u003E具体的框架设置请参加第一篇和第二篇,这里仅做简单描述。\u003C\u002Fp\u003E\u003Ch2\u003E3、编写行走代码\u003C\u002Fh2\u003E\u003Cp\u003E游戏使用UI组件,在CustomSettings.cs中添加如下两行,使tolua生成Image和Sprite相关的调用。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E_GT(typeof(Image)),\n_GT(typeof(Sprite)),\u003C\u002Fcode\u003E\u003Cp\u003E打开main.lua(如何运行main.lua请参见第一篇)开始编写代码。程序从Main方法开始执行,使用LoadPrefab(请参见第二篇)加载之前打包的资源文件Panel。这里还定义几个变量,其中map代表游戏场景(panel.map),role代表游戏角色(panel.map.role),roleImage是游戏角色中的图片组件,roleRes代表各个面向的角色图片,比如roleRes[“UP”]将会包含panel.res中3张角色朝上的图。roleAnm代表当前角色的动画,每个面向有3个动画,对应于不同的图片。lastAnmTime代表展现角色动画帧的时间,用于控制动画播放速度。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--主入口函数。从这里开始lua逻辑\nfunction Main()\t\t\t\t\t\n\tLuaHelper = LuaFramework.LuaH\n\tresMgr = LuaHelper.GetResManager();\n\tresMgr:LoadPrefab('SimpleGame', { 'Panel' }, OnLoadFinish);\nend\n\nlocal map\nlocal role\nlocal roleImage\nlocal roleRes = {\n\t[\&UP\&] = {},\n\t[\&DOWN\&] = {},\n\t[\&LEFT\&] = {},\n\t[\&RIGHT\&] = {},\n}\nlocal roleAnm = 1;\nlocal lastAnmTime = 0;\n\n--加载完成后的回调--\nfunction OnLoadFinish(objs)\n
--暂略\nend\u003C\u002Fcode\u003E\u003Cp\u003E接着编写加载完成的回调方法OnLoadFinish,它处理下面几件事情。\u003C\u002Fp\u003E\u003Cp\u003E1、使用Instantiate实例化面板,并且设置面板的坐标,具体请参见第5篇。\u003C\u002Fp\u003E\u003Cp\u003E2、获取面板中的部件,给map、role、roleImage赋值。\u003C\u002Fp\u003E\u003Cp\u003E3、获取素材res中的图片,赋值给roleRes,之后roleRes [\&UP\&],roleRes [\&DOWN\&],roleRes [\&LEFT\&],roleRes [\&RIGHT\&]都包含3张同面向不同动画的图片。\u003C\u002Fp\u003E\u003Cp\u003E4、使用UpdateBeat:Add()初始化Update方法(具体参照第三篇)。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction OnLoadFinish(objs)\n\t--显示面板\n\tgo = UnityEngine.GameObject.Instantiate(objs[0])\n\tlocal parent = UnityEngine.GameObject.Find(\&Canvas\&)\n
go.transform:SetParent(parent.transform, false)\n\t--获取元素\n\tmap = go.transform:FindChild(\&map\&).gameObject\n\trole = map.transform:FindChild(\&role\&).gameObject\n\troleImage = role:GetComponent(\&Image\&)\n\t--获取素材\n\tlocal res = go.transform:FindChild(\&res\&).gameObject\n\troleRes[\&DOWN\&][1] = res.transform:FindChild(\&role (0)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&DOWN\&][2] = res.transform:FindChild(\&role (1)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&DOWN\&][3] = res.transform:FindChild(\&role (2)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&LEFT\&][1] = res.transform:FindChild(\&role (3)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&LEFT\&][2] = res.transform:FindChild(\&role (4)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&LEFT\&][3] = res.transform:FindChild(\&role (5)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&RIGHT\&][1] = res.transform:FindChild(\&role (6)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&RIGHT\&][2] = res.transform:FindChild(\&role (7)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&RIGHT\&][3] = res.transform:FindChild(\&role (8)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&UP\&][1] = res.transform:FindChild(\&role (9)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&UP\&][2] = res.transform:FindChild(\&role (10)\&).gameObject:GetComponent(\&Image\&).sprite\n\troleRes[\&UP\&][3] = res.transform:FindChild(\&role (11)\&).gameObject:GetComponent(\&Image\&).sprite\n\t--UpdateBeat\n\tUpdateBeat:Add(Update, self)\nend\u003C\u002Fcode\u003E\u003Cp\u003E编写Update方法,它根据用户输入改变坐标(具体参见第三篇),并且根据不同的移动方向设置角色图片素材,将roleImage.sprite替换成roleRes[方向][动画索引]。最后判断“if Time.time - lastAnmTime & 0.1 then”,每隔0.1秒切换一次动画。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--每帧执行\nfunction Update()\n\t\n\t--移动\n\tlocal Input = UnityEngine.I\n\tlocal horizontal = Input.GetAxis(\&Horizontal\&);\n\tlocal verticla = Input.GetAxis(\&Vertical\&);\n\t\n\tlocal x = role.transform.position.x + horizontal\n\tlocal y = role.transform.position.y + verticla\n\trole.transform.position = Vector3.New(x,y,0)\n\t--转向\n\tif horizontal & 0 then\n\t\troleImage.sprite = roleRes[\&LEFT\&][roleAnm]\n\telseif horizontal & 0 then\n\t\troleImage.sprite = roleRes[\&RIGHT\&][roleAnm]\n\telseif verticla & 0 then\n\t\troleImage.sprite = roleRes[\&UP\&][roleAnm]\n\telseif verticla & 0 then\n\t\troleImage.sprite = roleRes[\&DOWN\&][roleAnm]\n\tend\n\t--步伐(动画索引)\n\tif Time.time - lastAnmTime & 0.1 then\n\t\troleAnm = roleAnm+1\n\t\tif roleAnm & 3 then roleAnm = 1 end\n\t\tlastAnmTime = Time.time\n\tend\n\t\nend\u003C\u002Fcode\u003E\u003Cp\u003E运行游戏,玩家可以通过键盘的方向键控制角色移动。\u003C\u002Fp\u003E\u003Cimg src=\&v2-59f762a794afcfcb6130e0.png\& data-rawwidth=\&593\& data-rawheight=\&337\&\u003E\u003Ch2\u003E3、编写射击代码\u003C\u002Fh2\u003E\u003Cp\u003E在main.lua中添加炮弹相关的变量,如下所示。其中bullet代表炮弹元件(panel.map.bullet),lastShootTime 代表上一次发射炮弹的时间,bulletSpeedX代表炮弹的水平移动速度,bulletSpeedY代表炮弹的垂直移动速度,roleFace代表角色的面向。然后在OnLoadFinish中给bullet赋值。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Elocal bullet\nlocal lastShootTime = -100\nlocal bulletSpeedX = 0\nlocal bulletSpeedY = 0\nlocal roleFace = 0\n\nfunction OnLoadFinish(objs)\n\t……\n\t--子弹元素\n\tbullet = map.transform:FindChild(\&bullet\&).gameObject\nEnd\u003C\u002Fcode\u003E\u003Cp\u003E在Update中给roleFace赋值。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--每帧执行\nfunction Update()\n\t\n\t--移动\n\t……\n\t--转向\n\tif horizontal & 0 then\n\t\troleImage.sprite = roleRes[\&LEFT\&][roleAnm]\n\t\troleFace = 1\n\telseif horizontal & 0 then\n\t\troleImage.sprite = roleRes[\&RIGHT\&][roleAnm]\n\t\troleFace = 2\n\telseif verticla & 0 then\n\t\troleImage.sprite = roleRes[\&UP\&][roleAnm]\n\t\troleFace = 3\n\telseif verticla & 0 then\n\t\troleImage.sprite = roleRes[\&DOWN\&][roleAnm]\n\t\troleFace = 4\n\tend\n\t--步伐\n\t……\nend\u003C\u002Fcode\u003E\u003Cp\u003E在Update中添加处理炮弹的代码,它处理如下几件事情。\u003C\u002Fp\u003E\u003Cp\u003E1、炮弹在飞行1.2秒后,燃尽消失;\u003C\u002Fp\u003E\u003Cp\u003E2、当玩家按下鼠标左键时,发射炮弹,根据角色面向,bulletSpeedX和bulletSpeedY会有不同的值。\u003C\u002Fp\u003E\u003Cp\u003E3、根据bulletSpeedX和bulletSpeedY移动炮弹。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E--每帧执行\nfunction Update()\n\t……\n\t--子弹\n\tif Time.time - lastShootTime & 1.2 then\n\t\t--消失\n\t\tif bullet.transform.position.x ~= -999 then\n\t\t\tbullet.transform.position = Vector3.New(-999,-999,0)\n\t\tend\n\t\t--发射\n\t\tif Input.GetMouseButton(0) then\n\t\t\tbullet.transform.position = Vector3.New(x,y,0)\n\t\t\tif roleFace == 1 then\n\t\t\t\tbulletSpeedX = -10\n\t\t\t\tbulletSpeedY = 0\n\t\t\telseif roleFace == 2
then\n\t\t\t\tbulletSpeedX = 10\n\t\t\t\tbulletSpeedY = 0\n\t\t\telseif roleFace == 3 then\n\t\t\t\tbulletSpeedX = 0\n\t\t\t\tbulletSpeedY = 10\n\t\t\telseif roleFace == 4
then\n\t\t\t\tbulletSpeedX = 0\n\t\t\t\tbulletSpeedY = -10\n\t\t\tend\n\t\t\tlastShootTime = Time.time\n\t\tend\n\telse\n\t\t--运动\n\t\tlocal x = bullet.transform.position.x + bulletSpeedX\n\t\tlocal y = bullet.transform.position.y + bulletSpeedY\n\t\tbullet.transform.position = Vector3.New(x,y,0)\n\tend\nend\u003C\u002Fcode\u003E\u003Cp\u003E运行游戏,点击鼠标左键,角色发射炮弹。另外也可以用基于组件的方法实现,这里就不展开了。\u003C\u002Fp\u003E\u003Cimg src=\&v2-2a28e210b4e23f2998ac6.png\& data-rawwidth=\&598\& data-rawheight=\&337\&\u003E\u003Cp\u003E最后依然到了广告时间:笔者即将出版的一本Unity3D实战类书籍《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e629e8fa3c93fe1f563be632d3a1ff2.png\& data-rawwidth=\&692\& data-rawheight=\&692\&\u003E&,&updated&:new Date(&T14:46:58.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:2,&likeCount&:2,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T22:46:58+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:2,&likesCount&:2},&&:{&title&:&Unity特效(1)
梦幻旋屏&,&author&:&pyluo&,&content&:&\u003Cp\u003E游戏开发中,往往会用到一些屏幕特效。下图展现的是一种“旋屏”效果,它会旋转屏幕图像,且距离中心点越远的点旋转角度越大。这种效果特别适合营造“梦幻”感,比如,在RPG游戏中,经过一段“旋屏”特效,主角穿越到了10年前。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c55abe1938a8.png\& data-rawwidth=\&748\& data-rawheight=\&571\&\u003E\u003Cbr\u003E\u003Ch2\u003E\u003Cb\u003E1、编写Shader\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E下面的着色器代码使用了顶点\u002F片元着色器处理旋屏特效的功能。这里定义3个属性,其中_MainTex代表屏幕贴图,_Rot 代表基准的旋转角度。核心代码在片元着色器frag中实现。\u003C\u002Fp\u003E\u003Cp\u003E如下图所示,屏幕图像被归一到[0,1]的空间中,中心点为(0.5,0.5)。假设某个点的uv坐标为(x,y),经过一系列处理,它的坐标变为(x1,y1),而(x1,y1)便是实现旋转效果后的uv坐标。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c874b5045afd.png\& data-rawwidth=\&678\& data-rawheight=\&639\&\u003E\u003Cp\u003E由“float distance = sqrt((i.uv.x - 0.5)*(i.uv.x - 0.5) +(i.uv.y - 0.5)*(i.uv.y - 0.5));”可以计算点到屏幕中心的距离distance。由于距离越远旋转角度越大,使用“_Rot *=distance”将角度增量基准与距离联系起来,即可获取需要旋转的角度:angle = _Rot*distance + A。\u003C\u002Fp\u003E\u003Cp\u003E由反正切公式可得∠A = atan((y - 0.5)\u002F(x - 0.5)),由于atan的取值为[-π\u002F2,π\u002F2],还需根据y值确定∠A所在的象限,故而∠A = step(x,0.5)*PI+ atan((y - 0.5)\u002F(x - 0.5)) 。计算∠A 后,便可由angle = _Rot*distance + A计算总的旋转角度。\u003C\u002Fp\u003E\u003Cp\u003E前面已经计算了点到屏幕中心的距离distance,故而:\u003C\u002Fp\u003E\u003Cp\u003Ex1 = 0.5 + distance *cos(angle)\u003C\u002Fp\u003E\u003Cp\u003Ey1 = 0.5 + distance *sin(angle)\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EShader代码如下所示:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EShader \&Lpy\u002FScreenRot\&\n{\n
Properties\n
_MainTex (\&Main Tex\&, 2D) = \&white\& {}\n
_Rot (\&Rotation\&, float) = 0\n
SubShader\n
Tags {\&Queue\&=\&Geometry\&}\n
Tags { \&LightMode\&=\&ForwardBase\& }\n
ZWrite Off\n\n
CGPROGRAM\n
#pragma vertex vert
#pragma fragment frag\n
#include \&UnityCG.cginc\&\n
#define PI 3.79
sampler2D _MainT\n
float _R\n
struct a2v\n
float4 vertex : POSITION;\n
float3 texcoord : TEXCOORD0;\n
struct v2f\n
float4 pos : SV_POSITION;\n
float2 uv : TEXCOORD0;\n
v2f vert (a2v v)\n
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.\n
fixed4 frag (v2f i) : SV_Target\n
\u002F\u002F与中心点(0.5,0.5)的距离\n
float distance = sqrt((i.uv.x - 0.5)*(i.uv.x - 0.5) +(i.uv.y - 0.5)*(i.uv.y - 0.5));\n
\u002F\u002F距离越大,旋转角度越大\n
\u002F\u002F计算旋转角度\n
float angle = step(i.uv.x,0.5)*PI+ atan((i.uv.y - 0.5)\u002F(i.uv.x - 0.5)) + _R\n
\u002F\u002F计算坐标\n
i.uv.x = 0.5 +
distance *cos(angle);\n
i.uv.y = 0.5 +
distance *sin(angle);\n \n
fixed4 c = tex2D(_MainTex, i.uv);\n
FallBack \&Specular\&\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Ch2\u003E\u003Cb\u003E2、使用材质\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E新建c#文件,编写ScreenRot类,它由一个共有变量mtl,在它的OnRenderImage方法中调用Graphics.Blit将屏幕图像(对应shader中的_MainTex)与材质混合起来。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\n \npublic class ScreenRot : MonoBehaviour \n{\n
public M\n \n
void OnRenderImage(RenderTexture src, RenderTexture dest)\n
Graphics.Blit (src, dest,mtl);\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E然后给新建一个名为ScreenRot的材质,使用上述编写的Shader。然后给摄像机添加ScreenRot组件,设置刚刚创建的材质,如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-8fbdcaad79fc3de9cdec.png\& data-rawwidth=\&383\& data-rawheight=\&339\&\u003E\u003Cp\u003E运行游戏,调整材质的“Rotation”属性,即可看到旋转特效。\u003C\u002Fp\u003E\u003Cimg src=\&v2-48ec859e357a2d46ef253a36ed7849df.png\& data-rawwidth=\&692\& data-rawheight=\&340\&\u003E\u003Ch2\u003E\u003Cb\u003E3、代码中引用\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003EShader中并没有涉及时间的控制,旋转速度需要由c#代码控制,将ScreenRot修改成下面的代码,即可让屏幕自动旋转。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\n \npublic class ScreenRot : MonoBehaviour \n{\n
public M\n \n\n
\u002F\u002F Update is called once per frame\n
void Update ()\n
rot += 0.1f;\n
void OnRenderImage(RenderTexture src, RenderTexture dest)\n
if (rot == 0.0)\\n
mtl.SetFloat(\&_Rot\&, rot);\n
Graphics.Blit (src, dest,mtl);\n
}\n}\u003C\u002Fcode\u003E\u003Cp\u003E这个效果能够运用在很多场合,比如使用“正向旋转→切换场景→反向旋转”实现切屏特效。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E最后依然到了广告时间:笔者出版了一本Unity3D实战类书籍《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e629e8fa3c93fe1f563be632d3a1ff2.png\& data-rawwidth=\&692\& data-rawheight=\&692\&\u003E\u003Cp\u003EUnity3D热更新框架教程:\u003Ca href=\&https:\u002F\\u002Fp\u002F\& data-editable=\&true\& data-title=\&https:\u002F\\u002Fp\u002F?refer=pyluo\& class=\&\&\u003Ehttps:\u002F\\u002Fp\u002F3C\u002Fa\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T12:56:18.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:23,&likeCount&:60,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T20:56:18+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-48ec859e357a2d46ef253a36ed7849df_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:23,&likesCount&:60},&&:{&title&:&Unity特效(2) 图片切割&,&author&:&pyluo&,&content&:&\u003Cp\u003E游戏开发中,常常把多张图片素材合并成图集,然后程序读取所需的部分显示。如下展示的是一张人物行走图,程序需要把它切分成12张小图加以显示。一种做法是使用Sprite Editor切分图集,当做多张小图来处理。如果Image能够只显示图集的一部分,程序还是把图集当做1张图片处理,可以减少不小工作量。本文提供一种使用shader实现上述功能的例子。\u003C\u002Fp\u003E\u003Cimg src=\&v2-833faedb6e4cdb54a189f.png\& data-rawwidth=\&692\& data-rawheight=\&388\&\u003E\u003Cbr\u003E\u003Ch2\u003E1、编写Shader\u003C\u002Fh2\u003E\u003Cp\u003E下面的着色器代码使用了顶点\u002F片元着色器处理图片切割功能。这里定义5个属性,其中_MainTex代表图片贴图,_ColCount和_RowCount代表图片的列数和行数,_ColIndex和_RowIndex表示要显示哪一行哪一列的图片。核心代码是“o.uv.x = (_ColIndex + v.texcoord.x)\u002F_ColCount”和“o.uv.y = (_RowIndex + v.texcoord.y)\u002F_RowCount”,它们实现了UV坐标的变换。“o.uv.x = (_ColIndex + v.texcoord.x)\u002F_ColCount”即是“o.uv.x = _ColIndex*(1\u002F_ColCount) + v.texcoord.x *(1\u002F_ColCount)”的化简式,由于纹理坐标被归一化到[0,1]的范围,1\u002F_ColCount即表示每一张小图的宽度。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EShader \&Lpy\u002FImageClip\& \n{\n\tProperties \n\t{\n\t\t_MainTex (\&Main Tex\&, 2D) = \&white\& {}\n
\t_ColCount (\&Column Count\&, int) = 4\n
\t_RowCount (\&Row Count\&, int) = 4\n
\t_ColIndex (\&Col Index\&, int) = 0\n
\t_RowIndex (\&Row Index\&, int) = 0\n\t}\n\t\n\tSubShader \n\t{\n\t\tTags {\&Queue\&=\&Transparent\& \&IgnoreProjector\&=\&True\& \&RenderType\&=\&Transparent\&}\n\t\t\n\t\tPass \n\t\t{\n\t\t\tTags { \&LightMode\&=\&ForwardBase\& }\n\t\t\tZTest off\n\t\t\tZWrite Off\n\t\t\tBlend SrcAlpha OneMinusSrcAlpha\n\t\t\t\n\t\t\tCGPROGRAM\n\t\t\t#pragma vertex vert
\n\t\t\t#pragma fragment frag\n\t\t\t#include \&UnityCG.cginc\&\n\t\t\n\t\t\tsampler2D _MainT\n\t\t\tint _ColC\n\t\t\tint _RowC\n\t\t\tint _ColI\n\t\t\tint _RowI\n\t\t\t
\n\t\t\tstruct a2v \n\t\t\t{
float4 vertex : POSITION; \n\t\t\t
float2 texcoord : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tstruct v2f \n\t\t\t{
float4 pos : SV_POSITION;\n\t\t\t
float2 uv : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tv2f vert (a2v v) \n\t\t\t{
\n\t\t\t\tv2
\n\t\t\t\to.pos = mul(UNITY_MATRIX_MVP, v.vertex);
\n\t\t\t\t\n\t\t\t\to.uv.x = (_ColIndex + v.texcoord.x)\u002F_ColC\n\t\t\t\to.uv.y = (_RowIndex + v.texcoord.y)\u002F_RowC\n\t\t\t\\n\t\t\t}
\n\t\t\t\n\t\t\tfixed4 frag (v2f i) : SV_Target \n\t\t\t{\n\t\t\t\tfixed4 c = tex2D(_MainTex, i.uv);\n\t\t\t\\n\t\t\t}\n\t\t\tENDCG\n\t\t}
\n\t}\n\tFallBack \&Transparent\u002FVertexLit\&\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E2、使用材质\u003C\u002Fh2\u003E\u003Cp\u003E新建一个名为ImageClip的材质,选择上述编写的shader。将ColumnCount和RowCount设置为图集的列数和行数,如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-ae98f27d3c4ecf3344d90a.png\& data-rawwidth=\&400\& data-rawheight=\&254\&\u003E\u003Cbr\u003E\u003Cp\u003E将刚创建的材质应用于图片上,如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c11ba699f4e20889fae575c242f0ee74.png\& data-rawwidth=\&383\& data-rawheight=\&193\&\u003E\u003Cp\u003E在Scene或Game视图中观察图片,改变材质的ColIndex和RowIndex属性,即可显示不同的小图。如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-84c9c825d1ca32dc9c4d4cfece0f4432.png\& data-rawwidth=\&390\& data-rawheight=\&267\&\u003E\u003Cbr\u003E\u003Ch2\u003E3、代码控制\u003C\u002Fh2\u003E\u003Cp\u003E如下代码展示使用脚本控制材质属性的方法,当按下空格键时,改变材质的RowIndex属性,展现不同小图。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\nusing UnityEngine.UI;\n\npublic class RoleCtrl : MonoBehaviour \n{\n\tpublic I\n\tprivate M\n\t\u002F\u002F Use this for initialization\n\tvoid Start () \n\t{\n\t\tmtl = image.\n\t}\n\t\n\t\u002F\u002F Update is called once per frame\n\tvoid Update () \n\t{\n\t\tif (Input.GetKeyDown (KeyCode.Space)) \n\t\t{\n\t\t\tint row = mtl.GetInt(\&_RowIndex\&);\n\n\t\t\trow++;\n\t\t\tif(row &= 4) \n\t\t\t\trow = 0;\n\n\t\t\tmtl.SetInt(\&_RowIndex\&, row);\n\t\t}\n\t}\n}\n\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Cp\u003E最后依然到了广告时间:笔者即将出版的一本Unity3D实战类书籍《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e629e8fa3c93fe1f563be632d3a1ff2.png\& data-rawwidth=\&692\& data-rawheight=\&692\&\u003E&,&updated&:new Date(&T14:14:02.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:2,&likeCount&:2,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T22:14:02+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:2,&likesCount&:2},&&:{&title&:&Unity特效(3) 图片翻转&,&author&:&pyluo&,&content&:&\u003Cp\u003E2D游戏开发中,往往会使用翻转的图片(如下图),然而Transform的旋转并不能完成这一功能,那么什么办法将图片翻转呢?\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-f5ab4e30fe05aeb15bfdab172d2d5f09.png\& data-rawwidth=\&502\& data-rawheight=\&614\&\u003E\u003Ch2\u003E1、编写Shader\u003C\u002Fh2\u003E\u003Cp\u003E下面的着色器代码使用了顶点\u002F片元着色器处理图片翻转功能。这里定义3个属性,其中_MainTex代表图片贴图,_Hor代表是否启用水平翻转,_Ver代表是否启用垂直翻转。核心代码是“o.uv.x = (1-v.texcoord.x)*_Hor + v.texcoord.x*(1-_Hor)”和“o.uv.y = (1-v.texcoord.y)*_Ver + v.texcoord.y*(1-_Ver);”它们实现了UV坐标的变换。在o.uv.x的计算式中,如果_Hor为0,那么“o.uv.x =
v.texcoord.x”即为原始UV,如果_Hor为1,由于纹理坐标被归一化到[0,1]的范围,那么“o.uv.x = 1-v.texcoord.x”即为翻转后的UV。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EShader \&Lpy\u002FImageFlip\& \n{\n\tProperties \n\t{\n\t\t_MainTex (\&Main Tex\&, 2D) = \&white\& {}\n\t\t\n
\t_Hor (\&Is Horizontal Filp\&, Range (0, 1)) = 0\n
\t_Ver (\&Is Vertical Filp\&, Range (0, 1)) = 0\n\t}\n\t\n\tSubShader \n\t{\n\t\tTags {\&Queue\&=\&Transparent\& \&IgnoreProjector\&=\&True\& \&RenderType\&=\&Transparent\&}\n\t\t\n\t\tPass \n\t\t{\n\t\t\tTags { \&LightMode\&=\&ForwardBase\& }\n\t\t\tZTest off\n\t\t\tZWrite Off\n\t\t\tBlend SrcAlpha OneMinusSrcAlpha\n\t\t\t\n\t\t\tCGPROGRAM\n\t\t\t#pragma vertex vert
\n\t\t\t#pragma fragment frag\n\t\t\t#include \&UnityCG.cginc\&\n\t\t\n\t\t\tsampler2D _MainT\n\t\t\tint _H\n\t\t\tint _V\n\t\t\t
\n\t\t\tstruct a2v \n\t\t\t{
float4 vertex : POSITION; \n\t\t\t
float2 texcoord : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tstruct v2f \n\t\t\t{
float4 pos : SV_POSITION;\n\t\t\t
float2 uv : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tv2f vert (a2v v) \n\t\t\t{
\n\t\t\t\tv2
\n\t\t\t\to.pos = mul(UNITY_MATRIX_MVP, v.vertex);
\n\t\t\t\t\n\t\t\t\to.uv.x = (1-v.texcoord.x)*_Hor + v.texcoord.x*(1-_Hor);\n\t\t\t\to.uv.y = (1-v.texcoord.y)*_Ver + v.texcoord.y*(1-_Ver);\n\t\t\t\\n\t\t\t}
\n\t\t\t\n\t\t\tfixed4 frag (v2f i) : SV_Target \n\t\t\t{\n\t\t\t\tfixed4 c = tex2D(_MainTex, i.uv);\n\t\t\t\\n\t\t\t}\n\t\t\tENDCG\n\t\t}
\n\t}\n\tFallBack \&Transparent\u002FVertexLit\&\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Ch2\u003E2、使用材质\u003C\u002Fh2\u003E\u003Cp\u003E新建一个名为ImageFilp的材质,选择上述编写的shader,设置Is Horizontal Filp和Is Vertical Filp,如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c1eab9ec5c.png\& data-rawwidth=\&345\& data-rawheight=\&253\&\u003E\u003Cp\u003E将刚创建的材质应用于图片上,即可看到翻转的效果。如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-babdd5f36c77.png\& data-rawwidth=\&692\& data-rawheight=\&335\&\u003E\u003Cp\u003E最后依然到了广告时间:笔者出版的一本Unity3D实战类书籍《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e629e8fa3c93fe1f563be632d3a1ff2.png\& data-rawwidth=\&692\& data-rawheight=\&692\&\u003E&,&updated&:new Date(&T12:53:35.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:12,&likeCount&:10,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T20:53:35+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-f5ab4e30fe05aeb15bfdab172d2d5f09_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:12,&likesCount&:10},&&:{&title&:&Unity特效(4) 标题光效&,&author&:&pyluo&,&content&:&\u003Cp\u003E标题光效是一种常见的图片特效,“遮罩层”从左往右经过,起到强调游戏标题的作用,如下图所示。那么怎样用Shader实现这种效果呢?\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-cdbdff03fa.png\& data-rawwidth=\&660\& data-rawheight=\&493\&\u003E\u003Ch2\u003E1、编写Shader\u003C\u002Fh2\u003E\u003Cp\u003E
下面的着色器代码使用了顶点\u002F片元着色器处理标题光效功能。这里定义4个属性,其中_MainTex代表图片贴图,_MaskColor代表遮罩颜色,Speed代表光效的移动速度,_MaskLimit控制着光效的宽度。核心代码为“float isMask = sin(_Time.y*_Speed
-i.uv.x*2*PI );”“isMask = step(_MaskLimit,isMask);”“c.rgb += _MaskColor*isM”这3句。如果isMask为1,代表该片元被遮罩,如果为0,表示不被遮罩,通过“c.rgb += _MaskColor*isM”便可计算片元的颜色。“float isMask = sin(_Time.y*_Speed -i.uv.x*2*PI );”将根据时间和uv的x坐标计算isMask,此时isMask的取值范围为[-1,1]。step(_MaskLimit,isMask)的功能相当于“if(isMask & MaskLimit) return 1; else return 0;”通过_MaskLimit将指定区间的值设为1,其他设为0。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EShader \&Lpy\u002FImageEffect\& \n{\n\tProperties \n\t{\n\t\t_MainTex (\&Main Tex\&, 2D) = \&white\& {}\n\t\t_MaskColor (\&Mask Color\&, Color) = (1, 1, 1, 1)\n\t\t\n\t\t_Speed (\&Speed\&, float) = 2\n\t\t_MaskLimit (\&MaskLimit\&, float) = 0.8\n\t}\n\t\n\tSubShader \n\t{\n\t\tTags {\&Queue\&=\&Transparent\& \&IgnoreProjector\&=\&True\& \&RenderType\&=\&Transparent\&}\n\t\t\n\t\tPass \n\t\t{\n\t\t\tTags { \&LightMode\&=\&ForwardBase\& }\n\t\t\tZTest off\n\t\t\tZWrite Off\n\t\t\tBlend SrcAlpha OneMinusSrcAlpha\n\t\t\t\n\t\t\tCGPROGRAM\n\t\t\t#pragma vertex vert
\n\t\t\t#pragma fragment frag\n\t\t\t#include \&UnityCG.cginc\&\n\t\t\t#define PI 3.79
\n\t\t\t\n\t\t\t\n\t\t\tsampler2D _MainT\n\t\t\tfixed3 _MaskC\n\t\t\tfloat _S\n\t\t\tfloat _MaskL\n\t\t\t\n\t\t\tstruct a2v \n\t\t\t{
float4 vertex : POSITION; \n\t\t\t
float3 texcoord : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tstruct v2f \n\t\t\t{
float4 pos : SV_POSITION;\n\t\t\t
float2 uv : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tv2f vert (a2v v) \n\t\t\t{
\n\t\t\t\tv2
\n\t\t\t\to.pos = mul(UNITY_MATRIX_MVP, v.vertex);
\n\t\t\t\to.uv = v.\n\t\t\t\\n\t\t\t}
\n\t\t\t\n\t\t\tfixed4 frag (v2f i) : SV_Target \n\t\t\t{\n\t\t\t\tfixed4 c = tex2D(_MainTex, i.uv);\n\t\t\t\t\n\t\t\t\tfloat isMask = sin(_Time.y*_Speed
-i.uv.x*2*PI );\n\t\t\t\tisMask = step(_MaskLimit,isMask);\n\t\t\t\t\n\t\t\t\tc.rgb += _MaskColor*isM\n\t\t\t\\n\t\t\t}\n\t\t\tENDCG\n\t\t}
\n\t}\n\tFallBack \&Transparent\u002FVertexLit\&\n}\u003C\u002Fcode\u003E\u003Cbr\u003E\u003Ch2\u003E2、使用材质\u003C\u002Fh2\u003E\u003Cp\u003E新建一个名为ImageEffect的材质,选择上述编写的shader。设置MaskColor、Speed、MaskLimit这3个参数,如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-96d6cd5b76b1c3e5494be6.png\& data-rawwidth=\&350\& data-rawheight=\&267\&\u003E\u003Cbr\u003E\u003Cp\u003E将刚创建的材质应用于图片上,即可看到效果。如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-6ca4456ae5.png\& data-rawwidth=\&693\& data-rawheight=\&317\&\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Cp\u003E最后依然到了广告时间:最后依然到了广告时间:\u003C\u002Fp\u003E\u003Cp\u003E笔者在分享文章的同时也结识了一群兴趣相投的朋友,本周五将会作客游戏蛮牛,在线答疑。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ca href=\&http:\u002F\\u002Fthread--1.html\& data-editable=\&true\& data-title=\&【2.24 某知名游戏公司主程在线答疑 参与有机会得《Unity 3D网络游戏实战》】\& class=\&\&\u003E【2.24 某知名游戏公司主程在线答疑 参与有机会得《Unity 3D网络游戏实战》】\u003C\u002Fa\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T12:40:39.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:0,&likeCount&:1,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T20:40:39+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-cdbdff03fa_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:0,&likesCount&:1},&&:{&title&:&Unity特效(5) 滚动的背景&,&author&:&pyluo&,&content&:&\u003Cp\u003E制作动画时,往往会使用到“滚动的背景”。如下图所示,一开始图片只显示素材的一部分,然后素材不断滚动。该效果可以模拟横版或飞行游戏的背景图,或实现一些动画效果。尽管有很多方法实现该功能,这里提供一种基于shader的方法。\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d9e7d6d51fa27f75b0e48436dee51b06.png\& data-rawwidth=\&692\& data-rawheight=\&240\&\u003E\u003Ch2\u003E1、编写Shader\u003C\u002Fh2\u003E\u003Cp\u003E下面的着色器代码实现了“滚动的背景”功能。这里定义了3个变量,其中_MainTex代表背景贴图,_Width代表显示背景的百分比,_Distance代表当前滚动的距离。核心代码为“i.uv.x = frac(i.uv.x*_Width + _Distance);”,其中frac是取小数的函数,如1.23 取出来是 0.23,其功能是将i.uv.x 控制在0到1的范围,进而显示出来。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EShader \&Lpy\u002FImageRoll\& \n{\n\tProperties \n\t{\n\t\t_MainTex (\&Main Tex\&, 2D) = \&white\& {}\n\t\t\n
\t_Width (\&Width\&, float) = 0.5\n
\t_Distance (\&Distance\&, float) = 0\n\t}\n\t\n\tSubShader \n\t{\n\t\tTags {\&Queue\&=\&Transparent\& \&IgnoreProjector\&=\&True\& \&RenderType\&=\&Transparent\&}\n\t\t\n\t\tPass \n\t\t{\n\t\t\tTags { \&LightMode\&=\&ForwardBase\& }\n\t\t\tZTest off\n\t\t\tZWrite Off\n\t\t\tBlend SrcAlpha OneMinusSrcAlpha\n\t\t\t\n\t\t\tCGPROGRAM\n\t\t\t#pragma vertex vert
\n\t\t\t#pragma fragment frag\n\t\t\t#include \&UnityCG.cginc\&\n\t\t\n\t\t\tsampler2D _MainT\n\t\t\tfloat _W\n\t\t\tfloat _D\n\t\t\t
\n\t\t\tstruct a2v \n\t\t\t{
float4 vertex : POSITION; \n\t\t\t
float2 texcoord : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tstruct v2f \n\t\t\t{
float4 pos : SV_POSITION;\n\t\t\t
float2 uv : TEXCOORD0;\n\t\t\t};
\n\t\t\t\n\t\t\tv2f vert (a2v v) \n\t\t\t{
\n\t\t\t\tv2
\n\t\t\t\to.pos = mul(UNITY_MATRIX_MVP, v.vertex);
\n\t\t\t\t\n\t\t\t\to.uv.x = v.texcoord.x;\n\t\t\t\to.uv.y = v.texcoord.y;\n\t\t\t\\n\t\t\t}
\n\t\t\t\n\t\t\tfixed4 frag (v2f i) : SV_Target \n\t\t\t{\n\t\t\t\ti.uv.x = frac(i.uv.x*_Width + _Distance);\n\t\t\t\tfixed4 c = tex2D(_MainTex, i.uv);\n\t\t\t\\n\t\t\t}\n\t\t\tENDCG\n\t\t}
\n\t}\n\tFallBack \&Transparent\u002FVertexLit\&\n}\u003C\u002Fcode\u003E\u003Ch2\u003E2、使用材质\u003C\u002Fh2\u003E\u003Cp\u003E新建一个名为ImageRoll的材质,选择上述编写的shader。设置WIdth和Distance两个参数,如下图所示。只要调整Distance的值,即可看到滚动到不同位置的背景。\u003C\u002Fp\u003E\u003Cimg src=\&v2-c71acc8f4a0dec5f2dc222.png\& data-rawwidth=\&692\& data-rawheight=\&230\&\u003E\u003Cimg src=\&v2-6a2f2a9e218d2c1797beed.png\& data-rawwidth=\&379\& data-rawheight=\&243\&\u003E\u003Ch2\u003E3、代码中引用\u003C\u002Fh2\u003E\u003Cp\u003E可以在代码中逐步增加distance 的值,造成背景不断向前滚动的效果。也可以在shader中使用Time相关的方法,实现同样的功能。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eusing UnityE\nusing System.C\nusing UnityEngine.UI;\n\npublic class ImageRoll : MonoBehaviour {\n\n\tpublic I\n\tprivate M\n\\n\t\u002F\u002F Use this for initialization\n\tvoid Start () {\n\t\tmtl = image.\n\t}\n\t\n\t\u002F\u002F Update is called once per frame\n\tvoid Update () {\n\t\tdistance += 0.005f;\n\t\tmtl.SetFloat(\&_Distance\&, distance);\n\t}\n}\u003C\u002Fcode\u003E\u003Cp\u003E将刚创建的材质应用于图片上,即可看到效果。如下图所示。\u003C\u002Fp\u003E\u003Cimg src=\&v2-1cd0be1d8d2ba59e6b404c7b9ec1bba0.png\& data-rawwidth=\&693\& data-rawheight=\&295\&\u003E\u003Cbr\u003E\u003Cp\u003E最后依然到了广告时间:笔者出版的一本Unity3D实战类书籍《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。\u003C\u002Fp\u003E\u003Cimg src=\&v2-9e629e8fa3c93fe1f563be632d3a1ff2.png\& data-rawwidth=\&692\& data-rawheight=\&692\&\u003E&,&updated&:new Date(&T15:18:50.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:8,&likeCount&:25,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T23:18:50+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\\u002Fv2-d9e7d6d51fa27f75b0e48436dee51b06_r.png&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:8,&likesCount&:25},&&:{&title&:&作为程序员,如何规划成长路线&,&author&:&pyluo&,&content&:&\u003Cp\u003E前不久参与了游戏蛮牛的专家在线活动,几十位网友提出了他们的问题,其中有些问题较为普遍,于是决定把这些问题整理出来,加以完善,希望能够帮助到更多人。\u003C\u002Fp\u003E\u003Cp\u003E
原贴地址:\u003Ca href=\&http:\u002F\\u002Fthread--1.html\& class=\&\& data-editable=\&true\& data-title=\&【2.24 某知名游戏公司主程在线答疑 参与有机会得《Unity 3D网络游戏实战》】-论坛活动-【游戏蛮牛】-ar增强现实,虚拟现实,unity3d,unity3d教程下载首选u3d,unity3d官网\&\u003E【2.24 某知名游戏公司主程在线答疑 参与有机会得《Unity 3D网络游戏实战》】-论坛活动-【游戏蛮牛】-ar增强现实,虚拟现实,unity3d,unity3d教程下载首选u3d,unity3d官网\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E作为程序员,如何规划职业成长路线\u003C\u002Fh2\u003E\u003Cp\u003E 问:\u003C\u002Fp\u003E\u003Cp\u003E
接触游戏开发也有一年多,感觉这一年基本上没学到什么东西。在机构学习的时候就常常听老师们说,框架,面向对象等,可是到做项目的时候基本都没用到过,每次做项目就是实现功能。到最后代码就乱得一塌糊涂。因为没有一套明确的方向,也不知道该去学那些。\u003C\u002Fp\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E其中的一条可能的学习途径可以参考下面的三个阶段。\u003C\u002Fp\u003E\u003Cp\u003E第一年:能够做功能。\u003C\u002Fp\u003E\u003Cp\u003E尽量接触公司内比较完善的游戏项目,在参与编写逻辑的同时,不断完善自己的编码水平。同时了解该项目的设计思想、用到了哪些技术、用到了哪些第三方库,这些第三方库又是怎样使用的。\u003C\u002Fp\u003E\u003Cp\u003E第二年:用得起现有框架。\u003C\u002Fp\u003E\u003Cp\u003E经过第一年的学习,应该对游戏项目很熟悉了。此时要熟悉这一套框架的各部分功能,假如要让你做一款新的游戏,你能够在这套框架的基础上把游戏做出来。同时也多了解几套可用的框架,积累自己能够“撬得动”的代码资源。\u003C\u002Fp\u003E\u003Cp\u003E第三年:深入底层。\u003C\u002Fp\u003E\u003Cp\u003E深入底层了解这些源码,能够修改它们,或者能够重新写一个。同时再次学习计算机原理、图形学、数据库实现等课程,能够有不同的体会\u003C\u002Fp\u003E\u003Cp\u003E客户端、服务端具体的方向可能会有所不同,但大致的路径都是一样的。就是先能够使用、再能够使用某个现有资源重新搭建,再到深入底层。经过这3个阶段,相信便能够独当一面了。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E想做游戏服务端,应该学些什么 \u003C\u002Fh2\u003E\u003Cp\u003E年少不懂事
问:\u003C\u002Fp\u003E\u003Cp\u003E
如果走服务器需要会那些技能\u003C\u002Fp\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E其实看看各大公司招聘基本就能够知道所需的技能了,大概有如下几点\u003C\u002Fp\u003E\u003Cp\u003E1、熟悉掌握一门主要编程语言,如c++或java\u003C\u002Fp\u003E\u003Cp\u003E2、熟悉掌握某一门脚本语言,如lua或python\u003C\u002Fp\u003E\u003Cp\u003E3、了解网络编程知识,了解TCP\u002FUDP\u002FIP协议的使用和实现细节。了解并能够使用一些现成的网络库,如libevent\u003C\u002Fp\u003E\u003Cp\u003E4、熟悉linux操作系统,能够操作它,并知道它的实现原理\u003C\u002Fp\u003E\u003Cp\u003E5、熟悉数据库,及其实现原理\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E目前服务端相关的资料很少,推荐书籍的话首先当然是推荐我自己的书《Unity3D网络游戏实战》啦,里面第6和7章有实现一套简易的服务端程序,还有下面几本书对能够让读者对服务端有更好的了解\u003C\u002Fp\u003E\u003Cp\u003E《Linux多线程服务端编程 使用muduo C++网络库》
陈硕\u003C\u002Fp\u003E\u003Cp\u003E《Redis源码涉及与实现(第二版)》
黄健宏\u003C\u002Fp\u003E\u003Cp\u003E《TCP\u002FIP详解:卷一》
看前面几章就行\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003EC++是必学的吗?能不能只用c#\u003C\u002Fh2\u003E\u003Cp\u003E 问:\u003C\u002Fp\u003E\u003Cp\u003E做游戏开发,lua和c++是必学的吗,我只用c#,unity不可以吗\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E游戏开发分前端和后端,使用到不同的技术。如果是后端,c++和lua是其中一种方案,如果是unity前端,c#是必须的,lua也有可能涉及,c++几乎不会用到。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E然而一些面试中,会以c++水平去代表编程水平,一般c++水平高的话,c#、lua都不在话下。但无论如何,数据结构和算法肯定是要很扎实的。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cbr\u003E\u003Ch2\u003E大学生想学游戏开发,该怎样规划\u003C\u002Fh2\u003E\u003Cp\u003EFlyingFishF 问:\u003C\u002Fp\u003E\u003Cp\u003E
作为一名有部分java编程语言基础的大学生请问应当如何规划自己的unity3d学习路线\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E学习路线大概分为下面几个步骤\u003C\u002Fp\u003E\u003Cp\u003E1、先入门,能做小demo。这个阶段可以参考《Unity3D\\2D手机游戏开发》等书籍\u003C\u002Fp\u003E\u003Cp\u003E2、实战做一款稍有规模的游戏,这里就推荐我的《Unity3D网络游戏实战》,哈哈。\u003C\u002Fp\u003E\u003Cp\u003E3、解析别人的做法,在蛮牛上下载一些较大型的游戏源码,尝试改动他们、然后能够使用相应的框架去修改\u003C\u002Fp\u003E\u003Cp\u003E4、行为树、热更新、MVC等应用\u003C\u002Fp\u003E\u003Cp\u003E5、学习图形学和shader\u003C\u002Fp\u003E\u003Cp\u003E6、学习物理引擎\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003EAR\u002FVR的前景如何? \u003C\u002Fh2\u003E\u003Cp\u003EObarong问:\u003C\u002Fp\u003E\u003Cp\u003E《Oculus:VR杀手级应用应会是PC端大型社交网游》这个帖子说到,“杀手级应用自然而然地会成为杀手级应用,它们不是被设定是这样的。开发者从未想着开发杀手级应用,只想开发自己力所能及的最好的游戏,最终反应很好。”我认为在VR这个新领域,开发者应该着眼于自己最想玩的应用,不是为了跟房地产之类的热门行业沾上边,而强行转移关注点。老师,您认为呢?\u003C\u002Fp\u003E\u003Cbr\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E看看股票也许能说明ARVR的现状,目前vr概念股已经不火了,杀手级应用迟迟未出现。没人知道杀手级应用会是什么样的,但如果做出来的东西自己都觉得不好,估计别人也肯定觉得不好。所以,还是要做自己认为好玩的、或者能够给大家带来便利的应用\u003C\u002Fp\u003E\u003Cp\u003EVRAR前几年的概念炒得火爆,但一直没有核心产品出现,今年有所降温。个人觉得短期内比较难有“改变世界”那样的ARVR产品出现,未来几年,大概会作为游戏、教育、房产、游乐园等领域的一种新运用。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E客户端服务端运算的区别\u003C\u002Fh2\u003E\u003Cp\u003EChenyici 问:\u003C\u002Fp\u003E\u003Cp\u003E请问服务端寻路和客户端自动寻路区别在哪里\u003C\u002Fp\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E最主要的区别在于客户端寻路因为算法暴露在客户端,而客户端在用户手中,用户可以通过破解等手段修改程序,从而实现作弊。如果是服务端寻路就无法作弊了。因为每个客户端只处理自己的寻路,计算量小,而如果放到服务端,它需要计算所有角色的寻路,计算量就大很多,从技术实现上看,客户端寻路要比服务端寻路简单。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E战斗回放功能的实现方法\u003C\u002Fh2\u003E\u003Cp\u003EDonot001 问:\u003C\u002Fp\u003E\u003Cp\u003E我们游戏为3d回合制游戏,客户端用unity开发,目前要做战斗的回放,请问您有什么高效简单的办法么 \u003C\u002Fp\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E回放功能一般是记录状态,如果是服务端运算,只需要将服务端发送的协议全部记录下来,客户端一定可以根据这些协议恢复战斗现场的。也就是说,把战斗中的每一个状态或者状态切换都记录下来,后面客户端再恢复现场,即是按照时间轴记录玩家行为。\u003C\u002Fp\u003E\u003Ch2\u003E怎样实现多人pvp对战?\u003C\u002Fh2\u003E\u003Cp\u003ESislcb问:\u003C\u002Fp\u003E\u003Cp\u003E请问《Unity3D网络游戏实战》有涉及多人PVP战斗,客户端跟服务器同步的详细交互过程吗?如果有的话,对于多人及时PVP战斗,都是用帧同步吗?有比较成熟的开源实现吗?有一些坑的经验吗?比如,多少人一个房间,不会卡?或者同步的周期如何?当网络卡的时候,如何进行帧补偿?如果没有,考虑为了版本增加这方面的知识吗?\u003C\u002Fp\u003E\u003Cp\u003E答:\u003C\u002Fp\u003E\u003Cp\u003E书中会实现多人pvp的战斗,使用客户端计算服务端转发的方式,描述应该还算是非常详细的。同步方式使用客户端计算、服务端转发(以及一些简单的校验)、其他客户端使用预测算法计算轨迹的方式,例子中同步周期约0.2秒,6个人一个房间。可以很简单的修改为10+个人同房间,服务端应该也还行。\u003C\u002Fp\u003E\u003Cp\u003E网络卡会影响到自己的显示,但不会影响他人,帧同步会拖慢所有人的操作,并不适合实时性很高的游戏。\u003C\u002Fp\u003E&,&updated&:new Date(&T14:00:53.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:3,&likeCount&:69,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&no

我要回帖

更多关于 方向盘锁放在哪里好 的文章

 

随机推荐