FairyGUI中常用的方法,对比UGUI[转]

素材管理
可以直接将图片(Gif)、声音(mp3)、动画、文字等素材从资源浏览器拖动到库中。
资源Url

UIPackage.getItemURL(“包名“,“资源名”)
AudioClip clip = (AudioClip)UIPackage.GetItemAsset(“包名称”,”声音名称”);

菜单”编辑”->“创建位图字体”,(输入字符,显示对应图片)
多信息文本(GRichTextField)支持链接和图文混排。
用例: aTextField.text = “请去找王大锤”;

列表 (GList)
AddChild : 增加一个项目。
AddChildAt :在指定的位置增加一个项目。
RemoveChild : 删除一个项目。
RemoveChildAt : 删除一个指定位置的项目。
RemoveChildren : 删除一个范围内的项目,或者全部删除。
GList内建了对象池。
使用对象池后的方法:
AddItemFromPool : 从池里取出(如果有)或者新建一个对象,添加到列表中
GetFromPool : 从池里取出(如果有)或者新建一个对象
ReturnToPool : 将对象返回池里
RemoveChildToPool : 删除一个列表项目,并将对象返回池里
RemoveChildToPoolAt : 删除一个指定位置的项目,并将对象返回池里
RemoveChildrenToPool:删除一个范围内的项目,或者全部删除,并将删除的对象都返回池里
添加对象时不使用池,对象池将不断增大。
正确的做法:从池中创建对象。即使用AddItemFromPool或GetFromPool。

虚拟列表 GList.SetVirtual
使用GList.numItems设定列表项目的数量。注意与GList.numChildren区分,后者是当前列表容器的显示对象数量。
但要注意显示对象和列表项目的数量在数量上和顺序上都是不一致的,也就是GetChildAt(0)获得的显示对象并不等于列表的第一条项目。
列表滚动到目标位置(第500个),调用GList.AddSelection(500)
虚拟列表只能通过numItems改变列表项目的数量,不允许使用AddChild或RemoveChild增删对象。例如如果要清空列表,必须要通过设置numItems=0,而不是RemoveChildren。
循环列表 GList.SetVirtualAndLoop()。

GList.ensureBoundsCorrect()通知GList立刻重排。(手动触发重新排列)

装载器 GLoader
运行时设置装载器内容的方式是:
aLoader.url = “ui://xxxxx”;
aLoader.url = “demo/aimage”; //这里加载的是路径为Assets/Resources/demo/aimage的一个贴图
例如,你希望从AssetBundle中获取资源,那么你可以扩展GLoader。首先编写你的Loader类,例如:

这是一个egret中使用到的自定义loader,使用getResByUrl这个API加载外部资源。
然后我们就可以用fairygui.UIObjectFactory.setLoaderExtension(MyGLoader);注册我们要使用的Loader类。注册完成后,游戏中所有装载器都是由MyGLoader实例化产生。
组件
点击穿透 默认情况下,组件的矩形区域将拦截点击,勾选后,点击事件可以穿透组件中没有内容的空白区域。
控制器 Controller
Controller c1 = aComponent.GetController(“c1”)
c1.selectedIndex = 1;//1是页面索引

c1.selectedPage = ‘pageName’;//pageName就是页面名称

说明:
对所有参与的页面,元件将分别保存不同的坐标值;对所有没有参与的页面,元件将保存同一个坐标值。
缓动功能 (位移的插值?)
按钮控制器 Button
单选组(RadioGroup)
在程序中,要获得或设置哪个按钮被选中也非常简单,使用控制器的selectedIndex或者selectedPage就可以了。

设置关联 ( 类似锚点,相对位置的设置 )

这里有一个垂直中线的关联,下面会说明,暂且不表。置可以看到,在文本内容发生变化时,文本的中线位置没有发生变化,位所以问号图标的置不变;但文本的右侧位发生了变化,所以叹号图标随之发生了移动,保持了与文本右侧的距离。
在代码中设置关联
(比如浏览器窗口被玩家拖大拖小),组件依然保持在右侧位置,那么可以这样调用:
aComoponet.addRelation(GRoot.inst, RelationType.Right_Right);

又例如希望一个动态添加到舞台的组件始终保持满屏大小,可以这样调用
aComoponet.setSize(GRoot.inst.width, GRoot.inst.height);
aComoponet.addRelation(GRoot.inst, RelationType.Size);

RelationType.Size相当于RelationType.Width_Width和RelationType.Height_Height的组合。这里强调一下,使组件变为满屏大小这个操作必须由你完成,也就是上面代码中的setSize调用。关联并不能完整这项任务,因为关联是不管元件当前的大小的,它只会在目标变化时保持两者大小的差别。

A:和容器组件”垂直中线“关联
B:和容器组件”右->右”关联
C:和容器组件“右->右”,”底->底“关联
D:和容器组件”底->底”,“垂直中线%”关联
E:和容器组件“底->底”关联
然后运行时把这个界面设置为满屏就可以了。
aComponent.SetSize(GRoot.inst.width, GRoot.inst.height);
aComoponet.addRelation(GRoot.inst, RelationType.Size);//当屏幕改变时仍然保持全屏

FairyGUI教程:(九)组件的扩展
FairyGUI提供的控件有:
图片(GImage)、动画(GMovieClip)、图形(GGraph)、文本(GTextField)、多信息文本(GRichTextFIeld)、列表(GList)、装载器(GLoader)、组件(GComponent)。这些都是非常基础的控件,他们是UI制作中的最小粒度。但仅靠这些基础控件是不足以制作出各种复杂的UI界面的。我们通过”扩展“功能,从组件(GComponent)中扩展出来以下这些复合组件:按钮(GButton),标签(GLabel),下拉框(GComboBox),进度条(GProgressBar),滑动条(GSlider),滚动条(GScrollbar)。

可以看到有六种“扩展”选择。组件可以随意在这些“扩展”中切换。选择哪种“扩展”,组件就有了哪个扩展的属性和行为特性。
指定统一点击音效
如果希望游戏中所有按钮的点击都有声音反馈,你并不需要每个按钮都设置一次声音属性,因为在游戏中可以使用代码UIConfig.buttonSound=’xxx’为所有按钮设置一个点击声音。
滚动条
运行时使用的滚动条需要通过以下代码设置:
UIConfig.horizontalScrollBar = ‘xxx’;
UIConfig.verticalScrollBar = ‘yyy’;
这里xxx和yyy就是滚动条资源的url。
UIConfig.defaultScrollBarDisplay = ScrollBarDisplayType.Auto; // 设置为,滚动时显示滚动条

9、自定义扩展
当基础组件、扩展组件都不能满足你的需求时,你可以编写自定义的扩展。使用API UIObjectFactory.setPackageItemExtension完成定义。例如:
UIObjectFactory.setPackageItemExtension(UIPackage.getItemURL(“包名“,”组件A”), MyComponent );
public class MyComponent extends GComponent
{
override protected function constructFromXML(xml:XML):void
{
super.constructFromXML(xml);
//在这里继续你的初始化
}
}
这样就为组件A指定了一个实现类MyComponent 。以后所有组件A创建出来的对象(包括在编辑器里使用的组件A)都为MyComponent 类型。例如:
var obj:MyComponent = UIPackage.createObject(“包名“, ”组件A”) as MyComponent ;
注意:如果组件A只是一个普通的组件,没有定义“扩展”,那么基类是GComponent,如上例所示;如果组件A的扩展是按钮,那么MyComponent的基类应该为GButton,如果扩展是进度条,那么基类应该为GProgressBar,等等。这个不要弄错。

控制器Tween效果
动效的播放在代码中启动,例如:
someComponent.GetTransition(“peng”).Play();
Play有多种原型,例如可以重复播放一定次数,可以在播放结束时回调等。要中途停止动效的播放,可以调用:
someComponent.GetTransition(“peng”).Stop();
Stop方法也可以带参数,原型是
public void Stop(bool setToComplete, bool processCallback);
setToComplete表示是否将组件的状态设置到播放完成的状态,如果否,组件的状态就会停留在当前时间。processCallback是否调用Play设定的播放完成回调函数。

注意:UI动效播放完毕后,组件的状态将停留在最后一帧,而不是回到第一帧,如果你希望动效播放完后组件的状态复原到播放前,你可以最后添加一帧重新设置组件的状态。

高级用法:
public void SetValue(string label, params object[] aParams)
改变指定帧的数值,例如某帧Label为aa,这帧是设置某个元件的XY值的,那么调用setValue(aa, 100,200)就可以将原来此帧设置XY的数值改为100,200。
public void SetHook(string label, TransitionHook callback)
可以设定运行到某帧时发生一个调用。

多国语言支持
2、运行时动态加载语言文件。这种方法相对比较灵活。
Unity代码片段:
string fileContent; //自行载入语言文件,这里假设已载入到此变量
FairyGUI.Utils.XML xml = new FairyGUI.Utils.XML(fileContent);
UIPackage.SetStringsSource(xml);

(十二)UI适配策略 (分辨率 设置)
GRoot.inst.setContentScaleFactor(设计分辨率宽度,设计分辨率高度,适配策略);
someComponent.setSize(GRoot.inst.width, GRoot.inst.height);// 满屏

GRoot是FairyGUI的根组件,它的大小与屏幕分辨率的关系为
GRoot.inst.width = 屏幕宽度/GRoot.contentScaleFactor;
GRoot.inst.height = 屏幕高度/GRoot.contentScaleFactor;

(十三)显示架构API
FairyGUI在原生的渲染引擎上封装了一层显示对象结构。
基础类显示对象:
GObject:显示对象的基类。
GGraph:图形对象。对应于编辑器中显示的一个图形。
GImage:图片对象。对应于编辑器中显示的一个图片。
GMovieClip:动画对象。对应于编辑器中显示的一个动画。
GLoader:装载器对象。对应于编辑器中显示的一个装载器。
GTextField:文本对象。对应于编辑器中显示的一个文本。
GRichTextField:多媒体文本。对应于编辑器中显示的一个多媒体文本。
GTextInput:输入文本对象。对应于编辑器中显示的一个文本,且文本类型被设置为“输入”。
容器类显示对象:
GComponent:组件对象。对应于编辑器中创建的一个组件。
GList:列表对象。对应于编辑器中显示的一个列表。
容器的扩展:
GLabel:标签对象。当一个组件的扩展为“标签”时,即为此类型。
GButton:按钮对象。当一个组件的扩展为“按钮”时,即为此类型。
GComboBox:组合框对象。当一个组件的扩展为“组合框”时,即为此类型。
GScrollBar:滚动条对象。当一个组件的扩展为“滚动条”时,即为此类型。
GProgressBar:进度条对象。当一个组件的扩展为“进度条”时,即为此类型。
GSlider:滑动条对象。当一个组件的扩展为“滑动条”时,即为此类型。
FairyGUI和Flash/Cocos类似,采用树状的结构组织显示对象。容器可以包含一个或多个基础显示对象,也可以包含容器。这个树状结构称为显示列表。如下图:

处于最前面的且元件范围包含点击位置的元件将捕获鼠标或触摸事件,并且开始冒泡传递(请参考事件系统 Flash/Starling/Egret/Unity)
对于组件(GComponent),宽度x高度的范围内点击检测都是有效的,无论这个范围内是否有子元件。举个例子说明。组件A和组件B分别为:

将B先添加进舞台,然后再添加A到舞台,也就是说,A显示B的前面,效果如下图:

可以看到,虽然A在B的上面,但红色方块是可见的,因为A在此区域并没有内容。当点击图中绿色点的位置时,点击事件将在A上面触发,而B是点击不了的。这是因为在A的范围内,点击是不能穿透的。
如果希望A能被穿透应该怎样?可以通过GComponent.opaque属性设定。当设定为false时,这个组件将能被穿透。例如A.opaque=false,这时,只有点击4个白块时,A才接收到点击事件,如果点击绿色点位置,B将接收到点击事件。这个特性在设计一些全屏界面时尤其要注意。也可以在编辑器里直接设定:

滚动支持
你可以在编辑器里很方便的对一个组件添加滚动特性。如果一个组件在编辑器里设置为滚动,我们可以通过GComponent.scrollPane获得滚动控制对象。
ScrollPane提供了多个API访问和控制滚动:
ScrollPane.percX/ScrollPane.percY:获得或设置当前滚动的位置,以百分比来计算,取值范围是0-1。如果希望滚动条从当前值到设置值有一个动态变化的过程,可以使用ScrollPane.setPercX和ScrollPane.setPercY,这两个API提供了一个是否使用缓动的参数。
ScrollPane.posX/ScrollPane.posY:获得或设置当前滚动的位置,以像素来计算。取值范围决定于当前显示的内容大小与视口大小的差别。例如,一个垂直滚动的组件,如果视口大小为100像素,实际内容大小为300像素,那么posY的取值范围是0~200像素,当posY=200时,滚动条滚动到最下方。posX/posY与percX/percY不同在于,除非开发者自己设置或者用户拖动滚动条,percX/percY是不变的,举个例子,当前某滚动容器的percX=0.1, posX=100,如果往容器里添加一定内容后,内容的宽度增大,percX的值仍将保持不变,依然是0.1,但posX的值会发生相应的改变。所以如果希望容器内增删内容后滚动条不发生滚动,可以先记录posX/posY的位置,添加完之后再设置posX/posY为此前记录的位置。
ScrollPane.scrollToView:调整滚动位置,使指定的元件出现在视口内。
窗口系统
窗口使用前首先要设置窗口中需要显示的内容,这通常是在编辑器里制作好的,可以直接使用Window.contentPane进行设置。建议把设置contentPane等初始化操作放置到Window.onInit方法中。
另外,FairyGUI还提供了一套机制用于窗口动态创建。动态创建是指初始时仅指定窗口需要使用的资源,等窗口需要显示时才实际开始构建窗口的内容。首先需要在窗口的构造函数中调用Window.addUISource。这个方法需要一个IUISource类型的参数,而IUISource是一个接口,用户需要自行实现载入相关UI包的逻辑。当窗口第一次显示之前,IUISource的加载方法将会被调用,并等待载入完成后才返回执行Window.OnInit,然后窗口才会显示。
调用Window.show显示窗口的流程:

如果你需要窗口显示时播放动画效果,那么覆盖doShowAnimation编写你的动画代码,并且在动画结束后调用onShown。
覆盖onShown编写其他需要在窗口显示时处理的业务逻辑。
调用Window.hide隐藏窗口的流程:

如果你需要窗口隐藏时播放动画效果,那么覆盖doHideAnimation编写你的动画代码,并且在动画结束时调用Window.hideImmediately(注意不是直接调用onHide!)。
覆盖onHide编写其他需要在窗口隐藏时处理的业务逻辑。
通常窗口会包括一个可用于拖动的标题栏,关闭按钮等。我们在编辑器创建窗口组件时,可以为它创建一个名称为frame的组件,frame组件的扩展应该选择为“标签”,这样外部组件能够为其设置图标和标题属性。frame组件中约定的内容还包括:
closeButton:一个名称为closeButton的按钮将自动作为窗口的关闭按钮。
dragArea:一个名称为dragArea的图形(类型设置为空白)将自动作为窗口的检测拖动区域,当用户在此区域内按住并拖动时,窗口随之被拖动。
contentArea:一个名称为contentArea的图形(类型设置为空白)将作为窗口的主要内容区域,这个区域只用于Window.showModalWait。当调用Window.showModalWait时,窗口会被锁定,如果设定了contentArea,则只锁定contentArea指定的区域,否则锁定整个窗口。如果你希望窗口在modalWait状态下依然能够拖动和关闭,那么就不要让contentArea覆盖标题栏区域。
注意以上的约定均为可选,是否含有组件frame,或者组件frame里是否含有约定的功能组件,并不会影响窗口的正常显示和关闭。
Popup管理
在UI系统中我们经常需要一些弹出一些组件,这些组件在用户点击空白地方的情况下就会自动消失,又或者由开发者控制消失。GRoot提供了几个API管理这些Popup组件。
GRoot.showPopup:弹出一个组件,如果指定了目标,则会调整弹出的位置到目标的下方,形成一个下拉的效果。同时提供了参数可以用来指定是向上弹出或者向下弹出。
GRoot.hidePopup: 默认情况下,用户点击空白地方就会自动关闭弹出的组件。也可以调用此API手工关闭。不指定参数时,所有当前的弹出都关闭。
FairyGUI会根据组件的大小自动计算弹出位置,以确保组件显示不会超出屏幕。

(十五)事件系统:Unity
发表回复
Unity平台参考了Flash的事件机制,设计了自己独特的事件系统。EventDispatcher是事件分发的中心,GObject就是一个EventDispatcher。每个事件类型都对应一个EventListener,接收事件并调用处理函数。例如需要编写某个元件单击的处理逻辑:
aObject.onClick.Add(aCallback);
void aCallback()
{
//some logic
}
事件回调函数
每个事件可以注册一个或多个回调函数。函数原型为:
public delegate void EventCallback0();
public delegate void EventCallback1(EventContext context);
函数可以不带参数或带一个参数。
EventContext
EventContext是回调函数的参数类型。
EventContext.sender:获得事件的分发者。
EventContext.initiator:获得事件的发起者。一般来说,事件的分发者和发起者是相同的,但如果事件已发生冒泡,则可能不相同。参考下面冒泡的描述。
EventContext.type:事件类型。
EventContext.inputEvent:如果事件是键盘/触摸/鼠标事件,通过访问inputEvent对象可以获得这类事件的相关数据。
EventContext.data:事件的数据。根据事件不同,可以有不同的含义。
事件冒泡和事件捕获
一些特殊的事件,比如鼠标/触摸事件,具备向父组件传递的特性,这个传递过程叫做冒泡。例如当手指接触A元件时,A元件触发TouchBegin事件,然后A元件的父组件B触发TouchBegin事件,然后B的父组件C也触发TouchBegin事件,以此类推,直到舞台根部。这个设计保证了所有关联的显示对象都有机会处理触摸事件,而不只是最顶端的显示对象。
冒泡过程可以被打断,通过调用EventContext.StopPropagation()可以使冒泡停止向父组件推进。
从上面的冒泡过程可以看出,事件处理的顺序应该是:A’s listeners->B’s listeners->C’s listeners,这里还有一种机制可以让链路上任意一个对象可以提前处理事件,这就是事件捕获。事件捕获是反向的,例如在上面的例子中,就是C先捕获事件,然后是B,再到A。所以所有事件处理的完整顺序应该是:
C’s capture listeners->B’s capture listeners->A’s capture listeners->A’s listeners->B’s listeners->C’s listeners
捕获传递链是不能中止的,冒泡传递链可以通过StopPropagation中止。
事件捕获的设计可以使父元件优于子元件和孙子元件检查事件。
并非所有事件都有冒泡设计,请参考下面关于各个事件的说明。并非只有冒泡事件才有捕获设计。在非冒泡事件中,capture的回调优于普通回调,仅此而已,可以作为一个优先级特性来使用。
InputEvent
对键盘事件和鼠标/触摸事件,可以通过EventContext.inputEvent获得此类事件的相关数据。InputEvent.x/InputEvent.y:鼠标或手指的位置;这是舞台坐标,因为UI可能因为自适配发生了缩放,所以如果要转成UI元件中的坐标,要使用GObject.GlobalToLocal转换。
InputEvent.keyCode:按键代码;
InputEvent.modifiers:参考UnityEngine.EventModifiers。
InputEvent.mouseWheelDelta:鼠标滚轮滚动值。
InputEvent.touchId:拖动使用手指;在PC平台,该值为0,没有意义。
InputEvent.isDoubleClick:是否双击。
EventListener
EventListener.Add/EventListener.Remove:添加或删除一个回调,回调函数可以带一个参数或者不带参数。参数的类型是object,它的实际含义随不同事件不同而不同;
EventListener.AddCapture/EventListener.RemoveCapture:添加或删除一个捕获期回调。
事件类型
GObject.onClick:单击。冒泡事件。事件数据为InputEvent对象。
GObject.onRightClick:右键单击。冒泡事件。事件数据为InputEvent对象。
GObject.onTouchBegin:鼠标或手指按下。冒泡事件。事件数据为InputEvent对象。
GObject.onTouchEnd:鼠标或手指释放。冒泡事件。事件数据为InputEvent对象。
GObject.onRollOver:鼠标移入元件。事件数据为InputEvent对象。
GObject.onRollOut:鼠标移出元件。事件数据为InputEvent对象。
GObject.onAddedToStage:元件被添加到舞台。无事件数据。
GObject.onRemovedFromStage:元件从舞台移出。无事件数据。
GObject.onKeyDown:元件接收到按键事件。只有获得焦点的情况下才能接收按键事件。冒泡事件。事件数据为InputEvent对象。
GObject.onClickLink:文本中的链接被点击。事件数据为href值,字符串类型。
GObject.onPositionChanged:元件的位置改变。无事件数据。
GObject.onSizeChanged:元件的大小改变。无事件数据。
GObject.onDragStart/GObject.onDragEnd:拖动是指玩家按住元件拖动然后释放的过程。注意只有设置了GObject.draggable属性的元件才会触发拖动事件。拖动过程中可以获得两个通知:开始和结束。当onDragStart中,调用EventContext.PreventDefault()可以立刻取消拖动。无事件数据。
GTextField.onFocusIn/GTextField.onFocusOut:只有输入类型的文本才会触发这个事件。当输入文本获得焦点时,在移动设备上会弹出小键盘。无事件数据。
GTextField.onChanged:只有输入类型的文本才会触发这个事件。无事件数据。
GMovieClip.onPlayEnd:动画设定的播放次数已经播放完毕。无事件数据。
GList.onClickItem:当GList容器内的元件被点击时触发的事件类型;事件数据为当前点击的GObject对象。
GComponent.onScroll:如果容器是滚动类型容器,则滚动发生时产生该事件。
GComponent.onDrop:注意要和普通拖动区别,一个元件被拖动并释放后并不会触发Drop事件。Drop事件需配合DragDropManager使用。当DragDropManager拖动的图标在某个元件上释放时,这个元件就会触发onDrop。事件数据为DragDropManager.StartDrag中传递的source值。
GButton.onChanged:当单选或者多选按钮选中状态改变时会触发该事件(只有在用户点击后状态改变才会触发,如果是程序改变不会触发)。无事件数据。
GComboBox.onChanged:当用户从下拉列表中选择一项时触发该事件。无事件数据。
GSlider.onChanged:当用户拖动滑块改变Slider的值时触发该事件。无事件数据。
Controller.onChanged:当控制器页面改变会触发该事件(改变selectedIndex会触发该事件,setSelectedIndex则不会)。无事件数据。

(十八)在Unity项目中使用FairyGUI
1. 使用UIPanel
只需3步就可以使用将编辑器中制作好的界面放入到Unity的场景中。第一步,从GameObject菜单中选择FairyGUI->UIPanel:

第二步,在Inspector里点击PackageName或者ComponentName,将弹出选择组件的窗口:

第三步:这个窗口里列出了所有工程里能找到的UI包,选择一个包和组件,然后点击OK。

可以看到,UI组件的内容显示出来了。(注意:Unity4版本目前不支持显示内容,只能显示线框)
如果UI包修改了,或者其他一些情况导致UIPanel显示不正常,可以使用下面的菜单刷新:

当运行后,获得UIPanel的UI的方式是:
UIPanel panel = gameObject.GetComponent();
GComponent view = panel.ui;
与其他UIPackage.CreateObject创建出来的界面不同,UIPanel在GameObjec销毁时(手动销毁或者过场景等)时会一并销毁。
UIPane只保存了UI包的名称和组件的名称,它不对纹理或其他资源产生任何引用,也就是UI使用的资源不会包含在场景数据中。
在编辑状态下,无论UI组件引用了哪些UI包的资源,包括放置在Resources目录下的和不放置在Resources下的,都能够正确显示。但当运行后,UIPanel只能自动载入放置在Resources目录或其子目录下的UI包,也只会载入自身所在的UI包,其他情况的UI包(例如引用到的UI包或打包到AssetBundle的UI包)不能自动载入。你需要在UIPanel创建前使用UIPackage.AddPackage准备好这类UI包。UIPanel在Start事件或者第一次访问UIPanel.ui属性时创建UI界面,你仍然有机会在Awake里完成这些操作。
下面是UIPanel的一些属性说明:
Render Mode: 有三种:
Screen Space Overlay (默认值),表示这个UI在屏幕空间显示,这时Transform的Scale将被锁定,而且不建议修改Transform中的其他内容(让他们保持为0)。如果要修改面板在屏幕上的位置,使用UI Transform(参考下面关于UI Transform的说明)。
Screen Space Camera 表示这个UI在屏幕空间显示,但不使用FairyGUI默认的正交相机,而是使用指定的正交相机。
World Space 表示这个UI在世界空间显示,由透视相机渲染。默认的使用场景的主相机,如果不是,那么设置Render Camera。当使用这种模式时,使用Transfrom修改UI在世界空间中的位置、缩放、旋转。但你仍然可以使用UI Transform。
注意:Render Mode只定义了FairyGUI对待这个UI的方式,通常是坐标相关的操作(如点击检测等),但和渲染无关。UI由哪个相机渲染是由GameObject的layer决定的。所以如果发现UI没有显示出来,可以检查一下GameObject的layer是否正确。例如如果是Screen Space,GameObject应该在UI层,如果是WorldSpace,则是在Default层或者其他自定义的层次。
Render Camera:当Render Mode是Screen Space Camera或者World Space时可以设置。如果不设置,默认为场景的主相机。
Sorting Order:调整UIPanel的显示顺序。越大的显示在越前面。
Fairy Batching:是否启用Fairy Batching。关于Fairy Batching请参考下面的说明。切换这个值,可以在编辑模式下实时看到DrawCall的变化(切换后点击一下Game,Stat里显示的内容才会更新),这可以使你更加容易决定是否启用这项技术。
Touch Disabled:勾选后,将关闭点击检测。当这个UI没有可交互的内容时可以勾选以提高点击检测时的性能。例如头顶血条这些类型的UI,就可以勾选。
UI Transform:当Render Mode是Screen Space时可以使用这里的设置调整UI在屏幕上的位置。你仍然可以调整Transform改变UI的位置,但我不建议你这样做,因为Transform中的坐标位置是没有经过不同分辨率自适应的调整的。当Render Mode是World Space时,建议使用Transform设置UI的位置,你仍然可以调整UI Transform改变UI的位置,但调整的效果可能不那么直观。同时你可以使用Scene视图中下图所示的原点调整UI Transform的位置属性:

Fit Screen:这里可以设置UIPanel适配屏幕。
Fit Size:UI将铺满屏幕。
Fit Width And Set Middle:UI将横向铺满屏幕,然后上下居中。
Fit Height And Set Center:UI将纵向铺满屏幕,然后左右居中。
这里提供的选项不多,因为FairyGUI推荐的是在FairyGUI编辑器中整体设计,而不是在Unity里摆放小元件。例如如果需要不同的UI在屏幕上的各个位置布局,你应该在FairyGUI编辑器中创建一个全屏大小的组件,然后在里面放置各个子组件,再用关联控制布局;最后将这个全屏组件放置到Unity,将Fit Screen设置为Fit Size即可。错误的做法是把各个子组件放置到Unity里再布局。
HitTest Mode:这里可以设置UIPanel处理鼠标或触摸事件的方式。
Default: 这是默认的方式。FairyGUI会用内置的机制检测鼠标或触摸动作,不使用射线,UIPanel也不需要创建碰撞体,效能比较高。
Raycast: 在这种方式下,UIPanel将自动创建碰撞体,并且使用射线方式进行点击检测。这种方式适合于UIPanel还需要和其他3D对象互动的情况。
2. 动态载入UI界面
在很多情况下,你并不需要将UI界面放到场景中。使用代码载入编辑器制作好的界面也非常简单:
GComponent view = UIPackage.CreateObject(“包名”, “组件名”) as GComponent
动态载入的界面不会自动销毁,例如一个背包窗口,你并不需要在每次过场景都销毁。如果要销毁界面,调用Dispose方法即可,例如
view.Dispose();
也可以动态创建UIPanel为任意游戏对象挂接UI界面,方法为:
UIPanel panel = yourGameObject.AddComponent();
panel.packageName = “包名”;
panel.componentName = “组件名”;
panel.CreateUI();
UIPanel的生命周期将和yourGameObject保持一致。再次提醒,注意yourGameObject的layer。
3. 载入UI包
Unity项目载入UI包有以下几种方式,开发者可以根据项目需要选择其中一种或者多种混搭使用:
1)将打包后的文件直接发布到Unity的Resources目录或者其子目录下,

然后在代码中调用UIPackage.AddPackage(“demo”), demo就是发布时填写的文件名。如果在子目录下,调用UIPackage.AddPacakge(“路径/demo”)即可。
2)将发布后的文件打包为两个AssetBundle,即定义文件和资源各打包为一个bundle(desc_bundle+res_bundle)。这样做的好处是一般UI的更新都是修改元件位置什么的,不涉及图片资源的更新,那么只需要重新打包和推送desc_bundle就行了,不需要让玩家更新通常体积比较大的res_bouble,节省流量。打包程序由开发者按照自己熟悉的方式自行实现。以demo为例,请遵循以下规则打包:
a)demo.bytes单独打包为desc_bundle;
b)其他资源(demo@atlas0.png等),打包到res_bundle(在此例中就是atlas0和sprites)
然后在代码中调用UIPackage.AddPackage(desc_bundle, res_bundle)。bundle的载入由开发者自行实现。
3)将发布后的文件打包为一个AssetBundle。打包程序由开发者按照自己熟悉的方式自行实现。以demo为例,将demo.bytes和其他资源(demo@atlas0.png等),都放入bundle。然后在代码中调用UIPackage.AddPackage(bundle, bundle)。bundle的载入由开发者自行实现。
在使用AssetBundle的载入方案中,将由FairyGUI接管bundle并负责bundle资源的释放。
4. UI适配
可以通过两种方式设置UI自适应,第一种方式是在游戏的启动创建里任意对象挂一个FairyGUI/UIContentScaler组件:

这里选项的含义可以参考教程里关于UI适配的说明。
另外一种方式是通过代码,可以参考教程里关于UI适配的说明。
5.图集的处理
FairyGUI编辑器发布到Unity的资源通常包含一个或多个UI图集,以demo工程为例,这里demo@atlas0.png就是一个图集

图集是由UI编辑器自动生成的,但在Unity里可以改变图集的属性,常用的设置有:
1)Generate Mip Maps
不勾选。UI不使用Mip Maps功能。
2)Filter Mode
使用Bilinear,这样图像在缩放时能产生比较平滑的过渡效果,副作用是会产生一定的模糊。而且单色的图像缩放会产生不必要的渐变边缘。而使用Point,则图像在缩放时会块状化。

一般UI图集使用Bilinear即可。你也可以在UI编辑器里将图片安排到不同图集,然后每个图集设置不同的Filter Mode以满足特殊需求。
3)Max Size
一般设置到2048。
4)Format
UI图集一般都是PNG格式,并带有透明通道。同时,UI对画质的要求比较高,所以建议选择AutoMatic TrueColor。但TrueColor有一个最大的问题是文件大,而且占用内存较高,例如1024×1024的图集,将占用4MB的内存,2048×2048则达到16MB。
如果对内存使用比较敏感,也可以选用压缩格式,即AutoMatic Compressed。在桌面平台上即相同于DXT5,在Android平台上相当于ETC1,在IPhone平台则为PVRTC。这些格式能够大大降低内存占用,也因为它们是显卡直接支持的格式,所以Unity在载入时省去了解码位图的步骤,能够加快载入速度。但它们都是属于有损压缩,在显示质量上肯定不如TrueColor,特别是图集颜色十分丰富,或者有颜色渐变时,失真会比较厉害。特别重要的是,ETC1是不支持透明通道的,PVRTC对透明通道的支持也比较弱,所以并不适合带透明通道的图集。FairyGUI提供了ETC1/PVRTC+A的解决方案。首先,在发布时勾选“为Alpha通道创建单独的贴图”,如下图:

这样,就产生了两个不含透明通道的贴图,一张去除了原图透明通道的贴图,和一张将原贴图透明通道数值转换为等价灰度的贴图。这两张贴图都可以设置为Automatic Compressed。(一定要注意,不能再将主贴图设置为True Color)

FairyGUI提供了特制的着色器处理两张贴图的合并。开发者并不需要编写额外的代码就可以使用这项技术。
TIPS
你可以将本身就不含透明通道的位图(例如一些大型的背景图)安排到一张图集上。如果一张图集内的所有图片都不包含透明通道,那么最终输出的图集也不包含透明通道。不含透明通道的图集可以选用Automatic 16bits格式。
6. 字体的处理
FairyGUI使用Unity的动态字体技术渲染文字。只需以下几个简单的步骤就可以完成设置:
1)拷贝一个字体文件到项目的Resources目录或Resources/Fonts目录。字体文件可以随意使用一个,例如arial.ttf,或simhei.ttf等,这个ttf是什么字体并不影响最终显示字体的选择。
2)在Unity中点击字体文件,在inspector中修改字体属性

注意在Font Names中填写的是字体名称,标准的字体名称可以从这里找到: 中文字体的英文名称对照表。例如Droid Sans Fallback是Android平台支持中文的内置默认字体之一。多个字体用逗号隔开,Unity会使用第一个在系统中能找到的字体。
3)设置UIConfig.defaultFont=”字体文件名称”即可,注意这里使用的是文件名称,也就是说,如果放置在Resources目录的是arial.ttf,则UIConfig.defaultFont=”arial”,不需要带.ttf后缀。
因为我们没有勾选Include Font Data,所以无论这个字体文件有多大,最终并不会包含在我们的发布包中,也就是说不会增大发行包的体积。Unity会在实际运行的系统中查找与Font Names匹配的第一个字体,并使用此字体进行动态渲染。
如果你是在Unity5.x平台开发,除了上述方式外,得益于Unity5.x引入的新的字体管理API,也可以不放置字体文件到Resources目录,你只需要直接设置UIConfig.defaultFont=”字体名称“即可,同样,多个字体名称用逗号隔开。例如:UIConfig.defaultFont=”Droid Sans Fallback, LTHYSZK, Helvetica-Bold, Microsoft YaHei, SimHei”;
4)如果你的界面使用了多种字体,比如对单独的文字设置了字体:

这里用到了“黑体”这个名字的字体,这是与UIConfig.defaultFont里设置的不同的字体,我们也需要注册这种字体。方法是,首先做好上面1)和2),假如字体文件名称是HeiTi.ttf,然后
FontManager.RegisterFont(“黑体”, FontManager.GetFont(“HeiTi”));
RegisterFont的第一个参数对应编辑器里使用的字体名称;第二个参数,是Unity中放入的字体文件资源。如果文件带路径,这也需要把路径填上。
5)当你使用部分字体的粗体效果时,你会发现粗体的效果在Unity中的显示不正确,这是因为有些字体不带粗体效果的,这时候Unity就会用拉宽来实现,就像变扁了。fairygui可以用额外的mesh来解决粗体的显示。方法是:
FontManager.GetFont(“字体路径”).customFold = true;
这里字体路径与UIConfig.default里设置的内容应该完全一样。
6)某些字体,unity渲染有粗体效果,但当设置成斜体时,粗体效果又丢失(例如雅黑)。fairygui在这种情况取消unity默认渲染粗体的效果,改为增加额外的面渲染粗体。激活这个功能的方法是
FontManager.GetFont(“字体路径”).customBoldAndItalic = true;
如果已经设置了customBold,不需要再设置customBoldAndItalic。
备注1
动态字体要求玩家的运行系统环境中有你设定的字体,如果没有,实际使用的字体可能并不是你想要的效果。因此,你可以选择嵌入整个字体文件。方法是把你要用的字体文件放到Resources目录,并把Include Font Data勾选,这样整个字体文件就会包含到最终的发布包中,坏处就是会大大增加发行包的体积。
备注2
在实际游戏制作过程中发现在桌面平台下Unity对中文字体的渲染稍显模糊和暗淡,因此FairyGUI使用了特制的着色器解决了这个问题。以下是Unity默认的字体渲染效果和FairyGUI的字体渲染效果的比较:

可以看见经过FairyGUI的特殊处理,中文文字更清晰更亮。
只有在桌面平台下FairyGUI才会开启这种技术,移动平台永远不会开启,因为在高DPI情况下,字体默认的渲染效果已经非常漂亮。
另外,如果你不喜欢这种显示效果,或者你使用的是全英文的文字,也可以手动把这种技术关闭:UIConfig.renderingTextBrighterOnDesktop = false;
7 在UI中穿插其他3D对象
FairyGUI底层使用Renderer.sortingOrder来决定对象的渲染顺序,利用这个特性,我们可以很容易的将UI对象与其他3D对象(例如例子)安排在一起。FairyGUI提供了封装类GoWrapper,可以直接包装其他GameObject插入到UI层次中。如下图,一个粒子特效被安排到了UI之间。

又例如,一个3D模型穿插在UI中间

详细实现方法可以参考FairyGUI-unity包中的Assets/FairyGUI/Examples/Particles和Assets/FairyGUI/Examples/Model。
8. 与RenderTexture配合
在UI上展现3D内容的另一种方式是使用RenderTexture,特别是需要进行剪裁的情况下(如果不需要剪裁,推荐直接放置3D内容,免除RenderTexture性能消耗。参考6. 在UI中穿插其他3D对象)。在FairyGUI中,可以将Image.texture设置为一个RenderTexture,然后就可以像使用普通图片一样使用这个RenderTexture了,可以出现在任何地方,包括滚动容器。特别地,FairyGUI还能支持将RenderTexture所在位置的背景图片影射到RenderTexture渲染相机的背景上,这样就不用担心透贴的问题了。如下图,NPC使用RenderTexture渲染到UI窗口中。

详细实现方法可以参考FairyGUI-unity包中的Assets/FairyGUI/Examples/RenderTexture。
9. Drawcall优化
在Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call(DC)。Draw Call次数是一项非常重要的性能指标。UI系统一般包含数量众多的物体,有效控制DC是衡量一个UI系统是否实用的关键因素,特别是在移动设备上。
我们先来看看NGUI是怎么做的,NGUI把UIPanel中的Widget按depth排序,然后将相同材质的Widget进行Mesh合并,例如使用相同图集的图片,或者文字。Mesh合并的优点是合并后这些Widget就只产生一个DC。但这个合并过程需要计算所有Widget坐标相对于Panel的变换,而且如果Widget行为改变,例如平移,缩放等,都会触发Mesh重新合并,这会带来一定的CPU消耗,这就需要开发者谨慎组织UI元素到各个UIPanel,并且对深度需要细致安排,否则达不到减少DC效果的同时更可能带来比较大的CPU消耗。
FairyGUI没有采取合并Mesh的策略,原因有两个:
● FairyGUI使用的是树状显示对象结构,各个元件之间的层次关系非常复杂;
● FairyGUI编辑器给予用户最大的设计自由度,加上动效的引入,各个元件的状态都可能非常动态;
FairyGUI基于Unity的Dynamic Batching技术,提供了深度调整技术进行 drawcall优化 。FairyGUI能在不改变最终显示效果的前提下,尽可能的把相同材质的物体调整到连续的renderingOrder值上,以促使他们能够被Unity Dynamic Batching优化。Dynamic Batching是Unity提供的Draw Call Batching技术之一。如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。但Dynamic Batching的一个重要的前提是这些动态物体是连续渲染的。先来看看FairyGUI中物体的渲染顺序,例如:

这里有4个按钮,每个按钮都是一个组件,每个组件里包含一个图片和一个文字对象。FairyGUI是树状的显示对象结构,那么他们按深度排序应该是:

因为文字和图片的材质并不相同,所以每次从文字到图片都产生上下文切换,所以产生了6个DC。
FairyGUI的深度调整技术可以优化这种情况。观察一下,其实四个按钮之间并不相交,所以FairyGUI智能地将渲染顺序调整为:

因为FairyGUI使用了图集,而且动态文字也使用了相同的贴图,这样,DC就降低到了2个,达到了优化的目的。实际情况会比这个复杂很多,但FairyGUI能在不改变最终显示效果的前提下,尽可能的把相同材质的物体调整到连续的renderingOrder值上,以促使他们能够被Unity Dynamic Batching优化。而对开发者来说,这些底层上的调整是透明的,也就是不会影响原来的显示对象层次。从效率上考虑,这种技术仅比较物体之间的显示矩形区域(一个Rect)是否相交,所以速度是非常快的,不会带来过多的CPU负荷。
FairyGUI提供了一个开关控制组件是否启用深度调整,API是:
GComponent someComponent;
someComponent.fairyBatching = true;
如果某个组件设置了fairyBatching,那么无需在子组件和孙子组件再启用fairyBatching。一般只在顶层组件打开这个功能,例如主界面,加载界面等。注意,Window这个类已经自动打开了fairyBatching,这符合我们的使用习惯,因为一般我们都是以窗口为单位安排功能的。如果界面不复杂,Draw Call本来就不高的情况下,开发者也可以忽略这个功能,从10个DC优化到8个DC并没有什么意义。在实际使用过程中,
对于打开了fairyBatching的组件,当开发者动态改变子元件或者孙子元件的位置或大小时,并不会自动触发深度调整,例如一个图片原来显示在一个窗口里的顶层,你用Tween将它从原来的位置移到另外一个位置,这个图片就有可能被窗口里的其他元素遮挡。这时开发者可以手动触发深度调整,API是
someObject.InvalidateBatchingState();
这个API并不需要由开启了fairyBatching的组件调用,它可以用任何一个内含的元件发起。对于UI动效(Transitions),FairyGUI已经自动调用了这个API,所以开发者不必处理。
下载并运行Demo,可以观察fairyBatching的实际效果。例如这个Demo的首页:

设置了fairyBatching后由42个DC减少到了6个DC,另外,可以看到Saved by batching: 27的字样。

与ULUA配合
一、安装
1、将以下语句添加到Assets\uLua\Editor\WrapFile.cs适当的位置,然后调用Lua的菜单Gen Lua Wrap Files,重新生成绑定文件。
_GT(typeof(EventContext)),
_GT(typeof(EventDispatcher)),
_GT(typeof(EventListener)),
_GT(typeof(InputEvent)),
_GT(typeof(DisplayObject)),
_GT(typeof(Container)),
_GT(typeof(Stage)),
_GT(typeof(Controller)),
_GT(typeof(GObject)),
_GT(typeof(GGraph)),
_GT(typeof(GGroup)),
_GT(typeof(GImage)),
_GT(typeof(GLoader)),
_GT(typeof(PlayState)),
_GT(typeof(GMovieClip)),
_GT(typeof(TextFormat)),
_GT(typeof(GTextField)),
_GT(typeof(GRichTextField)),
_GT(typeof(GTextInput)),
_GT(typeof(GComponent)),
_GT(typeof(GList)),
_GT(typeof(GRoot)),
_GT(typeof(GLabel)),
_GT(typeof(GButton)),
_GT(typeof(GComboBox)),
_GT(typeof(GProgressBar)),
_GT(typeof(GSlider)),
_GT(typeof(PopupMenu)),
_GT(typeof(ScrollPane)),
_GT(typeof(Transition)),
_GT(typeof(UIPackage)),
_GT(typeof(Window)),
_GT(typeof(GObjectPool)),
_GT(typeof(Relations)),
_GT(typeof(RelationType)),
2、拷贝FairyGUI.lua到你的lua文件存放目录。
二、使用
1、普通方法的侦听和删除侦听
require ‘FairyGUI’
function OnClick() –也可以带上事件参数,OnClick(context)
print(‘you click’)
end
UIPackage.AddPackage(‘Demo’)
local view = UIPackage.CreateObject(‘Demo’, ‘DemoMain’)
GRoot.inst:AddChild(view)
view.onClick:Add(OnClick)
–view.onClick:Remove(OnClick)
2、类方法的侦听和删除侦听
require ‘FairyGUI’
TestClass = class(‘TestClass’, {})
function TestClass:ctor()
UIPackage.AddPackage(‘Demo’)
self.view = UIPackage.CreateObject(‘Demo’, ‘DemoMain’)
GRoot.inst:AddChild(self.view)
self.view.onClick:Add(TestClass.OnClick, self)
–self.view.onClick:Remove(TestClass.OnClick, self)
end
function TestClass:OnClick() –也可以带上事件参数,TestClass:OnClick(context)
print(‘you click’)
end
TestClass.New()
3、如果要使用Tween,你可以直接使用GObject.TweenXXXX系列函数,免除了Wrap DOTween的麻烦。

关于AssetBundle中的材质球出现InternalErrorShader错误

通过AssetBundle加载的材质,Shader出现InternalErrorShader的情况:

1.被依赖的Shader AssetBundle没有在材质之前加载;
2.被依赖的Shader AssetBundle过早被Unload。

另外,Editor下可以加载任意平台下的AssetBundle,但必须加载所设定的开发平台下的AssetBundle

Unity3D占用内存太大的解决方法[转]

Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大。 Resources.Load就是从一个缺省打进程序包里的AssetBundle里加载资源,而一般AssetBundle文件需要你自己创建,运行时 动态加载,可以指定路径和来源的。

其实场景里所有静态的对象也有这么一个加载过程,只是Unity3D后台替你自动完成了。

详细说一下细节概念:
AssetBundle运行时加载:
来自文件就用CreateFromFile(注意这种方法只能用于standalone程序)这是最快的加载方法
也可以来自Memory,用CreateFromMemory(byte[]),这个byte[]可以来自文件读取的缓冲,www的下载或者其他可能的方式。
其实WWW的assetBundle就是内部数据读取完后自动创建了一个assetBundle而已
Create完以后,等于把硬盘或者网络的一个文件读到内存一个区域,这时候只是个AssetBundle内存镜像数据块,还没有Assets的概念。
Assets加载:
用AssetBundle.Load(同Resources.Load) 这才会从AssetBundle的内存镜像里读取并创建一个Asset对象,创建Asset对象同时也会分配相应内存用于存放(反序列化)
异步读取用AssetBundle.LoadAsync
也可以一次读取多个用AssetBundle.LoadAll
AssetBundle的释放:
AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。

一个Prefab从assetBundle里Load出来 里面可能包括:Gameobject transform mesh texture material shader script和各种其他Assets。
你 Instaniate一个Prefab,是一个对Assets进行Clone(复制)+引用结合的过程,GameObject transform 是Clone是新生成的。其他mesh / texture / material / shader 等,这其中些是纯引用的关系的,包括:Texture和TerrainData,还有引用和复制同时存在的,包括:Mesh/material /PhysicMaterial。引用的Asset对象不会被复制,只是一个简单的指针指向已经Load的Asset对象。这种含糊的引用加克隆的混合, 大概是搞糊涂大多数人的主要原因。
专门要提一下的是一个特殊的东西:Script Asset,看起来很奇怪,Unity里每个Script都是一个封闭的Class定义而已,并没有写调用代码,光Class的定义脚本是不会工作的。其 实Unity引擎就是那个调用代码,Clone一个script asset等于new一个class实例,实例才会完成工作。把他挂到Unity主线程的调用链里去,Class实例里的OnUpdate OnStart等才会被执行。多个物体挂同一个脚本,其实就是在多个物体上挂了那个脚本类的多个实例而已,这样就好理解了。在new class这个过程中,数据区是复制的,代码区是共享的,算是一种特殊的复制+引用关系。
你可以再Instaniate一个同样的Prefab,还是这套mesh/texture/material/shader…,这时候会有新的GameObject等,但是不会创建新的引用对象比如Texture.
所以你Load出来的Assets其实就是个数据源,用于生成新对象或者被引用,生成的过程可能是复制(clone)也可能是引用(指针)
当你Destroy一个实例时,只是释放那些Clone对象,并不会释放引用对象和Clone的数据源对象,Destroy并不知道是否还有别的object在引用那些对象。
等到没有任何 游戏场景物体在用这些Assets以后,这些assets就成了没有引用的游离数据块了,是UnusedAssets了,这时候就可以通过 Resources.UnloadUnusedAssets来释放,Destroy不能完成这个任 务,AssetBundle.Unload(false)也不行,AssetBundle.Unload(true)可以但不安全,除非你很清楚没有任何 对象在用这些Assets了。
配个图加深理解:

Unity3D占用内存太大怎么解决呢?

虽然都叫Asset,但复制的和引用的是不一样的,这点被Unity的暗黑技术细节掩盖了,需要自己去理解。

关于内存管理
按照传统的编程思维,最好的方法是:自己维护所有对象,用一个Queue来保存所有object,不用时该Destory的,该Unload的自己处理。
但这样在C# .net框架底下有点没必要,而且很麻烦。
稳妥起见你可以这样管理

创建时:
先建立一个AssetBundle,无论是从www还是文件还是memory
用AssetBundle.load加载需要的asset
加载完后立即AssetBundle.Unload(false),释放AssetBundle文件本身的内存镜像,但不销毁加载的Asset对象。(这样你不用保存AssetBundle的引用并且可以立即释放一部分内存)
释放时:
如果有Instantiate的对象,用Destroy进行销毁
在合适的地方调用Resources.UnloadUnusedAssets,释放已经没有引用的Asset.
如果需要立即释放内存加上GC.Collect(),否则内存未必会立即被释放,有时候可能导致内存占用过多而引发异常。
这样可以保证内存始终被及时释放,占用量最少。也不需要对每个加载的对象进行引用。

当然这并不是唯一的方法,只要遵循加载和释放的原理,任何做法都是可以的。

系统在加载新场景时,所有的内存对象都会被自动销毁,包括你用AssetBundle.Load加载的对象和Instaniate克隆的。但是不包括AssetBundle文件自身的内存镜像,那个必须要用Unload来释放,用.net的术语,这种数据缓存是非托管的。

总结一下各种加载和初始化的用法:
AssetBundle.CreateFrom…..:创建一个AssetBundle内存镜像,注意同一个assetBundle文件在没有Unload之前不能再次被使用
WWW.AssetBundle:同上,当然要先new一个再 yield return 然后才能使用
AssetBundle.Load(name): 从AssetBundle读取一个指定名称的Asset并生成Asset内存对象,如果多次Load同名对象,除第一次外都只会返回已经生成的Asset 对象,也就是说多次Load一个Asset并不会生成多个副本(singleton)。
Resources.Load(path&name):同上,只是从默认的位置加载。
Instantiate(object):Clone 一个object的完整结构,包括其所有Component和子物体(详见官方文档),浅Copy,并不复制所有引用类型。有个特别用法,虽然很少这样 用,其实可以用Instantiate来完整的拷贝一个引用类型的Asset,比如Texture等,要拷贝的Texture必须类型设置为 Read/Write able。

总结一下各种释放
Destroy: 主要用于销毁克隆对象,也可以用于场景内的静态物体,不会自动释放该对象的所有引用。虽然也可以用于Asset,但是概念不一样要小心,如果用于销毁从文 件加载的Asset对象会销毁相应的资源文件!但是如果销毁的Asset是Copy的或者用脚本动态生成的,只会销毁内存对象。
AssetBundle.Unload(false):释放AssetBundle文件内存镜像
AssetBundle.Unload(true):释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存对象
Reources.UnloadAsset(Object):显式的释放已加载的Asset对象,只能卸载磁盘文件加载的Asset对象
Resources.UnloadUnusedAssets:用于释放所有没有引用的Asset对象
GC.Collect()强制垃圾收集器立即释放内存 Unity的GC功能不算好,没把握的时候就强制调用一下

在3.5.2之前好像Unity不能显式的释放Asset

举两个例子帮助理解
例子1:
一个常见的错误:你从某个AssetBundle里Load了一个prefab并克隆之:obj = Instaniate(AssetBundle1.Load(‘MyPrefab”);
这个prefab比如是个npc
然后你不需要他的时候你用了:Destroy(obj);你以为就释放干净了
其实这时候只是释放了Clone对象,通过Load加载的所有引用、非引用Assets对象全都静静静的躺在内存里。
这种情况应该在Destroy以后用:AssetBundle1.Unload(true),彻底释放干净。
如果这个AssetBundle1是要反复读取的 不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets()把所有和这个npc有关的Asset都销毁。
当然如果这个NPC也是要频繁创建 销毁的 那就应该让那些Assets呆在内存里以加速游戏体验。
由此可以解释另一个之前有人提过的话题:为什么第一次Instaniate 一个Prefab的时候都会卡一下,因为在你第一次Instaniate之前,相应的Asset对象还没有被创建,要加载系统内置的 AssetBundle并创建Assets,第一次以后你虽然Destroy了,但Prefab的Assets对象都还在内存里,所以就很快了。

顺便提一下几种加载方式的区别:
其实存在3种加载方式:
一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细 节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部assets 都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会包含加载引用 assets的操作,导致第一次加载的lag。

例子2:
从磁盘读取一个1.unity3d文件到内存并建立一个AssetBundle1对象
AssetBundle AssetBundle1 = AssetBundle.CreateFromFile(“1.unity3d”);
从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
obj1.renderer.material.mainTexture = AssetBundle1.Load(“wall”) as Texture;
把obj2的主贴图也指向同一个Texture Asset
obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
Texture是引用对象,永远不会有自动复制的情况出现(除非你真需要,用代码自己实现copy),只会是创建和添加引用
如果继续:
AssetBundle1.Unload(true) 那obj1和obj2都变成黑的了,因为指向的Texture Asset没了
如果:
AssetBundle1.Unload(false) 那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
继续:
Destroy(obj1),//obj1被释放,但并不会释放刚才Load的Texture
如果这时候:
Resources.UnloadUnusedAssets();
不会有任何内存释放 因为Texture asset还被obj2用着
如果
Destroy(obj2)
obj2被释放,但也不会释放刚才Load的Texture
继续
Resources.UnloadUnusedAssets();
这时候刚才load的Texture Asset释放了,因为没有任何引用了
最后CG.Collect();
强制立即释放内存
由此可以引申出论坛里另一个被提了几次的问题,如何加载一堆大图片轮流显示又不爆掉
不考虑AssetBundle,直接用www读图片文件的话等于是直接创建了一个Texture Asset
假设文件保存在一个List里
TLlist<string> fileList;
int n=0;
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
obj.mainTexture = image.texture;

n = (n>=fileList.Length-1)?0:n;
Resources.UnloadUnusedAssets();
}
这样可以保证内存里始终只有一个巨型Texture Asset资源,也不用代码追踪上一个加载的Texture Asset,但是速度比较慢
或者:
IEnumerator OnClick()
{
WWW image = new www(fileList[n++]);
yield return image;
Texture tex = obj.mainTexture;
obj.mainTexture = image.texture;

n = (n>=fileList.Length-1)?0:n;
Resources.UnloadAsset(tex);
}
这样卸载比较快

 

 

Hog的评论引用:

感觉这是Unity内存管理暗黑和混乱的地方,特别是牵扯到Texture
我最近也一直在测试这些用AssetBundle加载的asset一样可以用Resources.UnloadUnusedAssets卸载,但必须先AssetBundle.Unload,才会被识别为无用的asset。比较保险的做法是
创建时:
先建立一个AssetBundle,无论是从www还是文件还是memory
用AssetBundle.load加载需要的asset
用完后立即AssetBundle.Unload(false),关闭AssetBundle但不摧毁创建的对象和引用
销毁时:
对Instantiate的对象进行Destroy
在合适的地方调用Resources.UnloadUnusedAssets,释放已经没有引用的Asset.
如果需要立即释放加上GC.Collect()
这样可以保证内存始终被及时释放
只要你Unload过的AssetBundle,那些创建的对象和引用都会在LoadLevel时被自动释放。

 

全面理解Unity加载和内存管理机制之二:进一步深入和细节
Unity几种动态加载Prefab方式的差异:
其实存在3种加载prefab的方式:
一是静态引用,建一个public的变量,在Inspector里把prefab拉上去,用的时候instantiate
二是Resource.Load,Load以后instantiate
三是AssetBundle.Load,Load以后instantiate
三种方式有细节差异,前两种方式,引用对象texture是在instantiate时加载,而assetBundle.Load会把perfab的全部 assets都加载,instantiate时只是生成Clone。所以前两种方式,除非你提前加载相关引用对象,否则第一次instantiate时会 包含加载引用类assets的操作,导致第一次加载的lag。官方论坛有人说Resources.Load和静态引用是会把所有资源都预先加载的,反复测试的结果,静态引用和Resources.Load也是OnDemand的,用到时才会加载。

几种AssetBundle创建方式的差异:
CreateFromFile:这种方式不会把整个硬盘AssetBundle文件都加载到 内存来,而是类似建立一个文件操作句柄和缓冲区,需要时才实时Load,所以这种加载方式是最节省资源的,基本上AssetBundle本身不占什么内 存,只需要Asset对象的内存。可惜只能在PC/Mac Standalone程序中使用。
CreateFromMemory和www.assetBundle:这两种方式AssetBundle文件会整个镜像于内存中,理论上文件多大就需要多大的内存,之后Load时还要占用额外内存去生成Asset对象。

什么时候才是UnusedAssets?
看一个例子:
Object obj = Resources.Load(“MyPrefab”);
GameObject instance = Instantiate(obj) as GameObject;
………
Destroy(instance);
创建随后销毁了一个Prefab实例,这时候 MyPrefab已经没有被实际的物体引用了,但如果这时:
Resources.UnloadUnusedAssets();
内存并没有被释放,原因:MyPrefab还被这个变量obj所引用
这时候:
obj  = null;
Resources.UnloadUnusedAssets();
这样才能真正释放Assets对象
所以:UnusedAssets不但要没有被实际物体引用,也要没有被生命周期内的变量所引用,才可以理解为 Unused(引用计数为0)
所以所以:如果你用个全局变量保存你Load的Assets,又没有显式的设为null,那 在这个变量失效前你无论如何UnloadUnusedAssets也释放不了那些Assets的。如果你这些Assets又不是从磁盘加载的,那除了 UnloadUnusedAssets或者加载新场景以外没有其他方式可以卸载之。

一个复杂的例子,代码很丑陋实际也不可能这样做,只是为了加深理解

IEnumerator OnClick()

{

Resources.UnloadUnusedAssets();//清干净以免影响测试效果

yield return new WaitForSeconds(3);

float wait = 0.5f;

//用www读取一个assetBundle,里面是一个Unity基本球体和带一张大贴图的材质,是一个Prefab

WWW aa = new WWW(@”file://SpherePrefab.unity3d”);

yield return aa;

AssetBundle asset = aa.assetBundle;

yield return new WaitForSeconds(wait);//每步都等待0.5s以便于分析结果

Texture tt = asset.Load(“BallTexture”) as  Texture;//加载贴图

yield return new WaitForSeconds(wait);

GameObject ba = asset.Load(“SpherePrefab”) as  GameObject;//加载Prefab

yield return new WaitForSeconds(wait);

GameObject obj1 = Instantiate(ba) as GameObject;//生成实例

yield return new WaitForSeconds(wait);

Destroy(obj1);//销毁实例

yield return new WaitForSeconds(wait);

asset.Unload(false);//卸载Assetbundle

yield return new WaitForSeconds(wait);

Resources.UnloadUnusedAssets();//卸载无用资源

yield return new WaitForSeconds(wait);

ba = null;//将prefab引用置为空以后卸无用载资源

Resources.UnloadUnusedAssets();

yield return new WaitForSeconds(wait);

tt = null;//将texture引用置为空以后卸载无用资源

Resources.UnloadUnusedAssets();

}

这是测试结果的内存Profile曲线图

Unity3D占用内存太大怎么解决呢?

图片:p12.jpg

很经典的对称造型,用多少释放多少。

这是各阶段的内存和其他数据变化

说明:
1        初始状态
2        载入AssetBundle文件后,内存多了文件镜像,用量上升,Total Object和Assets增加1(AssetBundle也是object)
3        载入Texture后,内存继续上升,因为多了Texture Asset,Total Objects和Assets增加1
4        载入Prefab后,内存无明显变化,因为最占内存的Texture已经加载,Materials上升是因为多了Prefab的材质,Total Objects和Assets增加6,因为 Perfab 包含很多 Components
5        实例化Prefab以后,显存的Texture Memory、GameObjectTotal、Objects in Scene上升,都是因为实例化了一个可视的对象
6        销毁实例后,上一步的变化还原,很好理解
7        卸载AssetBundle文件后,AssetBundle文件镜像占用的内存被释放,相应的Assets和Total Objects Count也减1
8        直接Resources.UnloadUnusedAssets,没有任何变化,因为所有Assets引用并没有清空
9        把Prefab引用变量设为null以后,整个Prefab除了Texture外都没有任何引用了,所以被UnloadUnusedAssets销毁,Assets和Total Objects Count减6
10        再把Texture的引用变量设为null,之后也被UnloadUnusedAssets销毁,内存被释放,assets和Total Objects Count减1,基本还原到初始状态

从中也可以看出:
Texture加载以后是到内存,显示的时候才进入显存的Texture Memory。
所有的东西基础都是Object
Load的是Asset,Instantiate的是GameObject和Object in Scene
Load的Asset要Unload,new的或者Instantiate的object可以Destroy

原文地址

 

Unity 3D中的内存管理[转]

Unity3D在内存占用上一直被人诟病,特别是对于面向移动设备的游戏开发,动辄内存占用飙上一两百兆,导致内存资源耗尽,从而被系统强退造成极 差的体验。类似这种情况并不少见,但是绝大部分都是可以避免的。虽然理论上Unity的内存管理系统应当为开发者分忧解难,让大家投身到更有意义的事情中 去,但是对于Unity对内存的管理方式,官方文档中并没有太多的说明,基本需要依靠自己摸索。最近在接手的项目中存在严重的内存问题,在参照文档和 Unity Answer众多猜测和证实之后,稍微总结了下Unity中的内存的分配和管理的基本方式,在此共享。

虽然Unity标榜自己的内存使用全都是“Managed Memory”,但是事实上你必须正确地使用内存,以保证回收机制正确运行。如果没有做应当做的事情,那么场景和代码很有可能造成很多非必要内存的占用, 这也是很多Unity开发者抱怨内存占用太大的原因。接下来我会介绍Unity使用内存的种类,以及相应每个种类的优化和使用的技巧。遵循使用原则,可以 让非必要资源尽快得到释放,从而降低内存占用。

 

Unity中的内存种类
实际上Unity游戏使用的内存一共有三种:程序代码、托管堆(Managed Heap)以及本机堆(Native Heap)。

程序代码包括了所有的Unity引擎,使用的库,以及你所写的所有的游戏代码。在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。

这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占 用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库,稍后再说。

托管堆是被Mono使用的一部分内存。Mono项目一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。

托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,

并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用,

从而导致Mono认为这块内存一直有用,而无法回收。

最后,本机堆是Unity引擎进行申请和操作的地方,比如贴图,音效,关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。

基本理念是,如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。听起来很美好也和托管堆一样,

但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,

但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),

也是Unity给人留下“吃内存”印象的罪魁祸首。

 

优化程序代码的内存占用
这部分的优化相对简单,因为能做的事情并不多:主要就是减少打包时的引用库,改一改build设置即可。

对于一个新项目来说不会有太大问题,但是如果是已经存在的项目,可能改变会导致原来所需要的库的缺失(虽说一般来说这种可能性不大),

因此有可能无法做到最优。

 

当使用Unity开发时,默认的Mono包含库可以说大部分用不上,在Player Setting(Edit->Project Setting->Player或者Shift+Ctrl(Command)+B里的Player Setting按钮)

面板里,将最下方的Optimization栏目中“Api Compatibility Level”选为.NET 2.0 Subset,表示你只会使用到部分的.NET 2.0 Subset,不需要Unity将全部.NET的Api包含进去。接下来的“Stripping Level”表示从build的库中剥离的力度,每一个剥离选项都将从打包好的库中去掉一部分内容。你需要保证你的代码没有用到这部分被剥离的功能,

选为“Use micro mscorlib”的话将使用最小的库(一般来说也没啥问题,不行的话可以试试之前的两个)。库剥离可以极大地降低打包后的程序的尺寸以及程序代码的内存占用,唯一的缺点是这个功能只支持Pro版的Unity。

这部分优化的力度需要根据代码所用到的.NET的功能来进行调整,有可能不能使用Subset或者最大的剥离力度。

如果超出了限度,很可能会在需要该功能时因为找不到相应的库而crash掉(iOS的话很可能在Xcode编译时就报错了)。

比较好地解决方案是仍然用最强的剥离,并辅以较小的第三方的类库来完成所需功能。

一个最常见问题是最大剥离时Sysytem.Xml是不被Subset和micro支持的,如果只是为了xml,完全可以导入一个轻量级的xml库来解决依赖(Unity官方推荐这个)。

关于每个设定对应支持的库的详细列表,可以在这里找到。关于每个剥离级别到底做了什么,Unity的文档也有说明。

实际上,在游戏开发中绝大多数被剥离的功能使用不上的,因此不管如何,库剥离的优化方法都值得一试。

 

托管堆优化
Unity有一篇不错的关于托管堆代码如何写比较好的说明,在此基础上我个人有一些补充。

首先需要明确,托管堆中存储的是你在你的代码中申请的内存(不论是用js,C#还是Boo写的)。

一般来说,无非是new或者Instantiate两种生成object的方法(事实上Instantiate中也是调用了new)。

在接收到alloc请求后,托管堆在其上为要新生成的对象实例以及其实例变量分配内存,如果可用空间不足,则向系统申请更多空间。

当你使用完一个实例对象之后,通常来说在脚本中就不会再有对该对象的引用了(这包括将变量设置为null或其他引用,超出了变量的作用域,

或者对Unity对象发送Destory())。在每隔一段时间,Mono的垃圾回收机制将检测内存,将没有再被引用的内存释放回收。总的来说,

你要做的就是在尽可能早的时间将不需要的引用去除掉,这样回收机制才能正确地把不需要的内存清理出来。但是需要注意在内存清理时有可能造成游戏的短时间卡顿,

这将会很影响游戏体验,因此如果有大量的内存回收工作要进行的话,需要尽量选择合适的时间。

如果在你的游戏里,有特别多的类似实例,并需要对它们经常发送Destroy()的话,游戏性能上会相当难看。比如小熊推金币中的金币实例,按理说每枚金币落下台子后

都需要对其Destory(),然后新的金币进入台子时又需要Instantiate,这对性能是极大的浪费。一种通常的做法是在不需要时,不摧毁这个GameObject,而只是隐藏它,

并将其放入一个重用数组中。之后需要时,再从重用数组中找到可用的实例并显示。这将极大地改善游戏的性能,相应的代价是消耗部分内存,一般来说这是可以接受的。

关于对象重用,可以参考Unity关于内存方面的文档中Reusable Object Pools部分,或者Prime31有一个是用Linq来建立重用池的视频教程(Youtube,需要FQ,上,下)。

如果不是必要,应该在游戏进行的过程中尽量减少对GameObject的Instantiate()和Destroy()调用,因为对计算资源会有很大消耗。在便携设备上短时间大量生成和摧毁物体的

话,很容易造成瞬时卡顿。如果内存没有问题的话,尽量选择先将他们收集起来,然后在合适的时候(比如按暂停键或者是关卡切换),将它们批量地销毁并 且回收内存。Mono的内存回收会在后台自动进行,系统会选择合适的时间进行垃圾回收。在合适的时候,也可以手动地调用 System.GC.Collect()来建议系统进行一次垃圾回收。

要注意的是这里的调用真的仅仅只是建议,可能系统会在一段时间后在进行回收,也可能完全不理会这条请求,不过在大部分时间里,这个调用还是靠谱的。

 

本机堆的优化
当你加载完成一个Unity的scene的时候,scene中的所有用到的asset(包括Hierarchy中所有GameObject上以及脚本中赋值了的的材质,贴图,动画,声音等素材),

都会被自动加载(这正是Unity的智能之处)。也就是说,当关卡呈现在用户面前的时候,所有Unity编辑器能认识的本关卡的资源都已经被预先加 入内存了,这样在本关卡中,用户将有良好的体验,不论是更换贴图,声音,还是播放动画时,都不会有额外的加载,这样的代价是内存占用将变多。Unity最 初的设计目的还是面向台式机,

几乎无限的内存和虚拟内存使得这样的占用似乎不是问题,但是这样的内存策略在之后移动平台的兴起和大量移动设备游戏的制作中出现了弊端,因为移动设 备能使用的资源始终非常有限。因此在面向移动设备游戏的制作时,尽量减少在Hierarchy对资源的直接引用,而是使用Resource.Load的方 法,在需要的时候从硬盘中读取资源,

在使用后用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()尽快将其卸载掉。总之,这里是一个处理时间和占用内存空间的trade off,

如何达到最好的效果没有标准答案,需要自己权衡。

在关卡结束的时候,这个关卡中所使用的所有资源将会被卸载掉(除非被标记了DontDestroyOnLoad)的资源。注意不仅是DontDestroyOnLoad的资源本身,

其相关的所有资源在关卡切换时都不会被卸载。DontDestroyOnLoad一般被用来在关卡之间保存一些玩家的状态,比如分数,级别等偏向文 本的信息。如果DontDestroyOnLoad了一个包含很多资源(比如大量贴图或者声音等大内存占用的东西)的话,这部分资源在场景切换时无法卸 载,将一直占用内存,

这种情况应该尽量避免。

另外一种需要注意的情况是脚本中对资源的引用。大部分脚本将在场景转换时随之失效并被回收,但是,在场景之间被保持的脚本不在此列(通常情况是被附 着在DontDestroyOnLoad的GameObject上了)。而这些脚本很可能含有对其他物体的Component或者资源的引用,这样相关的 资源就都得不到释放,

这绝对是不想要的情况。另外,static的单例(singleton)在场景切换时也不会被摧毁,同样地,如果这种单例含有大量的对资源的引用,也会成为大问题。

因此,尽量减少代码的耦合和对其他脚本的依赖是十分有必要的。如果确实无法避免这种情况,那应当手动地对这些不再使用的引用对象调用Destroy()

或者将其设置为null。这样在垃圾回收的时候,这些内存将被认为已经无用而被回收。

需要注意的是,Unity在一个场景开始时,根据场景构成和引用关系所自动读取的资源,只有在读取一个新的场景或者reset当前场景时,才会得到清理。

因此这部分内存占用是不可避免的。在小内存环境中,这部分初始内存的占用十分重要,因为它决定了你的关卡是否能够被正常加载。因此在计算资源充足

或是关卡开始之后还有机会进行加载时,尽量减少Hierarchy中的引用,变为手动用Resource.Load,将大大减少内存占用。在 Resource.UnloadAsset()和Resources.UnloadUnusedAssets()时,只有那些真正没有任何引用指向的资源 会被回收,因此请确保在资源不再使用时,将所有对该资源的引用设置为null或者Destroy。

同样需要注意,这两个Unload方法仅仅对Resource.Load拿到的资源有效,而不能回收任何场景开始时自动加载的资源。与此类似的还有 AssetBundle的Load和Unload方法,灵活使用这些手动自愿加载和卸载的方法,是优化Unity内存占用的不二法则~

总之这些就是关于Unity3d优化细节,具体还是查看Unity3D的技术手册,以便实现最大的优化.

原帖:http://www.onevcat.com/2012/11/memory-in-unity3d/

Git常用操作[转]

Git是目前最流行的版本管理系统,学会Git几乎成了开发者的必备技能。

Git有很多优势,其中之一就是远程操作非常简便。本文详细介绍5个Git命令,它们的概念和用法,理解了这些内容,你就会完全掌握Git远程操作。

git clone
git remote
git fetch
git pull
git push
本文针对初级用户,从最简单的讲起,但是需要读者对Git的基本用法有所了解。同时,本文覆盖了上面5个命令的几乎所有的常用用法,所以对于熟练用户也有参考价值。

一、git clone
远程操作的第一步,通常是从远程主机克隆一个版本库,这时就要用到git clone命令。

$ git clone <版本库的网址>

比如,克隆jQuery的版本库。

$ git clone https://github.com/jquery/jquery.git

该命令会在本地主机生成一个目录,与远程主机的版本库同名。如果要指定不同的目录名,可以将目录名作为git clone命令的第二个参数。

$ git clone <版本库的网址> <本地目录名>

git clone支持多种协议,除了HTTP(s)以外,还支持SSH、Git、本地文件协议等,下面是一些例子。

$ git clone http[s]://example.com/path/to/repo.git/
$ git clone ssh://example.com/path/to/repo.git/
$ git clone git://example.com/path/to/repo.git/
$ git clone /opt/git/project.git
$ git clone file:///opt/git/project.git
$ git clone ftp[s]://example.com/path/to/repo.git/
$ git clone rsync://example.com/path/to/repo.git/

SSH协议还有另一种写法。

$ git clone [user@]example.com:path/to/repo.git/

通常来说,Git协议下载速度最快,SSH协议用于需要用户认证的场合。各种协议优劣的详细讨论请参考官方文档。

二、git remote
为了便于管理,Git要求每个远程主机都必须指定一个主机名。git remote命令就用于管理主机名。

不带选项的时候,git remote命令列出所有远程主机。

$ git remote
origin

使用-v选项,可以参看远程主机的网址。

$ git remote -v
origin git@github.com:jquery/jquery.git (fetch)
origin git@github.com:jquery/jquery.git (push)

上面命令表示,当前只有一台远程主机,叫做origin,以及它的网址。

克隆版本库的时候,所使用的远程主机自动被Git命名为origin。如果想用其他的主机名,需要用git clone命令的-o选项指定。

$ git clone -o jQuery https://github.com/jquery/jquery.git
$ git remote
jQuery

上面命令表示,克隆的时候,指定远程主机叫做jQuery。

git remote show命令加上主机名,可以查看该主机的详细信息。

$ git remote show <主机名>

git remote add命令用于添加远程主机。

$ git remote add <主机名> <网址>

git remote rm命令用于删除远程主机。

$ git remote rm <主机名>

git remote rename命令用于远程主机的改名。

$ git remote rename <原主机名> <新主机名>

三、git fetch
一旦远程主机的版本库有了更新(Git术语叫做commit),需要将这些更新取回本地,这时就要用到git fetch命令。

$ git fetch <远程主机名>

上面命令将某个远程主机的更新,全部取回本地。

git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。

默认情况下,git fetch取回所有分支(branch)的更新。如果只想取回特定分支的更新,可以指定分支名。

$ git fetch <远程主机名> <分支名>

比如,取回origin主机的master分支。

$ git fetch origin master

所取回的更新,在本地主机上要用”远程主机名/分支名”的形式读取。比如origin主机的master,就要用origin/master读取。

git branch命令的-r选项,可以用来查看远程分支,-a选项查看所有分支。

$ git branch -r
origin/master

$ git branch -a
* master
remotes/origin/master

上面命令表示,本地主机的当前分支是master,远程分支是origin/master。

取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支。

$ git checkout -b newBrach origin/master

上面命令表示,在origin/master的基础上,创建一个新分支。

此外,也可以使用git merge命令或者git rebase命令,在本地分支上合并远程分支。

$ git merge origin/master
# 或者
$ git rebase origin/master

上面命令表示在当前分支上,合并origin/master。

四、git pull
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。

$ git pull <远程主机名> <远程分支名>:<本地分支名>

比如,取回origin主机的next分支,与本地的master分支合并,需要写成下面这样。

$ git pull origin next:master

如果远程分支是与当前分支合并,则冒号后面的部分可以省略。

$ git pull origin next

上面命令表示,取回origin/next分支,再与当前分支合并。实质上,这等同于先做git fetch,再做git merge。

$ git fetch origin
$ git merge origin/next

在某些场合,Git会自动在本地分支与远程分支之间,建立一种追踪关系(tracking)。比如,在git clone的时候,所有本地分支默认与远程主机的同名分支,建立追踪关系,也就是说,本地的master分支自动”追踪”origin/master分支。

Git也允许手动建立追踪关系。

git branch –set-upstream master origin/next

上面命令指定master分支追踪origin/next分支。

如果当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。

$ git pull origin

上面命令表示,本地的当前分支自动与对应的origin主机”追踪分支”(remote-tracking branch)进行合并。

如果当前分支只有一个追踪分支,连远程主机名都可以省略。

$ git pull

上面命令表示,当前分支自动与唯一一个追踪分支进行合并。

如果合并需要采用rebase模式,可以使用–rebase选项。

$ git pull –rebase <远程主机名> <远程分支名>:<本地分支名>

如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致git pull不知不觉删除了本地分支。

但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。

$ git pull -p
# 等同于下面的命令
$ git fetch –prune origin
$ git fetch -p

五、git push
git push命令用于将本地分支的更新,推送到远程主机。它的格式与git pull命令相仿。

$ git push <远程主机名> <本地分支名>:<远程分支名>

注意,分支推送顺序的写法是<来源地>:<目的地>,所以git pull是<远程分支>:<本地分支>,而git push是<本地分支>:<远程分支>。

如果省略远程分支名,则表示将本地分支推送与之存在”追踪关系”的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。

$ git push origin master

上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。

如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。

$ git push origin :master
# 等同于
$ git push origin –delete master

上面命令表示删除origin主机的master分支。

如果当前分支与远程分支之间存在追踪关系,则本地分支和远程分支都可以省略。

$ git push origin

上面命令表示,将当前分支推送到origin主机的对应分支。

如果当前分支只有一个追踪分支,那么主机名都可以省略。

$ git push

如果当前分支与多个主机存在追踪关系,则可以使用-u选项指定一个默认主机,这样后面就可以不加任何参数使用git push。

$ git push -u origin master

上面命令将本地的master分支推送到origin主机,同时指定origin为默认主机,后面就可以不加任何参数使用git push了。

不带任何参数的git push,默认只推送当前分支,这叫做simple方式。此外,还有一种matching方式,会推送所有有对应的远程分支的本地分支。Git 2.0版本之前,默认采用matching方法,现在改为默认采用simple方式。如果要修改这个设置,可以采用git config命令。

$ git config –global push.default matching
# 或者
$ git config –global push.default simple

还有一种情况,就是不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用–all选项。

$ git push –all origin

上面命令表示,将所有本地分支都推送到origin主机。

如果远程主机的版本比本地版本更新,推送时Git会报错,要求先在本地做git pull合并差异,然后再推送到远程主机。这时,如果你一定要推送,可以使用–force选项。

$ git push –force origin

上面命令使用–force选项,结果导致远程主机上更新的版本被覆盖。除非你很确定要这样做,否则应该尽量避免使用–force选项。

最后,git push不会推送标签(tag),除非使用–tags选项。

$ git push origin –tags

(完)

原文