关于unity创建tag(tag标签是什么意思)

浏览1137次
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/对C#还不是很熟,在学习的过程中了解到C#有种代理和事件的机制很方便,之前用cocos2d的时候也用过代理,虽然此代理非彼代理,但他们的作用有相似之处,都是触发者只是告知事件,并不调用相关类和方法实现,而是由代理方法实现,可能有些难懂,下面我用之前的demo中英雄施放技能这里做个试验。本篇涉及三个知识点,一个是最重要的代理,一个是NGUI的按钮,一个是施放技能特效。1、NGUI制作技能按钮关于NGUI我前面的文章说了好多,重复的部分我就不介绍了。我打算把技能按钮放在NGUI界面布局的右下角锚点处,先在右下角锚点处加个Panel,我起名SkillPanel,在该Panel下我建立一个按钮,选NGUI-&Open the Widget Wizard,Atlas选你将图片打包的那个文件,没有的话先建立一个,不知道怎么建的看前面的文章。Template里选Button,然后会出现Background,是按钮图片,从Atlas中选一个,我的设置如下:选择Add To后SkillPanel下新增一个按钮,我起名ZhongJiButton,布局如下:调整位置,我的按钮截屏如下(右下角那个):2、利用C#的代理和事件施放技能。要施放技能,肯定要点击按钮,NGUI为按钮提供了OnClick接口。我们先创建一个脚本,我命名CastSkill,然后拖到ZhongJiButton上,CastSkill里只要实现OnClick接口就好了。一般我们会怎么实现点击施放呢,最传统的做法就是获得英雄的对象,然后利用该对象向她传递施放技能的消息,比如代码如下:[csharp]&void&OnClick()&{&&&&&&GameObject&hero&=&GameObject.Find(&Blade_Girl_Prefab&);&&&&&&if&(hero)&&&&&&&&&&hero.SendMessage(&CastSkill&,&1);&&}&&像上面我通知hero的CastSkill接口,传递的参数1是SkillID,很简单,这就实现了,但你有没有想过,这又陷入了脚本之间耦合性的增加,看过我前面这篇文章应该明白脚本之间大量的互相访问不是件好事。那我们就看看代理能不能帮我们解决办法。想了解什么是代理和怎么用代理的可以看这篇文章,写的挺全的:,重点看里面的一和三,我就偷个懒,只说明我这里是怎么实现的。现在我用代理重新改一下上面的脚本:[csharp]&public&class&CastSkill&:&MonoBehaviour&{&&&&&&public&delegate&void&EventHandler(int&nSkillId);&&&&&&public&event&EventHandler&OnCastS&&&&&&&&&&&&void&OnClick()&{&&&&&&&&&&if&(OnCastSkill&!=&null)&&&&&&&&&&&&&&OnCastSkill(1);&&&&&&}&&}&&可以看出上面先创建了一个代理EventHandler,传递一个参数nSkillId,然后创建该代理的一个事件OnCaskSkill,在OnClick里触发这个事件OnCastSkill(1),“1”是代理传递的那个参数nSkillId。你会发现这里根本没有Hero的事,那Hero怎么接收消息呢。下面我们看看Hero相关的代码。[csharp]&CastSkill&onCastSkill&=&skillBtn.GetComponent&CastSkill&();&&onCastSkill.OnCastSkill&+=&CastS&&[csharp]&void&CastSkill(int&nSkillId)&&{&&&&&&if&(curState&==&en_state.en_state_idel&||&curState&==&en_state.en_state_attack)&{&&&&&&&&&&if&(nSkillId&==&1)&{&&&&&&&&&&&&&&lastState&=&curS&&&&&&&&&&&&&&curState&=&en_state.en_state_&&&&&&&&&&}&&&&&&}&&}&&在Hero里,我先是获得按钮的那个脚本CaskSkill对象,重点是下面那句,我为OnCastSkill这个事件挂接了实现的方法CastSkill,然后你就可以在CastSkill里实现释放技能了。所谓代理,就是触发者根本就不用关心到底是谁接收了,而接受者只要调用该代理事件,并挂接个实现方法就OK了,这样只要触发者一触发,凡是挂接过该事件的方法都会响应,这就是代理。不知道您明白了吗。3、施放技能如上面代码,我给英雄置了当前状态为技能状态,她会在Update的状态机里处理,调用playEffect函数。[csharp]&void&playEffect()&&{&&&&&&if&(!animation.IsPlaying(&ComboAttack&))&&&&&&&&&&gameObject.animation.CrossFade(&ComboAttack&);&&&&&&&&AnimationState&state&=&animation[&ComboAttack&];&&&&&&if&(state.time&&=&state.length-0.5f&&&&!isCastSkill)&{&&&&&&&&&&&&&&&&&&&&&Vector3&pos&=&transform.position&+&transform.TransformDirection(Vector3.forward*3);&&&&&&&&&&Instantiate(effect,&pos,&transform.rotation);&&&&&&&&&&isCastSkill&=&true;&&&&&&}&&&&&&&&&&&&&&&&if&(isCastSkill&&&&state.time&&=&state.length-0.1f)&{&&&&&&&&&&isCastSkill&=&false;&&&&&&&&&&curState&=&lastS&&&&&&}&&}&&上面代码大致意思是先播放动作,当动作播放到一半的时候播放特效,我这里特效是个prefab,特效只播放一次,所以isCastSkill是用来控制播放次数的,因为特效要播放在角色身前,所以上面获取pos那句代码是根据角色的位置加上其朝向前方3个单位的距离获得的。最后上个图吧。
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/在mac os下写过手游程序的应该都清楚csv文件,它类似于excel表格文件,但csv文件里的数据是以逗号分隔的。unity3d并未提供直接读取csv文件的方法,那怎么办呢,没关系,很简单,下面看我是怎么做到的。先声明,我的操作系统是mac os。1、创建csv文件既然做实验嘛,没有资源怎么行,自己徒手写个csv文件吧,打开Numbers工具,新建一个表格文件,我的文件编辑截图如下:创建完成后,导出成csv格式,我这里文件名为test.csv;2、改成txt格式既然unity3d不认识csv,那么我们人工处理一下,很简单,像上一篇文章一样,简单粗暴的把csv的扩展名改成txt吧,谁让unity3d比较亲txt呢,更改后是test.txt,打开后是这个样子的:3、读取txt文件上代码:[csharp]&private&string&[][]A&&void&Start&()&&{&&&&&&//读取csv二进制文件&&&&&&TextAsset&binAsset&=&Resources.Load&(&test&,&typeof(TextAsset))&as&TextA&&&&&&&&&&&&&&&&&&&&&&&//读取每一行的内容&&&&&&string&[]&lineArray&=&binAsset.text.Split&(&\r&[0]);&&&&&&&&&&&&&&&&//创建二维数组&&&&&&Array&=&new&string&[lineArray.Length][];&&&&&&&&&&&&&&&&//把csv中的数据储存在二位数组中&&&&&&for(int&i&=0;i&&&lineArray.L&i++)&&&&&&{&&&&&&&&&&Array[i]&=&lineArray[i].Split&(',');&&&&&&}&&}&&先读取test文件,这里要注意,你要把test文件放到Asset的Resources目录下,没有这个目录就自己创建一个。因为每一行结尾都会有个“\r”,所以用这个可以分隔出每行数据,然后我们创建一个二维数组Array,行数用上一句求出的行数据,列数待定。因为每一行的数据都是用“,”分隔符分开的,所以我们用逗号分隔出每列数据,再以行为单位保存在Array中。现在你打印Array.Length应该是5,表示行数,Array[0].Length应该是3,表示列数。4、以行列数读取数据[csharp]&string&GetDataByRowAndCol(int&nRow,&int&nCol)&&{&&&&&&if&(Array.Length&&=&0&||&nRow&&=&Array.Length)&&&&&&&&&&return&&&;&&&&&&if&(nCol&&=&Array[0].Length)&&&&&&&&&&return&&&;&&&&&&&&&&&&&&&&return&Array[nRow][nCol];&&}&&这个代码很简单,前面都是行列越界判断,没啥好说的,这时打印GetDataByRowAndCol(1,2)应该是“这是苹果”。5、以Id和名称读取数据这应该是项目最常用到的,以行列的名称定位数据。[csharp]&string&GetDataByIdAndName(int&nId,&string&strName)&&{&&&&&&if&(Array.Length&&=&0)&&&&&&&&&&return&&&;&&&&&&&&&&&&&&&&int&nRow&=&Array.L&&&&&&int&nCol&=&Array[0].L&&&&&&&&&&&for&(int&i&=&1;&i&&&nR&++i)&{&&&&&&&&&&string&strId&=&string.Format(&\n{0}&,&nId);&&&&&&&&&&if&(Array[i][0]&==&strId)&{&&&&&&&&&&&&&&for&(int&j&=&0;&j&&&nC&++j)&{&&&&&&&&&&&&&&&&&&if&(Array[0][j]&==&strName)&{&&&&&&&&&&&&&&&&&&&&&&return&Array[i][j];&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&}&&&&&&&&&&&&&&&&return&&&;&&}&&上面代码也很简单,先循环行数据,判断ID是否相同,找到ID后再循环列数据,看名称是否相同,然后返回行列对应的数据。这里有一点值得注意,就是判断Id的时候,id前面会有个“\n”,这是换行标识符,你可以用Debug的方式看一下Array,除了第一行,每一行的Id都会有个“\n”的前缀,所以必须这么判断。这时打印GetDataByIdAndName(4,&name&)应该是“梨”。就这些,有问题欢迎提问,哈哈。
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/前一篇文章介绍了协同异步加载游戏场景,但前台没干活,浪费了。有的游戏在loading的时候会把一些游戏的Tips(小提示)介绍给玩家,我这里说一下是怎么实现的。说到小提示,其实是后台有个配置文件,里面有很多提示语句,程序以一定的时间间隔随机读取一条,显示在界面上,很简单。这里说到配置文件,花样就多了,以前做cocos2d-x游戏时,用的最多的是plist文件和csv文件,unity用的都是xml和exel文件,那我能用unity也读取plist文件和csv文件文件吗,当然可以,本篇我先说读取plist文件。其实读取plist文件很简单,因为plist文件就是xml文件,所以只要把文件扩展名改一下就可以了,把.plist改成.xml,然后按读取xml的方式读它就行了,哈哈,不要喷我。所以我重点介绍一下读取xml的方法。先看一下我的plist文件内容:[html]&&?xml&version=&1.0&&encoding=&UTF-8&?&&&&!DOCTYPE&plist&PUBLIC&&-//Apple//DTD&PLIST&1.0//EN&&&/DTDs/PropertyList-1.0.dtd&&&&&plist&version=&1.0&&&&&dict&&&&&&&&key&tips&/key&&&&&&&&array&&&&&&&&&&&&string&重新挑战已经通过的关卡,也可以获得金币&/string&&&&&&&&&&&&string&某一关卡无法通过?您缺少的仅仅是英雄和士兵的等级罢了!&/string&&&&&&&&&&&&string&优先升级城堡等级、兵力恢复速度,会带来意想不到效果&/string&&&&&&&&&&&&string&选择合适的时机使用英雄技能或城防道具,可以提高获胜几率&/string&&&&&&&&&&&&string&尽可能一次性出多个士兵,能够最大化提高您的军团战斗力&/string&&&&&&&&&&&&string&合适的时机使用英雄技能或城防道具,可以提高您的获胜几率&/string&&&&&&&&&&&&string&获得成就可以获得大量钻石,当您缺少钻石时不妨看看成就列表&/string&&&&&&&&&&&&string&每个兵种的特殊各不相同,不仅仅是高级兵种才有展示的空间&/string&&&&&&&&&&&&string&当连续闯关失败时,换一个战术或出兵的方式就能有所转机&/string&&&&&&&&&&&&string&让士兵与敌人的等级保持一致是一个不错的主意&/string&&&&&&&&&&&&string&获得成就可以获得大量钻石,缺少钻石的时候不妨看看成就列表&/string&&&&&&&&&&&&string&战斗中想查看地图上的状态,只需要左右滑动屏幕就行啦&/string&&&&&&&&/array&&&&/dict&&&&/plist&&&这是“时空领地”的Loading界面用到的tips,当时用plist做的,用xml文件打开,其实就是个xml,这就好办了,unity已经有成型的读取xml的接口了。先上一下代码吧。[csharp]&void&loadXml()&&&&&&{&&&&&&&&&&string&filePath&=&Application.dataPath&+&@&/Resource/settings/tips.xml&;&&&&&&&&&&if&(File.Exists&(filePath))&{&&&&&&&&&&&&&&XmlDocument&xmlDoc&=&new&XmlDocument();&&&&&&&&&&&&&&xmlDoc.Load(filePath);&&&&&&&&&&&&&&XmlNodeList&node&=&xmlDoc.SelectSingleNode(&plist&).ChildN&&&&&&&&&&&&&&foreach(XmlElement&nodeList&in&node)&{&&&&&&&&&&&&&&&&&&foreach(XmlElement&xe&in&nodeList)&{&&&&&&&&&&&&&&&&&&&&&&if&(xe.Name&==&&array&)&{&&&&&&&&&&&&&&&&&&&&&&&&&&int&i&=&0;&&&&&&&&&&&&&&&&&&&&&&&&&&_tips&=&new&string[xe.ChildNodes.Count];&&&&&&&&&&&&&&&&&&&&&&&&&&foreach(XmlElement&xe1&in&xe.ChildNodes)&{&&//&&&&&&&&&&&&&&&&&&&&&&&&&&Debug.Log(xe1.InnerText);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&_tips[i]&=&xe1.InnerT&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&i++;&&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&}&&结合上面的配置文件,你会很容易明白怎么用的。Application.dataPath是Assets文件夹目录,先判断文件是否存在,如果存在,创建一个XmlDocument,load加载文件,根节点是&plist&,先循环遍历&plist&的子节点们,这里只有&dict&,然后再循环遍历&dict&的子节点们,这里有两个,一个是&key&,一个是&array&,所以判断Name是否array,是的话创建一个字符串数组,拥有array这么多元素的数组,然后从array里逐一读取出来存到数组里。以后我们就可以在该数组里随机查找了。这段代码在start里调用,只读取一次。记住,不要试图总是查询外部文件,那样效率会很低下的,提前cache起来是不错的选择。读取完了,我要在loading节目加载了,我用的依然是NGUI,在bottom那里布局个label,不清楚怎么布局的看我这篇文章:,这里截图如下:其中Label要用中文字体,不清楚怎么创建中文字体的看我这篇文章:。最后程序控制一定间隔读取上面cache的字符串。[csharp]&private&string&[]&_&&public&UILabel&_tipsL&&private&float&_lastTime&=&0.0f;&&private&const&int&WAIT_TIME&=&2;&&&&&&&&&&&&void&Start&()&{&&&&&&loadXml();&&&&&&if&(_tips&!=&null&&&&_tips.Length&&&0)&{&&&&&&&&&&int&idx&=&Random.Range(0,&_tips.Length-1);&&&&&&&&&&_tipsLabel.text&=&_tips[idx];&&&&&&}&&&&&&StartCoroutine(loadScene());&&}&&&&&&&&//&Update&is&called&once&per&frame&&void&Update&()&{&&&&&&if&(_tips&==&null&||&_tips.Length&&=&0)&&&&&&&&&&return;&&&&&&&&&&&&&&&&if&(Time.time&-&_lastTime&&=&WAIT_TIME)&{&&&&&&&&&&_lastTime&=&Time.&&&&&&&&&&int&idx&=&Random.Range(0,&_tips.Length-1);&&&&&&&&&&_tipsLabel.text&=&_tips[idx];&&&&&&}&&}&&这样2秒钟界面显示一下tips,我们的loading界面的协同异步终于有意义了。最后上个图吧。
浏览1256次
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/前一篇文章介绍了游戏开始场景的制作,可还没有任何交互,按理说,我的设计是点击界面然后直接到游戏场景,但看到了雨松的这篇文章--后,决定尝试下用Loading界面异步加载游戏。先看一下我的Loading界面:像我这种小demo,从开始场景到游戏场景其实用Application.LoadLevel就可以了,但对于一个正常的游戏,新场景的资源加载往往会使你的游戏卡上一段时间,用户体验上就跟游戏死了一样,这时候Loading界面加上后台异步加载场景是你不错的解决方案。下面看看我是怎么做的。1、首先是Loading场景的制作这个相对简单,如上图,就是用了NGUI的Texture而已,需要注意的就是给它加了个Stretch,使屏幕分辨率自适应,不知道怎么弄的看我前一篇文章,这里不介绍NGUI怎么做的了。2、异步加载游戏场景最简单的Application.LoadLevel(&SceneName&)这种方式加载场景是同步的,如果用这种方法,新场景资源小还好,如果资源量多,那在加载资源时让你的游戏卡死,直到新场景的资源全部加载完成,这种方式对用户体验显然是不好的,最传统的做法都是开个线程,一个线程用来处理数据加载,一个线程用进度条或是动画的方式提示玩家游戏并未卡死,而是等待。但是Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。StartCoroutine为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。而LoadLevelAsync则允许你在后台加载新资源和场景,所以再利用协同,你就可以前台用loading条或动画提示玩家游戏未卡死,同时后台协同处理加载的事宜。想具体了解StartCoroutine可以看这里:,想具体了解LoadLevelAsync可以看这里:。3、利用单例类记录场景名称有关单例脚本和单例类我前面的文章介绍过,这里就不说了,为什么要用到单例类呢,因为Loading脚本要知道下一个加载的是什么场景,而这是上一个场景告诉它的,场景切换数据就丢了,而单例类的数据会一直保留,所以要用单例类。看一下代码:[csharp]&public&class&Global&{&&&&&&public&string&loadN&&&&&&&&&&&&private&static&Global&&&&&&&public&static&Global&GetInstance()&&&&&&{&&&&&&&&&&if&(instance&==&null)&&&&&&&&&&&&&&instance&=&new&Global();&&&&&&&&&&&&&&&&&&&&return&&&&&&&}&&}&&这里就一个变量loadName,负责记录场景名称。4、A场景过渡到Loading场景我前一篇文章讲了开始游戏场景的制作,但缺切换场景的过渡,假如它是A场景,我用触摸屏幕的方法让它过渡到Loading场景,然后再通过协同程序等待加载资源完成后自动过渡到B场景。先看A场景的代码:[csharp]&public&class&OnPress&:&MonoBehaviour&{&&&&&&&&//&Use&this&for&initialization&&&&&&public&UICamera&nguiC&&&&&&void&Start&()&{&&&&&&&&&&&&}&&&&&&&&&&&&//&Update&is&called&once&per&frame&&&&&&void&Update&()&{&&&&&&&&&&if&(Input.GetMouseButton(0))&{&&&&&&&&&&&&&&Ray&ray&=&nguiCamera.camera.ScreenPointToRay(Input.mousePosition);&&&&&&&&&&&&&&RaycastHit&&&&&&&&&&&&&&&if&(Physics.Raycast(ray,&out&hit))&{&&&&&&&&&&&&&&&&&&Global.GetInstance().loadName&=&&GameScene&;&&&&&&&&&&&&&&&&&&Application.LoadLevel(&LoadingScene&);&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&}&&}&&有关触摸的代码我不解释了,重要的就两句,一句“Global.GetInstance().loadName = &GameScene&;”,这是告诉Loading场景下一个要加载的场景是GameScene,然后“Application.LoadLevel(&LoadingScene&);”开始切换场景到LoadingScene。5、LoadingScene协同程序,后台加载新场景。先看代码:[csharp]&public&class&Loading&:&MonoBehaviour&{&&&&&&void&Start&()&{&&&&&&&&&&StartCoroutine(loadScene());&&&&&&}&&&&&&&&&&&&IEnumerator&loadScene()&&&&&&{&&&&&&&&&&AsyncOperation&async&=&Application.LoadLevelAsync(Global.GetInstance().loadName);&&&&&&&&&&yield&return&&&&&&&}&&}&&这是最简单的协同程序了,只是后台处理加载新场景而已,没有前台的事,这一篇先不讲前台的工作。代码很简单,不解释。因为Global.GetInstance().loadName = &GameScene&,所以加载完就自动跳到GameScene这个场景了。6、要切换场景,有个必要的工作没做。那就是在Unity里注册场景。选File-&Build Settings...,上面有个Scenes In Build,你要把你用到的场景都加到这个框里,程序才能正常调用,否则程序写了也无效。右下角有个Add Current的按钮,这是加载当前场景用的,你也可以直接把所有场景拖到这里。后面的数字是关卡ID,0是第一个要加载的场景。我的截图如下:这样就可以实现最简单的Loading场景协同异步加载场景了,但本篇的协同异步其实没意义,因为我们的前台没做任何事情,下一篇让我们的前台别闲着,顺便看一下U3D如何利用XML加载游戏Tips。
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/通过前面十三篇文章的介绍,我的游戏场景基本搭建完成了,我们在玩任何一款手游产品时,都是先上来个logo界面,游戏欢迎界面等,这就意味着我们要做一款游戏需要多个场景,场景之间来回切换实现游戏逻辑,unity也不例外,所以从本篇开始将会介绍如何搭建多个场景,本篇先看游戏logo场景的制作,先上图:再次用时空领地的logo了,这里的时空领地的logo是个动画,全部是用NGUI实现的,重点是做这个界面没用一句代码,除了点击切换场景。之前那个游戏场景,我起名GameScene,然后我新建一个场景File-&New Scene,起名BeginMenuScene。下面开始制作。1、先用NGUI新建一个Atlas,把场景中用到的图片都加到Atlas里,不知道怎么建Atlas的看前面的文章。2、用NGUI建一个UI Root,然后在Panel下新建一个Panel和Texture,因为上图右上角的游戏logo是由背板,文字动画,箭头动画三个部分组成的,所以我把它们统一放到这个Panel里,背景图片放到Texture里,其实背景图片也可以是个Sprite,由你喜好。结构图如下:3、把背景图片拖到Texture的UITexture里,这样背景就有了,别忘了把尺寸改成图片大小。4、把logo的Panel移到合适位置,里面的那个背板不说了,就是个sprite,放到合适位置就可以了,重点说Sprite动画,先看一下atlas图片:上图中包含一个logo背板,11帧文字动画,14帧箭头动画,以文字动画为例,NGUI怎么不用一句代码加载动画呢,我们要用到Sprite Animation,选Component-&NGUI-&UI-&Sprite Animation,这样我的Sprite(LogoText)就增加了UISprite Animation组件,是个脚本,你可以看看里面的代码。我的设置如下:Framerate是帧速率,最大60,越大播放越快,Name Prefix是图片名的前缀,这个很有用,比如我的atlas里有很多不同种类的图片,像上面,有背板图片,文字图片,箭头图片,那我这里的动画只想用文字的11张图片,怎么办呢,Name Prefix起作用了,它能够对文件名过滤,比如我的文字的图片都是以menu_title_为前缀的,menu_title_01,menu_title_02,...,menu_title_11,这样它就只会用到这11张图片了,并且会根据后面的数字排序播放,有一点要注意,后面的数字如果像01,02等有0在前面,那超过9的话要把0取消,直接10,11等,而不能010,011,这样顺序会错。Loop表示是否循环播放。就这么简单就能实现简单的动画循环播放了,如果想程序控制,那就看源代码怎么用吧。箭头动画也是如此,我就不介绍了。5、NGUI屏幕自适应不要以为这样就完了,如果你的图片是960*640的,或是其他的分辨率,而你的屏幕不是,在手机各种屏幕尺寸的今天,做不同分辨率的图片是多么痛苦的一件事,就像上面,运行一下程序,总是有难看的黑边,除非我把屏幕也拉成960*640的,不要怕,NGUI早就为我们处理了,只要加个Stretch就好了,点选Anchor下的那个Panel,我要把Stretch给它,因为它是整个界面的Panel,选择Component-&NGUI-&UI-&Stretch,你就会发现你的Inspector面板上新增了一个UIStretch脚本,下面就是调参数,网上搜的NGUI屏幕自适应教程大部分都是转的,调一堆参数,也有人经过试验发现了个捷径,我这里只说捷径,Style那里选Both,表示长宽都拉伸,Relative Size那里X=1/960,Y=1/640,这样就可以了,960和640是图片的宽高,根据你的尺寸可改,截个图:本篇重点介绍了NGUI的Animation Sprite制作sprite动画和如何屏幕自适应,但这里并没有交互,下一篇文章介绍如何异步加载游戏场景。
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/这一篇是纯技术讨论,看过我前面文章的童鞋应该清楚,我的奔跑是靠鼠标响应的,鼠标点到哪就跑到哪,后来又有了界面,麻烦就来了,我的界面竟然能点穿,我不希望点界面的时候还能点到界面后面的地面上,角色傻不拉几的往那跑,那肿么办呢,总不能每次点击都要判断点击的物体名称吧,那界面多了会累死的,好在我们有标签和层这两个神器,所以我这里介绍两种方案,都能达到效果。方案一:利用标签(Tag)拦截射线消息。每个GameObject的Inspector面板最上方都有个Tag选项,也就是说我们可以给所有的物体加个标签,Unity3d已经默认有了几个标签,这里我们可以自定义个UI的标签,然后把所有UI都用这个标签。点击Tag右方的选项,选Add Tag,Size调成2,Element 0的值改成UI。然后你所有不想被点穿的UI都选成这个标签。除了设置标签,还有一点很重要,就是界面要加碰撞器Collider,有的UI默认就有碰撞器,比如NGUI的按钮,你可以看到有一个Box Collider,如果没有的你就要自己加了,Size那里要调成这个UI的尺寸:这样UI才能接收射线碰撞,然后代码可以这样写:[csharp]&Ray&ray1&=&nguiCamera.camera.ScreenPointToRay(Input.mousePosition);&&RaycastHit&hit1;&&if&(Physics.Raycast(ray1,&out&hit1))&{&&&&&&if&(hit1.collider.gameObject.tag&==&&UI&)&&&&&&&&&&return;&&}&&上面的nguiCamera是获取的NGUI下的Camera,因为是这个Camera发出的射线与UI的碰撞,而不是Main Camera,这点要注意。其他的不解释了,很简单,自己看。方案二、利用层(Layer)拦截射线消息每个GameObject的Inspector面板最上方都也有个Layer选项,就在Tag旁边,unity3d已经有了几个层,我们新建个层,也叫UI,点击Add Layer,可以看到从Layer0到Layer7都灰掉了,那是不能用的,从第八个起可以用,所以在第八个建个UI的层。然后我们看一下Physics的Raycast的参数:static function&Raycast&(ray&:&,&out hitInfo&:&,&distance&: float =&,&layerMask&: int = kDefaultRaycastLayers) : bool一般情况下我们只用前两个参数,distance表示射线距离,默认是无限远,重点是最后一个参数layerMask,专门处理layer过滤的,是个整型,怎么用呢,是靠layer的二进制位来操作的,看下面代码就清楚了:[csharp]&Ray&ray1&=&nguiCamera.camera.ScreenPointToRay(Input.mousePosition);&&RaycastHit&hit1;&&LayerMask&mask&=&1&&&&LayerMask.NameToLayer(&UI&);&&if&(Physics.Raycast(ray1,&out&hit1,&600,&mask.value))&{&&&&&&return;&&}&&LayerMask的NameToLayer是通过层的名称返回该层的索引,这里是8,然后1&&8换算成LayerMask值,再用LayerMask的value就可以了。注意也必须设置collider才能接收碰撞,这里才能判断到。我这里提供了两个方案,如果有其他方案的欢迎留言,谢谢。
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/看网上有很多关于刀光的文章,然后也就学着给我们的侠女配上了,这下挥刀更带感了,先上张效果图吧:截图看着有瑕疵,不过动起来效果还不错。乍一看有点复杂,其实刀光的算法已经有人做好了,我们只需要站在巨人的肩膀上就可以了。1、在Asset Store里搜索MeleeWeaponTrail,第一个就是,不知道怎么进Asset Store的我告诉你,Window-&Asset Store,进去后需要注册个账号,里面有很多资源,包括模型,动画和脚本,MeleeWeaponTrail是免费的,放心下吧。2、它里面有个demo,可以看一下怎么用,脚本其实就两个,不过如果能看懂脚本的话,那我要膜拜一下。反正我是直接用了。我只用了MeleeWeaponTrail.cs这个脚本,然后把它的Example里的Textures里的Swoosh01.mat和Swoosh01.png拷到你的工程下。3、刀光嘛,我们要先找到刀,我这个模型的刀在这里:就是上面的headusOBJexprot009,然后我在其下加了两个空对象(GameObject-&Create Empty),分别标记两个点,Base是刀光的起始点,Tip是刀光的结束点。在Scene视图中调好位置,如下图:& &4、把Melee Weapon Trail 脚本拖到刀(headusOBJexprot009)上,然后把Swoosh01的材质拖到Material上,把上面的Base和Tip分别拖到Base和Tip上,设置如下图:5、好了,运行游戏应该能看到效果了,不过你会发现,不论奔跑还是休息,刀光无处不在,这也太假了,挥刀的时候才应该出现嘛。好在MeleeWeaponTrail里有个bool变量是Emit,当你置成false时就没刀光了,置成true就有刀光了,所以你可以在挥刀的时候置成true,其他时候是false,再精致点你就要用动画的时间控制刀光的显示和隐藏,比如只有当刀砍下时有刀光,抬起时没有刀光。
浏览1861次
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/在做这个demo的过程中,制作小地图着实刁难了我一把,百度了很多文章,花了好长的时间,需要的知识点实在太多了,尤其是shader语言,好在最后成功把它啃下来了,先声明一下,本篇文章将会是这个系列中最难的,不过如果成功做出来成就感也是大大的,其实按照我的步骤一步一步来也没那么复杂啦,接下来我把这个过程分享给大家,下面上一张截图:看右上角,那个就是小地图,也许有点不太好看,没办法,谁让我不是美术啊,需要的图素都是自己用Photoshop做的,记得有人说过,不会美术的程序不是好策划,所以自己来吧。先说一下原理,我们做小地图用的技术就是遮罩,玩过flash的对这个词应该不新鲜,何为遮罩呢,通俗一下讲,你有两张纸,一张纸上画了漂亮的山川河流,另一张纸就是张白纸,中间挖个圆洞,然后你把白纸盖在那张画满山川河流的纸上,只有中间圆洞的地方你能看到,其他地方都被盖住了,保持白纸不动,移动后面的画纸,你会看到连续的不同的地貌,我们的小地图原理就是这样的,时刻保持角色在中间,动的只是后面的背景罢了。只是这里需要多处理一下,就是除了那个圆洞,其他的部分我们要透明掉。相信这么解释应该没有不懂的了吧。如果还不清楚那要么是我的表达能力有问题,要么你的智商。。。咳咳,言归正传,按照上面的理论,我们都该准备些什么呢。最起码的得有一张完整的地图吧,得有个圆形的遮罩吧,再漂亮点圆形遮罩需要个圆框,地图上得有个小标志显示玩家的位置和方向。地图好搞,在unity中调到场景的顶视图,然后截个图就好了,缩放一下比例,我的地图是512*512的,取名map,如下图:丑是丑了点,谁让我的地形简单呢,再一次声明,我是个程序。遮罩就需要你自己画个了,要保证它和背景图一样大,我这里也是512*512的,中间是个白色的圆,其他地方alpha是透明的。如图:我这里并没有截全部512的图,不要以为只这么小啊,除了白圆,其他地方都是透明的,其实什么颜色的无所谓,只要是圆的就行。然后做个圆框,也是512的,圆框和白圆大小一致,中间和其他地方都是透明的。最后是角色的那个小标记资源就这些,准备好就可以开干了。关于NGUI我这里就不解释了,看过我前面文章的童鞋应该知道怎么用了,我们要把小地图放在右上角的锚点上,不是简单的放几个sprite的事情,那我们需要做些什么呢。来,我们倒着说,先看结果,我希望我的右上角先有个Panel,然后Panel下有个Textrue,而我的小地图就是绘制在这个Texture上;那小地图是以什么方式绘制到这个Texture上的呢,当然是靠material(材质),而且是一个能实现遮罩,透明,动态渲染的材质。那只能靠材质中的shader了;先不说遮罩和透明,先说动态渲染,我们以前用到的材质都是静态的,而现在我们要用到的是地图可以移动的动态材质,所以要用动态渲染,要说动态渲染,那最先想到的就是Render Texture,要说什么是Render Texture,看这里好了:。由果推因,我们就知道都该做些什么了,一个一个攻克吧,现在我们由因及果。1、绘制渲染纹理(Render Texture)a、先在MainCamera下创建一个UI,选择NGUI-&Open the UI Wizard,保持设置,点Create Your UI,然后Anchor下的Panel改名为MiniMapRenderPanel。b、创建一个Atlas,不知道什么是Atlas的可以看我前面的文章,选NGUI-&Open the Atlas Maker,新弹出的界面上修改你的atlas名称,点击那个完整的地图,就是我上面的那个map,然后点Create。c、在MiniMapRenderPanel下建一个sprite,NGUI-&Open the Widget Wizard,Atlas选刚建的那个Atlas,Template选sprite,Sprite选map,其他默认。最后建完了是这个结构。d、在资源里新建个Render Texture,Assets-&Create-&Render Texture,起名MiniMapRenderTexture。e、设置camera,那个NGUI的camera设置如下:Clear Flags设为Solid Color,Background设为黑色,这样当你走到地图的边缘时,没有地图的地方会绘制成黑色。Projection设为平行投影Orthographic,不了解平行投影和透视投影的话需要补一下3d基础了。Target Texture那里把之前做的那个Render Texture拖上去。这样摄像机投影的地方就会绘制在这个Render Texture上,也就是地图会绘制在它上面了,然后我们就可以做material了。f、有一点差点忘了,Anchor那里的Side一定要选择TopLeft,地图的原点是从左下角开始的。2、制作材质(material)这是本篇的难点,要用到shader语言,如果没有3d基础的可能理解起来有点费劲,不懂也没关系,按着操作也能做出来。a、创建一个Shader,Assets-&Create-&Shader。b、打开shader,把下面的代码覆盖过去。[csharp]&Shader&&Transparent/Mask&&&{&&&&&Properties&&&&&{&&&&&&&&_MainTex&(&Base&(RGB)&,&2D)&=&&white&&{}&&&&&&&&_Mask&(&Culling&Mask&,&2D)&=&&white&&{}&&&&&&&&_Cutoff&(&Alpha&cutoff&,&Range&(0,1))&=&0.1&&&&&}&&&&&SubShader&&&&&{&&&&&&&&Tags&{&Queue&=&Transparent&}&&&&&&&&Lighting&Off&&&&&&&&ZWrite&Off&&&&&&&&Blend&Off&&&&&&&&AlphaTest&GEqual&[_Cutoff]&&&&&&&&Pass&&&&&&&&{&&&&&&&&&&&&&&&&&&&&SetTexture&[_Mask]&{combine&texture}&&&&&&&&&&&SetTexture&[_MainTex]&{combine&texture,&previous}&&&&&&&&}&&&&&}&&}&&你百度遮罩shader的话会有很多,但基本没有解释的,我这里解释一下,第一行是设置你的shader的路径和名称,比如上面的shader就建在了Transparent下,起名Mask,大括号里面有两个部分,第一个部分是属性Properties,第二部分是SubShader。Properties里是设置渲染的属性,比如这里设置了两张图片和一个滑动条,第一张图片用来加载背景图片,就是我们之前做的那个Render Texture,用来显示地图,第二张图片就是遮罩图片,用我们之前的那个白圆,滑动条范围从0到1,初始值0.1,再看一下语法。[csharp]&_MainTex&(&Base&(RGB)&,&2D)&=&&white&&{}&&_MainTex是属性名,会在SubShader中用到,Base (RGB)是在界面上显示的名称,你会在属性面板上看到,2D是图片维数,white是默认值。SubShader是处理渲染的主体,Tags是标签,Queue标签决定被渲染的次序,而Transparent是四个预定义的渲染队列之一,任何有关alpha混合的对象都应该在这里处理,看这里可以了解的更多:,关闭光照,关闭z缓冲器的写操作,关闭混合,Alpha测试是当Alpha大于等于你之前设定的_Cutoff时有效,也就是说这里当alpha大于等于0.1的图素会被渲染出来,其他的就透明了。Pass通道里处理混合,先设置第一张图片:SetTexture [_Mask] {combine texture},这里是遮罩图片,然后第二张图片和前一张混合:SetTexture [_MainTex] {combine texture, previous},而我们之前关闭了混合,所以第二张图片只是纯粹的显示,但大括号里第二个参数表示alpha,这里previous表示我们用之前那张图片的alpha,而第一张图片的alpha除了白圆部分,其余部分都是0,所以这张图片除了与白圆 重合的地方,其他地方alpha也是0,这样就透明了,只剩下了圆的地方,想了解Pass看这里:,想了解SetTexture看这里:。写了这么多,纯粹手打,觉得好的麻烦支持一下,哈哈。Shader有一定难度,但据说会shader的程序员薪水都在2万以上啊,望眼欲穿啊,所以大家努力学吧。如果上面的没看懂,你又不想学,那就跳过吧,反正把代码粘过去就可以了。c、创建一个material,Assets-&Create-&Material,然后Shader那里找到你建的那个shader,在Transparent下面。d、这时界面上会出现Base(RGB)和Culling Mask,Base(RGB)里把你之前那个Render Texture拖上去,Culling Mask把那个白圆的图片拖上去,如下图:这样你的材质就做完了。坚持到这步的为自己鼓个掌吧,你离成功不远了。3、在界面的右上角用NGUI建地图终于到了最后一步了,剩下的工作就简单多了,回到你的界面布局那里,有关NGUI界面布局不了解的看我前面的文章,在右上角的Anchor下建个Panel,起名MiniMapPanel,然后下面加两个sprite和一个Texture,一个sprite是圆框,一个sprite是小箭头,表示地图上的玩家,Texture用来接收之前的材质显示地图,如下图:Texture那里大小调成512*512的,Material那里把之前做的材质拖上去就ok了,如果显示顺序有问题别忘了调Depth或Z值。现在你应该可以看到东西了,但小箭头位置不对,当然,还没上代码呢。创建一个MiniMap的脚本,我把全部代码贴上来。[csharp]&public&class&MiniMap&:&MonoBehaviour&{&&&&&&&&//&Use&this&for&initialization&&&&&&public&GameObject&&&&&&&public&GameObject&&&&&&&private&GameObject&&&&&&&private&float&miniMapScaleR&&&&&&&&&&&&void&Start&()&{&&&&&&&&&&map.transform.localScale&=&new&Vector3(Screen.height,&Screen.height,&1);&&&&&&&&&&hero&=&GameObject.Find(&/Blade_Girl_Prefab&);&&&&&&&&&&GameObject&terrain&=&GameObject.Find(&Terrain&);&&&&&&&&&&Terrain&script&=&terrain.GetComponent&Terrain&();&&&&&&&&&&miniMapScaleRatio&=&(float)map.transform.localScale.x&/&script.terrainData.size.x;&&&&&&}&&&&&&&&&&&&//&Update&is&called&once&per&frame&&&&&&void&Update&()&{&&&&&&&&&&&&&&&&&&&&if&(hero&&&&point&&&&map)&{&&&&&&&&&&&&&&point.transform.rotation&=&Quaternion.Euler(0,&0,&-hero.transform.rotation.eulerAngles.y);&&&&&&&&&&&&&&map.transform.localPosition&=&new&Vector3()&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&x&=&-hero.transform.position.x&*&miniMapScaleRatio,&&&&&&&&&&&&&&&&&&y&=&-hero.transform.position.z&*&miniMapScaleRatio,&&&&&&&&&&&&&&&&&&z&=&0,&&&&&&&&&&&&&&};&&&&&&&&&&}&&&&&&}&&}&&代码不多吧,point是玩家标志小箭头,map是地图面板,就是1里面那个MiniMapRenderPanel,public属性的,自己拖上去吧。hero是我们的主角,miniMapScaleRatio是地图和真实地形尺寸比例,start里的第一句是在干吗呢,虽然我们的地图是512*512的,但经过实测,发现这个尺寸会随着屏幕的高度而有误差,需要设置成屏幕的高度这种尺寸,好吧,我也需要有人帮我解释下,Update里时刻获得主角的转向,主角在世界中是绕y轴旋转的,小标志是绕z轴旋转的,并且方向相反,这里要注意一下。然后按照角色在地形上的位置乘以地图与地形的比例获得小标志在地图上的位置,噢啦,大功告成,打完收工。
浏览3391次
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/终于写到第十篇了,庆祝一下。哈哈。这一篇我讲一下如何用NGUI做中文字体,先声明一下,我的NGUI版本是2.6.4,操作系统是mac os,版本不同或系统不同的话请自行体会哈。先在mac下的应用程序里找到字体册,然后在里面选一个你喜欢的中文字体,我选的是Kai,就是楷体啦,在电脑中找到它对应的ttf文件:Kai.ttf,然后把它存到unity的资源中,好,下面开始制作了。选择NGUI-&Open the Font Maker,在打开的Font Maker中,Type选Dynamic,Font TTF选刚建的Kai.ttf,Font Size是字体大小,旁边是字体风格,包括普通,粗体,斜体,粗体和斜体组合。Font Name是字体名称。我的截图如下:点Create the Font就可以了,很简单吧,你会发现你的资源中多了一个Kai的prefab,这个就是我们做好的字体。下面说怎么用。用法也很简单,先创建一个Label,选NGUI-&Open the Widget Wizard,Atlas不用管,Font那里把我们刚建好的Kai的prefab拖上来,Template那里选Label,如下图:我这里是给我们的英雄写个名字,好喜欢时空领地里的露娜,处处为人照相却不求回报,来做我们的女神吧,所以在UILabel那里留下了她的大名:注意黄叹号,大致意思就是说我们设置Depth是没用的,你得通过设置Z值来改变先后顺序,如果你有遮挡关系,这个要注意。最后Color Tint那里设置一下字体颜色就OK了。最后上个效果图吧。
浏览2200次
本系列文章由Aimar_Johnny编写,欢迎转载,转载请标明出处,谢谢。http://blog.csdn.net/lzhq1982/article/details/上一篇讲了NGUI如何制作游戏界面,这一篇来点干货,重点讲NGUI制作头像,血条。先上一张图。OK,这个就是我实现后的结果。先看看英雄的头像血条部分。看图说话,我的英雄头像和血条蓝条是在左上角绘制的,所以这些UI都绘制在了锚点LeftTop部分,过程是:先增加一个锚点,我起名Anchor-LeftTop,然后在Inspector面板上找到UIAnchor脚本,Side那里选择TopLeft。锚点制作完成,然后在锚点下创建一个Panel,命名为HeroBase,我将所有的英雄头像血条蓝条都绘制在这个面板上,这样有益于统一管理,比如我觉得头像位置不好,我移动这个面板就可以了,不用头像血条什么的都重调。然后我要在这个面板上加多少元素呢,如上图,HeroBlood(血条),HeroMana(蓝条),Label(英雄名字),Sprite(heroHead)(头像),Sprite(heroHeadBox)(头像背景),Sprite(heroName)(名字背景),就这些。我用到的所有图素都用NGUI的Atlas Maker打成了一张图,然后从中选择自己需要的,这个和cocos2d的spriteBatchNode类似,具体怎么用可以参考雨松大神的这篇文章:,我这里就不介绍了。先说那几个sprite,相对简单一些,选择NGUI-&Open the Widget Wizard,设置如下图所示:Atlas选你刚建的那个Atlas,用不着字体,Template选Sprite,然后点Sprite会出那个Atlas里的所有图片,选一张你需要的,最后AddTo你要加的面板上就可以了。然后你会在你的Game Scene上看到,调整它到你想要的位置,Unity的可视化编辑太方便了。这里我的头像用的是《时空领地》里的露娜的头像,再一次宣传一下,一款很不多的单机游戏哦,哈哈,sprite很简单,这里重点说一条,就是有关sprite的屏幕显示的前后顺序,如果图素是来自于一个Atlas里的,那么可以通过调整UISprite下的Depth来调整前后遮挡关系,数值越大越在上面,如果不是同一个Atlas里的资源,那调Depth不管用,Unity也会提示你,这时候可以通过调translate的Z值来调整前后。下面是本篇的重点,血条和蓝条。我的血条蓝条是用NGUI的Slider做的,选择NGUI-&Open the Widget Wizard,我这里的Atlas是用NGUI自带的Fantasy Atlas,找不到的可以在这里找:Project-&Assets-&NGUI-&Examples-&Atlases-&Fantasy,里面选蓝色小方块的Fantasy Atlas,这是prefab,然后自行拖到那个Widget Tool上的Atlas处。剩下的设置如下图:Empty就是背景图素,就是没有任何填充的时候的框的图素,Full就是前景,填充满的时候的图素。点击Add To,就会创建一个Slider的控件,其下包含背景,前景和sprite,sprite不用,删掉。然后看背景部分,把scale那里调成长方形,例如我把x调成了200,y是30,然后UISprite部分设置如下:先看那个Color Tint,是背景框的颜色,默认白色,看我最开始的那张图,我希望即使血条空了漏出后面的框,也是有透明红框的效果,所以这里我设置成了淡红,旁边那个小笔是个吸管工具,你点它可以在界面上提取任何颜色,很方便,Copy是可以复制当前颜色,会在Clipboard中体现出来,当在另一个同样的设置中也想用这个颜色时,点击Paste就可以了,复制粘贴嘛。重点是下面的Sprite Type,如果用默认的Simple,你会发现你这里是很难看的有黑框的原始图素,选Sliced是那种水平拉伸的图素,选Tiled是垂直的一格一格的图素,适合从上到下进度的那种,Filled选项,是设置那种需要转圈进度的方式,你可以都试一下,我们这里选Sliced。同理设置前景,颜色要用大红,效果就出来了,然后选择那个Slider父控件,我这里是HeroBlood,我们看一下它的UISlider部分:那个Value就是我们用来显示进度的百分比,最大为1,你可以拖动它后运行一下看看效果。那个Steps我不知道怎么用,默认就好了,Direction那里有必要说一下,咱们这里是水平的,如果你之前Sprite Type那里选的Tiled,那这里应该选垂直。好了,看看用法,用法超级简单,你也可以研究一下它的UISlider脚本,里面有个参数是sliderValue,对应的就是上图那个Value,只要改变它就可以了,绘制部分不用你管,它的UISlider里面的Set函数都处理好了,所以你只要像下面这样写一句代码就可以了。[csharp]&bloodSlider.sliderValue&=&(float)curHP&/&MAX_HP;&&不用我解释了吧。在血条上加个数值吧,这样更直观一些。看上面我的slider截的图,我在slider下加了个Label,选择NGUI-&Open the Widget Wizard,Atlas无所谓,Font选NUGI自带的Fantasy Font,它也在上述的Fantasy文件夹下,设置如下图:然后调整它的位置到血条上,Depth调整到最前面。用法也简单,直接上代码吧:[csharp]&bloodText.text&=&string.Format(&{0}/{1}&,&curHP,&MAX_HP);&&只要是进度条都可以像上面这么做,怪物的头像血条也是这种方法,我这里就不赘述了。下一篇我重点讲一下NGUI如何制作中文字体。好累,终于写完了,哈哈。}

我要回帖

更多关于 tag标签是什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信