【译】Capcom 自研引擎 Panta Rhei在CEDEC2014上的会议报告

原文:http://www.4gamer.net/games/032/G003263/20140911067/作者:西川善司    翻译:Traceyang2...
  • 2017-03-28

实现协程的另一种思路——关于协程的思考

  偶然看到protothread,用另一种方式实现了协程的效果。说是协程库,其实它并不是以任何库的形式存在的。而仅仅是头文件,在使用时仅仅需要包含头文件即可...
  • 2017-03-14

Unity3D引擎中投影式纹理映射应用

投影式纹理映射(Projective texturing) 常用于在低端平台实现实时阴影效果,或实现一些类似幻灯片投射的效果。从其名字就可以看出,这种技术与普通的网格渲染流程是有关系的:​ 常规的网格渲染流程如左图,首先将网格从物体本地坐标转换到世界坐标,然后通过反转的摄像机世界坐标变换矩阵将其转换到摄像机的本地坐标(Eye Space),接下来将其乘以投影矩阵,进入裁剪空间(Clip Space)。通过将裁剪空间的四维向量除w进入NDC空间(范围在(-1, 1)),最后通过ViewPort和深度的范围进行剔除操作即可。​ 将投影器看作摄像机,将投影目标(接收投影结果的网格)看作摄像机渲染的屏幕,就能看出,投影式纹理映射的原理与网格渲染的流程很相似。首先我们定义投影器的方向及投影矩阵,接着将本地坐标的网格通过相同的转换,转换到投影器的NDC空间,再进行一次重映射后(将(-1,1)映射到(0,1)),就得到了投影目标网格在投影器的屏幕空间中正确的UV值,使用这个UV对投影器产生的贴图进行采样,就可以得到正确的投影像素。​ 以上图为例,红色的炸弹是产生投影的网格,地面和地上的汽车是接收投影的网格。首先我们通过设置投影的方向(左手系沿X轴转90度)与投影的矩阵(平行投影),接着将一定范围的网格渲染到一张RenderTexture上。通过给接受投影的网格置上自定义的着色器,对着色器传入用于将世界坐标的投影接收网格转换到投影器屏幕坐标的矩阵:其中OffsetMatrix是一个用于重映射的矩阵,可将NDC空间的坐标(-1,1),映射到(0,1)的范围:ProjectionMatrix代表投影器的投影矩阵,InvertWorldMatrix代表投影器的世界到本地坐标的转换矩阵。在着色器中,通过将世界坐标的顶点位置ProjectorMatrix相乘,传入片段着色器,将传入的四维向量除w,就得到了正确的可以用于采样投影器RenderTexture的UV坐标。我们就可以实现幻灯片投影效果,硬阴影,甚至是假的软阴影(即对RenderTexture做一次模糊)。下面是部分着色器代码,仅供参考:?12345678910111213141516171819202122232425float4x4 ProjectorMatrix;  v2f vert(float4 vertex : POSITION){    v2f o;    o.pos = mul(UNITY_MATRIX_MVP, vertex);    float4 worldPos = mul(_Object2World, vertex);    o.uv = mul(ProjectorMatrix, worldPos);    return o;}  sampler2D Maou_ProjectorTexture;  fixed4 frag(v2f i) : SV_Target{    float2 texCroodProj = i.uv.xy / i.uv.w;    fixed4 color = tex2D(ProjectorTexture, texCroodProj);      // clip掉(0,1)范围之外的像素    float2 uvmasks = min(texCroodProj, 1.0 - texCroodProj);    float mask = min(uvmasks.x, uvmasks.y);    color = mask < 0.0 ? fixed4(1, 1, 1, 0) : color;      return color;}参考:http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter09.htmlhttp://www.songho.ca/opengl/gl_projectionmatrix.html
  • 2017-03-28

Unity教程:路径移动

Path Following 大多数用在 AI 的路径移动,针对事前规划好的移动路径,呈现出 AI 在移动巡逻的行为,下面就给大家介绍下在Unity中规划 AI 的移动路径的教程。 原理相当简单大家看完后也可以自行使用不同的方法应用 先建立一个新专案接著建立一个 Path.cs 脚本这个脚本是用来规划 AI 的移动路径并将其显示以供游戏开发者调整?1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586using UnityEngine;using System.Collections; public class Path : MonoBehaviour {     //Display the path    public bool showPath = true;    public Color pathColor = Color.red;     //The path is loop or not    public bool loop = true;     //The waypoint radius    public float Radius = 2.0f;     //Waypoints array    public Transform[] wayPoints;     //The Reset fuction is one of Unity API.    //MonoBehaviour.Reset()    //http://docs.unity3d.com/ScriptReference/MonoBehaviour.Reset.html    //This function is only called in editor mode.    void Reset() {         //Reset the wayPoint array        wayPoints = new Transform[ GameObject.FindGameObjectsWithTag ("WayPoint").Length ];         for( int cnt = 0; cnt < wayPoints.Length; cnt++ ) {             wayPoints[cnt] = GameObject.Find( "WayPoint_" + (cnt + 1).ToString() ).transform;         }     }     //Get the length of wayPoint array    public float Length {         get {             return wayPoints.Length;                 }         }     //Get the position in the array with its index number    public Vector3 GetPosition(int index) {         return wayPoints[index].position;           }      //The OnDrawGizmos function is one of Unity API    //MonoBehaviour.OnDrawGizmos()    //http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDrawGizmos.html    //This function will display gizmo in Scene and will not display in Game    void OnDrawGizmos() {         //If showPath is false, return        if (!showPath) return;         //Draw the path line        for ( int i = 0; i < wayPoints.Length; i++ ) {             if (i + 1 < wayPoints.Length) {                 Debug.DrawLine( wayPoints[i].position, wayPoints[i + 1].position, pathColor );                         }            else {                 if( loop ) {                     Debug.DrawLine( wayPoints[i].position, wayPoints[0].position, pathColor );                 }             }                 }         } }6~17一些基本参数的定义 19~34 Reset接著要介绍 Unity 中的一个实用方法MonoBehaviour.Reset()这个方法是应用在 Inspector 中 Component 的 Reset 裡 它可以帮助使用者快速的重新定义这裡就是使用它来重新抓取命名为:WayPoint_X 的物件所以如果使用者有多的物件时不需要一个一个重新抓取只需要点一下 Reset 就可以立即更新36~45 Length阵列 wayPoints 的封装 47~52 GetPosition取得对应 waypoint 的位置 55~84 OnDrawGizmosMonoBehavior.OnDrawGizmos()这个方法可以在 Scene 页面上绘出一些基本线段、图形而不会在 Game 页面上显示方便开发者调整 这裡使用这个方法来做路径的显示使用者可以观察目前移动路径并即时调整 接下来建立一个空物件命名为 Path并将 Path.cs 拖拉至该物件上调整实际画面如下目前路径规划已经完成接下来要创造出一台会跟著我们定义出的路径移动的车子 建立一个 PathFollowing.cs 脚本此脚本用来控制车子的移动接著创建一个 Cube 并命名为 Car并把 PathFollowing.cs 脚本拖拉至 Car 身上?1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980using UnityEngine;using System.Collections;  public class PathFollowing : MonoBehaviour {          public Path path;//The path      public float speed = 20.0f;//following speed    public float mass = 5.0f;//this is for object mass for simulating the real car      public bool isLooping = true;//the car will loop or not          private float curSpeed;//Actual speed of the car    private int curPathIndex;    private float pathLength;    private Vector3 targetPosition;      private Vector3 curVelocity;       void Start () {          pathLength = path.Length;          curPathIndex = 0;          //get the current velocity of the vehicle        curVelocity = transform.forward;          }           void Update () {          //Unify the speed        curSpeed = speed * Time.deltaTime;          targetPosition = path.GetPosition( curPathIndex );          //If reach the radius within the path then move to next point in the path        if ( Vector3.Distance(transform.position, targetPosition) < path.Radius ) {              //Don't move the vehicle if path is finished            if ( curPathIndex < pathLength - 1 )                curPathIndex++;            else if ( isLooping )                curPathIndex = 0;            else                return;                  }          //Calculate the acceleration towards the path        curVelocity += Accelerate( targetPosition );          //Move the car according to the velocity        transform.position += curVelocity;        //Rotate the car towards the desired Velocity        transform.rotation = Quaternion.LookRotation( curVelocity );          }      //Steering algorithm to steer the vector towards the target    public Vector3 Accelerate( Vector3 target ) {          //Calculate the directional vector from the current position towards the target point        Vector3 desiredVelocity = target - transform.position;          //Normalise the desired Velocity        desiredVelocity.Normalize();          desiredVelocity *= curSpeed;          //Calculate the force Vector        Vector3 steeringForce = desiredVelocity - curVelocity;        Vector3 acceleration = steeringForce / mass;          return acceleration;          }   }6~18参数定义要注意的是 curPathIndex 这个参数这个参数是用来记录当前的目标点车子会依照这个 int 来做目标点的转换 20~29 StartMonoBehaviour.Start() 31~59 UpdateMonoBehavior.Update()每 frame 都会执行一次 Update 裡的方法 为了统一速度参数而使用了 Time.deltaTime当我们把参数乘上 Time.deltaTime在这个范例中可以把它想成:每秒移动 curSpeed 公尺如果不使用 Time.deltaTime 的话则是:每 frame 移动 curSpeed 公尺两者会有极大的差距Time.deltaTime 接著就是判断 car 与目标的位置如果接近到一定距离则将目标点设为下一点并计算其加速度 这裡还可以到 Quaternion.LookRotation() 这个方法这个方法是用来返回一个 Quaternion因为 transform.rotation 本身定义是一个 Quaternion所以需要使用这个方法才不会导致编译出错 61~78 Accelerate计算加速度这裡只是简单的数学运算要注意的只有 desiredVelocity.Normalize()Vector3.Normalize() 是将向量规一化将向量规一化后在乘上我们的速度就能得到下一步的移动速度 接著回到 Hierarchy 进行设定将 Path 物件拖拉至 PathFollowing 中并填入想要的速度及重量执行结果可以试试调整不同的速度及重量看看不同的执行结果
  • 2017-03-28

GuiltyGate 罪之门 – 开发技术全解析

GuiltyGate 罪之门,是一款在 Android 平台上所推出近未来风格推理解谜游戏,结合密室逃脱的概念让玩家可以透过关卡中的线索,慢慢的解开深藏在关卡中的谜题,而本文的主要目的的就是给大家分享下在做GuiltyGate 罪之门时所用的开发技术。GuiltyGate 罪之门(以下简称 GG)由 HiParty Studio 所开发,团队成员只有两位,负责美术、企划的日式虾球吃到饱以及负责程式、专案规划的阿祥的开发日常。而这次则要以程式负责人的身份来分享开发 GG 的过程中所用到的各种技术以及遇到的问题,以下就针对核心机制、辅助工具和细节优化三大部分来做分享。核心机制程式架构相信有很多人都清楚 Unity 是一款组件式游戏引擎(Component-Based Engine)或称为实体组件系统(Entity-Component System),透过 AddComponent 可以很轻易地将组件化脚本附加于游戏物体(GameObject)。但这种便利的优点同时也带来了相对应的缺点,使得开发者无法轻易的掌握各脚本之间的生命週期(Script Lifecycle),虽然这部分可以透过 Script Execution Order 来强制修改脚本的执行顺序,但使用上却十分不方便。所以这次在设计核心机制时,尝试规划了一套独立运作的程式架构,一方面降低对 Unity 引擎的依赖性,另一方面提升脚本的可控性,透过脚本 Engine.cs 作为唯一与 Unity 引擎沟通的接口来驱动其馀系统。而为了让各系统之间的沟通能够更加顺畅,规划了一套程式架构来作为游戏的骨架,透过这一套架构达到:避免各系统之间的依赖性、保留资料互通的便利性以及游戏逻辑的独立性。状态管理器(State Manager)在完成了许多专案后,渐渐察觉到可以将游戏的运作流程视为一个巨大的状态机,透过状态机来切换不同游戏画面、运作逻辑甚至游戏场景。所以在 GG 专案中遵循了状态模式(State Pattern)来实作出状态管理器,使场景的转换透过状态管理器来驱动,增加场景转换时开发者对场景资源的可控性,这边可以试著将状态管理器理解成一个自定义的场景切换器。任务管理器(Task Manager)在不同的状态中,常常需要处理许多运作方式完全不同的子任务,像是互动介面、触发道具、剧情播放、解谜步骤…等都是独立的游戏逻辑,而任务管理器的首要任务就是管理、切换在状态中所包含的子任务。游戏系统管理器(Game System Manager)独例模式(Singleton Pattern)是最常见的设计模式之一,透过独例模式可以相当轻易的让开发者能够取得唯一的实体物件,但在这方便且容易使用的特性背后,也往往造成开发者滥用独例模式,使独例模式的扩充出现问题。所以在 GG 专案中则透过唯一实作独例模式的游戏系统管理器,来注册并管理各系统类别。?123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105using System;using System.Collections.Generic;using TEDCore.Utils;using UnityEngine;  namespace TEDCore{    public class GameSystemManager    {        private static GameSystemManager _instance = null;        public static GameSystemManager Instance        {            get            {                if(_instance == null)                {                    _instance = new GameSystemManager();                }                  return _instance;            }        }          private GameObject _root;        private Dictionary""> _monoBehaviours;        private Dictionaryobject=""> _objects;          public GameSystemManager()        {            _root = new GameObject ("[GameSystemManager]");            _root.hideFlags = HideFlags.HideInHierarchy;            GameObject.DontDestroyOnLoad (_root);              _monoBehaviours = new Dictionary"">();            _objects = new Dictionaryobject="">();        }            public static void Set(System.Object entity) where T : class        {                       if (Has ())            {                Debug.LogException (new Exception (string.Format ("[GameSystemManager] - GameSystem \"{0}\" has already existed!", typeof(T).Name)));            }            else            {                Debug.LogFormat (string.Format ("[GameSystemManager] - GameSystem \"{0}\" has set up.", typeof(T).Name));                Instance._objects.Add(typeof(T), entity);            }        }            public static void Set() where T : MonoBehaviour        {            if (Has())            {                Debug.LogException (new Exception (string.Format ("[GameSystemManager] - GameSystem \"{0}\" has already existed!", typeof(T).Name)));            }            else            {                Debug.LogFormat (string.Format ("[GameSystemManager] - GameSystem \"{0}\" has set up.", typeof(T).Name));                Instance._monoBehaviours.Add(typeof(T), Instance._root.AddComponent ());            }        }            public static T Get() where T : class        {                       if(Has())            {                if (IsInheritMonoBehaviour ())                {                    return Instance._root.GetComponent ();                }                else                {                    return Instance._objects[typeof(T)] as T;                }            }              Debug.LogException(new Exception(string.Format("[GameSystemManager] - GameSystemManager of {0} doesn't exist!", typeof(T).Name)));              return null;        }            public static bool Has() where T : class        {            if (IsInheritMonoBehaviour ())            {                return Instance._monoBehaviours.ContainsKey (typeof(T));            }            else            {                return Instance._objects.ContainsKey(typeof(T));            }        }            private static bool IsInheritMonoBehaviour() where T : class        {            return typeof(T).IsSubclassOf(typeof(MonoBehaviour));        }    }}但由于 Unity 的 MonoBehaviour 特性,导致必须针对继承与无继承 MonoBehaviour 的两种情况做相对应的处理,所以在进行类别注册时会产生相对应的两种写法。?123456789//注册继承 MonoBehaviour 类别GameSystemManager.Set();  //注册无继承 MonoBehaviour 类别GameSystemManager.Set(new EventManager());  //使用类别GameSystemManager.Get();GameSystemManager.Get();资源管理器(Resource Manager)资源管理器是为了将所有资源操作功能进行整合,包含 Resources 目录以及 AssetBundle 的载入、读取、生成…等。当开发者对资源进行任何操作时,只需要针对需求而使用相对应的方法即可。 事件管理器(Event Manager)事件管理器的实现是透过观察者模式(Observe Pattern),目的是为了在没有互相关联情况下的脚本中传送讯息,透过这种方式可以很轻易地降低类别或脚本之间的依赖性。其中管理器主要包含三个公开方法,分别是注册事件、注销事件与传送事件。?1234GameSystemManager.Get().RegisterListener(eventName, IEventListener, priority);GameSystemManager.Get().UnregisterListener(eventName, IEventListener);GameSystemManager.Get().SendEvent(eventName, eventData);声音管理器(Audio Manager)与资源管理器的作用相当,集中处理有关声音的所有功能,包含播放一次性音效、播放背景音乐…等。 计时管理器(Timer Manager)一般在处理计时功能时,都会单独处理该类别中的计时逻辑,像是时间倒数、动画间隔、特效延迟…等。虽然各个效果的运作流程有些许差异,但在计时器运作方面却完全相同,所以为了集中管理计时器的运作而完成这个子系统。Timer.cs?123456789101112131415161718192021222324252627282930313233343536373839using System;  namespace TEDCore.Timer{    public class Timer    {        public float Duration { get { return _duration; } }        public object Data { get { return _data; } }        public bool HaveFinished { get { return _haveFinished; } }          private float _duration;        private Action _onTimerFinished;        private object _data;        private bool _haveFinished = false;          public Timer(float duration, Action onTimerFinished = null, object data = null)        {            _duration = duration;            _onTimerFinished = onTimerFinished;            _data = data;        }            public void Update(float deltaTime)        {            _duration -= deltaTime;              if (_duration 更新版本输出位置 > 设定 Android Keystore > AssetBundle 建置 > Define Symbol 设定 > 版本建立。细节优化在开发 GG 的过程中,并没有遇到太多的效能议题,但是在处理档案大小以及游戏顺畅性时,做了几项特别的处理,档案包优化、Sprite Packer、预载管理器以及预载优化。档案包优化如同每个专案的开发进度一样,GG 在开发的初期与开发后期,档案包的大小有很明显的提升,由初期的 26 MB 一路上升到 76 MB,造成档案包变化这麽巨大的其中一个最明显的原因就是贴图的数量以及品质的设定。所以为了有效降低档案包的大小,很简单的做了切换贴图格式的小工具,来进行有效率的贴图格式切换动作。Sprite PackerSprite Packer 是 Unity 所提供的图集工具,由于 GG 是一款纯 2D 结构的游戏,所以在开发过程中只使用了 uGUI 来作为介面操作。虽然在开发过程中游戏的 Draw Call、CPU Usage、Memory…等都没有遇到太大问题,但是在游戏上架前夕,还是一口气的将图片资源做了分类处理,使专案的 Draw Call 能够维持最小值。预载管理器(Preload Manager)在游戏开发初期中,资源都是在使用时才进行载入的动作,导致游玩时的顺畅度降低,为了改善这个问题将资源做了预载处理,配合前面所提到的资源管理器,透过关卡之间的读取画面来进行该关卡中的所有资源预载。在专案中,预载流程分别为:读取新关卡资源 > 比对并释放旧关卡的无用资源 > 进行资源载入。预载优化在预载的过程中,一开始是直接使用异步读取机制,但是这个做法导致读取画面的流畅性降到最低,为了改善这个环节,使读取画面带给玩家较为顺畅的体验,所以在异步读取机制的基础上新增了排程规划,透过排程规划将资源分批读取,以达到顺畅读取画面的需求。?123456789101112131415161718192021222324252627282930private void Preloading(){    _loadingTimer = Time.realtimeSinceStartup;    Debug.LogFormat("Start loading object {0}", m_loadingObjects[0]);    Engine.Instance.StartCoroutine(m_resourceManager.LoadResourceAsync(m_loadingObjects[0], AsyncCallback));}    private void AsyncCallback(string name){    m_loadingObjects.Remove (name);    Debug.LogFormat("Finish loading object {0} in {1} seconds. Remaining object count = {2}",        name,        Time.realtimeSinceStartup - _loadingTimer,        m_loadingObjects.Count);      if (m_loadingObjects.Count == 0)    {        isReady = true;          if (null != _onPreloadFinished)        {            _onPreloadFinished ();        }    }    else    {        Preloading ();    }}开发回顾2015 年 06 月 09 日,在某个奇妙的因缘际会之中认识了虾球,经过简单的认识后就很正常的开始聊起游戏开发的故事,也因为这样得知了 GuiltyGate 罪之门这款游戏风格强烈的作品。一拍即合的聊天过程中,得知游戏製作遇到了意想不到的阻碍,当时并没有过多的想法,单纯的认为这款游戏相当特别,如果就这样看它消失在茫茫的游戏大海中实在是很可惜的一件事,于是就毛遂自荐的提议「要不我们就一起合作吧!」,也是因为这样才有这个机会让玩家们可以玩到现在的这个版本。在开发初期时,进度的控制都相当顺利,几乎没有太多的沟通、讨论或是意见不合的情况,就这样一路顺利的开发到了 2015 年底,但事情往往没有这麽顺遂。就在这时开始发生了製作问题,由于时间配合与工作的种种因素导致製作从 2015 年底停摆到 2017 年初将近一年之久,这其中只包含了少数的技术实验与优化更新,但在故事内容、美术素材与关卡设计就没这麽顺利了。在 2017 年初时,我们决定终止 GuiltyGate 罪之门的开发,并且打算就这样让玩家渐渐的淡忘它,不过在经过简单的沟通后,觉得不管游戏完成与否,还是希望这段时间中彼此的努力能够有个结局,所以「就让它推出吧!」,而游戏也在 2017 年 01 月 23 日上架 GooglePlay,曾经悄悄的进入热门免费排行榜前二十名,截至目前为止也获得超出团队成员预期的收穫。整体来说,参与 GuiltyGate 罪之门开发的这段时间真的是相当的充实,虽然最后没办法带给玩家们最完善的游戏内容与体验,但若是能够回到过去再选择一次,我还是会立刻参与专案开发。独立开发的过程虽然辛苦,但从中获得的经验是相当珍贵的,正在为独立开发而苦恼的开发者们,相信自己吧!至于 GuiltyGate 罪之门真的就这样结束了吗?各位玩家们,就拭目以待吧!
  • 2017-03-28

Import NGUI Package and Create UIAtlas – NGUI 插件导入、NGUI 图集制作

插件导入和图集制作在Unity开发中都是一些经常会用到的功能,为了方便大家开发,下面就给大家介绍下在NGUI中插件导入和图集制作的教程,一起来看看吧。虽然 Unity 4.6 Beta 已经出来了,网络上也已经有不少新版 uGUI 及 NGUI 的系列教学,但因为还是会有人问到 NGUI 的相关问题,所以一方面用来记录自己使用 NGUI 的心得,一方面希望能提供给刚开始使用 NGUI 的人一点方向,这个系列教学中所使用的 NGUI 版本为 3.6.8。 NGUI插件导入这里提到的导入方法适用于任何Unity插件 当我们开启一个新的Unity专案后在 Project 面板中点选右键选择 Import Package → Custom Package 加入 NGUI.unitypackage选择好插件后按下开启会出现导入画面点选Import即可导入NGUI插件NGUI图集制作导入好图集后要来制作我们的第一张图集这里使用的图片为 NGUI 里的原生图片点选 NGUI → Open → Atlas Maker接著选取自己的图片后按下Create 设定好储存路径后按下存档即产生NGUI图集这裡稍微描述一下Atlas Maker里的参数?1234567Padding:        设置图片间的距离( 单位为pixel )Trim Alpha:     是否移除空白区块PMA Shader:     勾选时,预设 Shader 为 Unity/Premultiplied ColoredPMA Shader:     未勾选,预设 Shader 为 Unlit/Transparent ColoredUnity Packer:   选用Unity内建打包方式或是NGUI自订打包方式Truecolor:      图集格式储存为ARGB32Force Square:   图集预设为2的次方
  • 2017-03-27

Comment on UIAtlas – NGUI 图集新增、更新、导出、移除

在上一篇中我们新增了一个图集,这里来讲解在NUGI中如何对图集做新增、移除、导出操作,下面是具体对对图集做新增、移除、导出的教程,一起来看看吧。 图集新增图集新增的流程和制作图集的流程大致上一样开启Atlas Maker后将想要新增的图集拖拉至UI Atlas选项中选择想要新增的图片按下 Add/Update 后即可新增图片至图集中图集更新图集更新的方法跟新增一样差别是在选择图片后图集更新的字样从Add → Update按下Add/Update后即可更新图片至图集中 图集导出有时候在图集打包好后会把多馀的零碎图片从专案中移除但如果后来又希望找到同一张图片来做调整时就会显得不方便所以NGUI支援了图片导出的功能可以直接从图集中将选定的图片导出选择想要导出的图集接著在Inspector面板中可以看到UIAtlasComponent选择想要导出的图片后按下Save As就可以将图片重新导出图集移除开启Atlas Maker面板将图集拖拉至UI Atlas选项中按下想要移除的图片旁的X按钮后点选Delete即可移除
  • 2017-03-27

最新问答

更多

【有奖问答】如何判断自己是否适合做程序员?

游戏开发社区_Gad-腾讯游戏开发者平台
灰囧太狼 2017-03-22 回答了该问题
30岁之前,做过很多事,和几个行业,也自己单干过。过了30岁,发觉自己还是适合做程序,所以果断转做程序猿。现在业余时间学习模型设计还学了素描基础,音乐制作软件FLstudio也学了,顺便进修深度学习。总之,30岁之前,我觉得自己什么都能干,年轻使劲折腾,30岁之后,才定下性子,专心为8岁时定下的诺言,做一款属于自己的游戏,为成为独立游戏开发者而学习。我个人觉得时间并不存在,所以年龄也不存在,只要我存在,世间所有的一切也就存在,所谓人生就是为了证明自己的存在。我喜爱编程,喜欢解决问题后的那种快感,我想我可以写代码写到我不存在为止。
  • 2017-03-20

【无奖问答】VR已死?

游戏开发社区_Gad-腾讯游戏开发者平台
浪里小白龙 2017-03-07 回答了该问题
VR没死,不过本来就没算活过。因为硬件上的用户体验还需要很长一段路改善,加之这得 烧很多钱,所以一开始炒得火热本来就是概念炒作而已。
  • 2017-03-06

女生真的不适合做程序员么?

游戏开发社区_Gad-腾讯游戏开发者平台
heather大主管 2017-03-20 回答了该问题
女生适合做程序员鼓励师————————————————————————简单抖机灵
  • 2017-03-17

【有奖问答】游戏开发,无止尽加班的代价到底有多大?

游戏开发社区_Gad-腾讯游戏开发者平台
、Sunshine 2017-03-06 回答了该问题
1、无止境的加班,工作压力过大,身体超负荷2、作为一个女生 从一开始步入这个行业到现在工作7年之久,用多少面膜多少化妆品 也抵不过电脑的辐射,皮肤长斑,皮肤缺水,大把大把掉头发3、时间长了犯颈椎病4、一天整个人的脑子都没有时间想多余的事情,所有的时间都在程序的思路里游荡5、怀着孕也要去都是服务器的机房里调整系统bug,当时真担心我儿子会被辐射,刚怀孕三个月作为项目主要人员加班到凌晨3点,跟着调试修改bug6、坑爹的是公司没有加班费 都是义务劳动7、好在我结婚了公司还有好多大龄 男单身,由于从事这个职业老加班 一直在单身
微信扫码关注GAD官方公众号 关注GAD二维码
qq

Gad游戏开发核心用户群

484290331

合作伙伴

  • 游戏开发社区_Gad-腾讯游戏开发者平台 Microsoft
  • 游戏开发社区_Gad-腾讯游戏开发者平台 超维星球孵化器
  • 游戏开发社区_Gad-腾讯游戏开发者平台 白鹭引擎
  • 游戏开发社区_Gad-腾讯游戏开发者平台 开源引擎LayaAir
  • 游戏开发社区_Gad-腾讯游戏开发者平台 腾讯大学
  • 游戏开发社区_Gad-腾讯游戏开发者平台 腾讯质量开放平台 WeTest