不知道从哪个版本开始离线文档(主要是../Editor/Data/Documentation/html/en这个目录下的Manual和ScriptReference两个文件夹下的html页面)的打开很是缓慢。经查,在html页面内有google字样,如下图,因此可以判断被墙了。
于是写个程序把两个目录下html文件中的这句字符串都用“ ”代替了。顺利解决问题。
程序员也有一个艺术家梦。
不知道从哪个版本开始离线文档(主要是../Editor/Data/Documentation/html/en这个目录下的Manual和ScriptReference两个文件夹下的html页面)的打开很是缓慢。经查,在html页面内有google字样,如下图,因此可以判断被墙了。
于是写个程序把两个目录下html文件中的这句字符串都用“ ”代替了。顺利解决问题。
打包android,报错(Unable to kill the adb server.Please make sure the Android SDK is installed and is properly configured in the Editor……),具体如下图所示:
adb的默认端口是5037
命令:netstat -ano
如上图最后一列PID,即Process ID(进程标识ID)。
命令:netstat -ano|findstr “XXXX”
如上图22056即为占用5037端口的进程的PID。
有了PID,可以在任务管理器找到对应的进程,如下图:
也可以使用命令:tasklist|findstr “XXXX”,如下图:
(注意,命令tasklist可以获得当前所有的进程)
命令:taskkill/pid XXXX
有时候需要/f 强制关闭。即:taskkill/pid xxxx/f,如下图。
有时候占用adb的进程怎么都关闭不了,比如金山手机助手的进程sjk_daemon.exe,该进程会占用5037端口,且关闭后不一会儿又重启,很是恼火。无奈只有卸载金山毒霸(金山助手是集成在毒霸内的,日它仙人了~)
adb全称为Android Debug Bridge,起调试桥的作用。
1:将Android SDK中的platform-tools路径加到系统环境变量中
2:也可以不安装,每次都全目录来执行cmd命令
adb help 查看adb相关信息,包括版本号,常用命令等等
adb version 查看adb版本
adb install abc.apk 安装apk,一般虚拟机安装的时候使用该命令
adb devices 列出当前连接到主机的设备,可以用该命令查看手机是否和电脑通过usb连接了,比如用于Unity Remote联调判断连接情况
adb logcat 捕获设备的日志信息
adb logcat -s Unity 专门获得Unity相关的日志(注意U要大写)
adb shell 获取设备的shell环境
adb shell dumpsys meminfo com.test 查看正在运行的的安卓设备的程序的内存占用情况
移动端测试的时候,打包、安装等一系列工作很耗时,导致效率低下,而且移动端打包测试的log信息也不好读取。
使用Remote可以在手机上操作、测试软件,特别是可以用来测试Touch、Accelerometer、Gyroscope、Device camera streams、Compass、GPS等,而且可以在客户端PC上查看相关log信息。
对安卓,官方推荐googlePlay下载Remote。
对iOS,官方推荐asset store下载Remote软件。
本文以安卓为例说明,可以通过91助手搜索Remote,下载安装(尼玛最新版本都才13年的)
(补充:20150928,可以unity store上搜索Remote,使用Unity封装好的插件,发布到对应的移动平台,这个也是替代原来的Remote软件的。
)
2.1:转换到对应的平台
2.2:设置Remote的对应平台
Edit->Project Settings->Editor
打开后如下,第一个图为4.5.2f1的界面,第二个为5.1.1f1的界面,选择对应的设备。
上述搞定后,把移动设备和电脑用USB连接,先启动移动端的Remote软件,然后ctrl+p运行UnityEditor的程序。
1:Remote无响应
运行UnityEditor程序后,Remote软件没有显示Game界面的视图。
解决方法:
分别关闭Remote和UnityEditor程序,重新来,注意一定是两个都重启,且重启后先开Remote,再开Unity。
PC上关闭*adb进程
关闭GPRS、WIFI(备注,个人测试在打开WIFI情况下可以连接,由于测试机无卡,因此没有测试GPRS的影响)
2:糟糕的画质
使用Remote后,事实上游戏是运行在Unity editor上的,只是画面内容会通过流的形式传到设备上,由于带宽的限制,因此对数据流会大量压缩,造成画质效果差。
如上述图所示,在5.0+Remote可以选择压缩(compression)方式为png,这样便可以无损传输图像,但会占用比JPEG更多的带宽。
Resolution默认为normal,可以选择Downsize,这样可以使得图像需要低一点的带宽,从而在图像的清晰和帧率之间达到一个平衡。
3:部分API不支持
如在移动设备或者客户端上Input.GetAxis(“Mouse X/Y”)都是可以获得值的,但是在Remote上始终为0(4.6.3的结果),可以用Input.GetTouch(0).deltaPosition代替,该API在移动设备和Remote上都有效~
官方介绍Unity Remote4:http://docs.unity3d.com/Manual/UnityRemote4.html/
Firebug插件安装,一直显示如下图所示,然后便提示安装不成功。
试了使用代理,使用不同版本的firefox,以及不同版本的firebug,都无法解决。
最后找到一个解决方法,即在hosts文件下添加如下两行:
117.18.232.191 mozorg.cdn.mozilla.net
117.18.232.191 addons.cdn.mozilla.net
补充:hosts文件修改方法:http://jingyan.baidu.com/article/47a29f2410f331c01423999e.html/
Unity3D 5.0之后API中取消了AddComponent(“”),因此若要使用string来获得对应的Type,可以使用Type.GetType(“”)。注意目前(2015-09-16)GetComponent(“”)仍然保留。
采用官方XData方式的热更新方案。
游戏中的代码分为以下几类:
1:Support
用于游戏框架代码的编写,如:文件的读写、UI页面的开关、资源更新、逻辑热更assetbundle的更新等等。
2:XData
用于和逻辑代码通信,通常XData代码会挂载在具体的obj上,在XData中通常会声明public的gameobject,实现对游戏内部物体的引用。
3:逻辑热更代码
和XData脚本一一对应,通过反射的方式加载到gameobject上,另外通过获得XData中的public gameobject来实现逻辑代码的编写。
一般Support代码不需要热更,不过如果要热更的话,Support还需要分为SupportLogic和XDataSupport,本文以不需要热更为例。
support代码中提供editor模式和发布模式两种方式,保证editor模式下不需要检查逻辑代码的dll是否最新,可以直接运行工程和调试。
对于发布模式,发布的时候需要新建VS类库(建议.NET3.5),引用工程build出的Assembly-CSharp这个dll, build(建议Release模式下Build)后生成XXXXX.dll,建议后面加上新后缀.bytes,这样Unity会把它识别为AssetText,然后打包成assetbundle。
首次启动手机的时候把assetbundle解压到persistantDataPath下,然后通过本地注册表内的版本号和服务器上版本号比对,确定是否需要下载服务器上的assetbundle。然后WWW读取assetbundle,提取Type。
以上就是大概思路,另外VS类库打dll时,把不需要的引用给删除,确保dll最小。demo工程我已经放到百度云上了。
另外,以下附上,Unity官方提供的安卓代码热更的文档:
项目:Android平台代码增量更新
原则:在XData目录下的脚本不能涉及到序列化信息的更改,文件名,类名,类中所有能被序列化的field(哪些可以被序列化,参考网页serialization-in-unity和unity-serialization)。
编译步骤:
1、 编译Support的XData工程(refer: null; output:support.dll)
2、 编译Support的Logic工程(refer: support.dll; output:supportLogic.dll)动态加载
3、 单独编译本地代码的XData部分,移除Script(Logic)目录后编译(refer: support.dll; output:CSharp-Assembly.dll)
4、 编译本地代码的Logic部分(refer:support.dll, supportLogic.dll, CSharp-Assembly; output: CSharp-Assembly-Logic.dll)动态加载
Note
1、 保证四个dll的编译平台一致,统一用3.5
2、 保证CSharp-Assembly-Logic.dll和CSharp-Assembly.dll的宏定义一致
3、 关于AddComponent接口,如果参数是字符串,则只会在CSharp-Assembly中去查找对应的类;如果参数是Type,则只是调用该接口的方法所在的dll中去查找。
4、 关于程序报错可从以下方面去查找原因
在A的Awake方法中GetComponent(B),其中B也是另一个被拆分的类,出现空引用,原因在于Awake方法是时序的,A被初始化时,B有可能还没有初始化(出现这种情况,保证B在前执行就行,调整XDataA和XDataB的Script Execution Order就行:Edit->Project Settings->Script Exection Order)
5、 关于脚本跟资源的依赖关系
网上下了一个视频教程包,里面有avi格式的有mp4格式的。所有avi格式的视频都无法播放,用QQ影音播提示视频无法渲染。用迅雷播放器、自带的media player都无法播放。
最后找到了一个叫影音先锋的软件,牛牛牛。
这一系列用于记录笔者学习ulua热更新的过程。首先来看Windows下lua环境的搭建。
环境:lua for windows (lfW)
主页:http://luaforwindows.luaforge.net/
lua for windows是一整套Lua的开发环境,主要包括:
Lua Interpreter(Lua解释器)
Lua Reference Manual(Lua参考手册)
Quick Lua Tour(Lua快速入门)
Examples(Lua范例)
Librarier with documentation(一些Lua库和文档)
上述提到的主页上可以下载lfW(需要翻~墙),最新版本为:5.1.4-46。一路默认安装,最后一步会询问是否使用SciTE默认的“黑色”风格,一般建议勾选。
使用SciTE:打开SciTE,新建文件,输入print(“hello world”),然后保存为test.lua,注意文件名后缀一定要带上,且为.lua,否则无法正确运行。
然后按F5,结果如图:
基础知识以5.1.4-46这个环境下为例。
1,注释
–单行注释
–[[多行注释,
多行注释]]
2,变量
—-代码—-
a=1
b=”abc”
c={}
d=print
print(type(a))
print(type(b))
print(type(c))
print(type(d))
—-输出—-
number
string
table
function
3,变量名称
字符、数字、下划线组成,且不能以数字开头。
以下划线开头的变量通常用于特殊的值,如_VERSION,输出结果为当前Lua的版本。所以变量一般也不以下划线开头。
—-代码—-
print(_VERSIONG)
—-结果—-
Lua 5.1
4,大小写敏感
—-代码—-
ab=1
Ab=2
AB=3
print(ab,Ab,AB)
—-输出—-
1 2 3
5,关键字
Lua的关键字,如:and,break,do,else,elseif,for,function,if,local,nil,not,or,end,false,in,repeat,return,then,true,until,while。
关键字不能用于变量名。
6,字符串
单行字符串可以用””或’’修饰,若字符串中包含”或’字符,需要使用转义符\
多行字符串用[[XXXXXX]]
7,赋值
Lua支持多重赋值。
—-代码—-
a,b,c,d,e=1,2,”three”,”four”,5
print(a,b,c,d,e)
a,b,c,d,e=e,d,c,b,a
print(a,b,c,d,e)
—-输出—-
1 2 three four 5
5 four three 2 1
8,number类型
lua的数字类型都是默认double型,不用担心其精度和处理速度的问题。
—-代码—-
a,b,c,d,e=1,1.123,1E9,-123,.0008
print(“a=”..a,”b=”..b,”c=”..c,”d=”..d,”e=”..e) –..是字符串连接符
—-输出—-
a=1 b=1.123 c=1000000000 d=-123 e=0.0008
9,打印
print “Hello world!”
print ‘Hello world!’
print(“Hello world!”)
print(‘Hello world!’)
print打印后会换行。
io.write “Hello world!”
io.write ‘Hello world!’
io.write(“Hello world!”)
io.write(‘Hello world!’)
io打印后不换行。
10,Tables
—-代码,简单表的创建—-
a={} –{}创建一个空的表
b={1,2,3} –创建一个表,包含数值1,2,3
c={“a”,”b”,”c”} –创建一个表包含字符串 a,b,c
print(a,b,c) –表不能直接打印,因此结果很诡异
—-输出—-
table:002DBA80 table:002DBAD0 table:002dbaf8
—-代码,访问表—-
address={} –一个空表address
address.Street=”WuHan”
address.Age=”100”
address.Country=”China”
print(address.Street,address[“Age”],address[“Country”])
—-输出—-
Wuhan 100 China
11,if语句
—-代码,简单if语句—-
a=1
if a==1 then
print("a is one")
end
—-输出—-
a is one
—-代码,if else—-
b="happy"
if b=="sad" then
print("b is sad")
else
print("b is not sad")
end
—-输出—-
b is not sad
—-代码,if elseif else—-
c=3
if c==1 then
print("c is 1")
elseif c==2 then
print("c is 2")
else
print("c is not 1 or 2,c is"..tostring(c)) --也可以用print("c is not 1 or 2,c is"..c)
end
—-输出—-
c is not 1 or 2,c is 3
12,while语句
—-代码—-
a=1
while a~=5 do --Lua use ~= to mean not equal
a=a+1
io.write(a.." ")
end
—-输出—-
2 3 4 5
13,repeat until语句
—-代码—-
a=0
repeat
a=a+1
print(a)
until a==5
—-输出—-
1
2
3
4
5
可以发现,repeat until语句不需要使用end
14,for语句
—-代码—-
for a=1,4 --从1输出4,每次递增1
do io.write(a.." ")
end
print()
for a=1,6,3
do io.write(a.." ")
end
for key,value in pairs({1,21,3,4})
do print(key,value)
end
—-输出—-
1 2 3 4
1 4
1 1
2 21
3 3
4 4
15,打印表
—-代码—-
a={1,2,3,4,"five","elephant","mouse"}
for i,v in ipairs(a)
do print(i,v)
end
—-输出—-
1 1
2 2
3 3
4 4
5 five
6 elephant
7 mouse
16,break语句
–break用于跳出循环
—-代码—-
a=0
while true do
a=a+1
if a==10 then
break
end
end
print(a)
—-输出—-
10
17,函数
–定义一个无参数,无返回值的函数。
function myFirstLuaFunction()
print("My first lua function was called")
end
–定义一个有返回值的函数
function mySecondLuaFunction()
return “string from my second function”
end
–第一有参数,有返回值的函数
function myThirdLuaFunction(a,b,c)
return a,b,c,“a string”,1,true
end
–调用函数
myFirstLuaFunction()
mySecondLuaFunction()
myThirdLuaFunction(1,2,”three”)
18,变量作用域
默认变量的作用域为全局的,只有前面加了local标识的变量才是局部的。
19,格式化打印
—-代码—-
function printf(fmt,...)
io.write(string.format(fmt,...))
end
printf("Hello %s from %s on %s\n",os.getenv "USER" or "there",_VERSION,os.date())
—-输出—-
Hello there from Lua 5.1 on 09/03/15 16:55:59
20,Lua库
math库:
math.abs,math.acos,math.cos,math.deg,math.exp,math.ceil,math.floor,math.fmod,math.log,math.log10,math.min,math.max,math.modf,math.pi,math.pow,math.rad,math.random,math.sqrt,math.randomseed等。
string库:
string.byte,string.char,string.dump,string.find,string.format,string.gfind,string.gsub,string.len,string.lower,string.match,string.rep,string.reverse,string.sub,string.upper
table库:
table.concat,table.insert,table.maxn,table.remove,table.sort
—-代码—-
a={2}
table.insert(a,3);
table.insert(a,4);
table.sort(a,function(v1,v2) return v1>v2 end)
for i,v in ipairs(a) do print(i,v) end
—-输出—-
1 4
2 3
3 2
21,标准库-input/output
io.close,io.flush,io.input,io.lines,io.open,io.output,io.popen,io.read,io.stderr,io.stdout,io.tmpfile,io.type,io.write,
file:close,file:flush,file:line,file:read,file:seek,file:setvbuf,file:write
22,标准库-os相关
os.clock,os.date,os.difftime,os.execute,os.exit,os.getenv,os.remove,os.rename,os.setlocale,os.time,os.tmpname
—-代码—-
print(os.date())
—-输出—-
09/03/15 22:48:22
23,外部库
–Lua通过使用require()来引用外部模块。
—-代码—-
require("iuplua")
ml=iup.multiline
{
expand="YES"
value="quit this multiline edit app to continue Tutorial!"
border="YES"
}
dlg=iup.dialog{ml;title="IupMultiline",size="QUARTERxQUARTER",}
dlg:show()
print("Exit GUI app to continue!")
iup.MainLoop()
补充:
1,pairs和ipairs
ipairs(t):
for i,v in ipairs(t) do body end
will iterate over the pairs(1,t[1]),(2,t[2]),…,up to the first integer key absent from the table.(特别要注意这里index是从1开始的,也即lua的下标是从1开始的)
pairs(t):
for k,v in pairs(t) do body end
will iterate over all key-value pairs of table t.
2,null在lua中用nil表示,对于布尔类型只有nil和false是false,数字0、空字符串都是true
4,条件表达式中的与、或、非用and、or、not关键字,不可以使用&&、||、!
5,lua中的全局变量实质上被存储在一个叫”_G”的Table中了,比如我们有个全局变量abc,则可以通过_G.abc或者_G[“abc”]来访问这个全局变量。
参考:
Lua5.1在线手册:http://book.luaer.cn/
云风翻译Lua手册:http://www.codingnow.com/2000/download/lua_manual.html
编辑器扩展这部分包括很多内容,我这里给它分成以下几类:
如下为示例代码:
using UnityEngine;
using System.Collections;
using UnityEditor;
public class AddChild
{
[MenuItem("Custom/Create Child For Selected GameObjects")]
static void MenuAddChild()
{
Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable);
foreach (Transform transform in transforms)
{
GameObject aChild = new GameObject("_Child");
aChild.transform.parent = transform;
}
}
[MenuItem("Custom/Create Child For Selected GameObjects", true)]
static bool ValidateMenuAddChild()
{
return Selection.activeGameObject != null;
}
}
上述代码实现了为选择的GameObject添加子物体的功能。
函数为静态函数,函数名任意,但是第二个验证函数是固定的,必须以Validate开头,再加上前面函数的名字。
另外需要注意到第二个验证函数中第一个参数必须和第一个函数一致,第二个参数为true。当验证函数返回false的时候,则第一个函数对应的操作无法激活。代码所示即当没有选择GameObject的时候,Custom/Create Child For Selected GameObjects这个目录下的选项是灰化的。
另外MenuItem后面若是跟GameObject则选项出现在已经存在的GameObject选项栏下;
若跟Assets,则出现在Assets选项栏下,另外需要注意的是Assets选项栏下的操作都是可以通过在Project面板下右键具体assets来弹出选项的;
若跟Assets/Create则选项出现在Project视图的Create按钮下;
若跟”CONTEXT/具体Component名/XXX”则可以为这个具体组件的Inspector上下文添加菜单,菜单的打开方式为右键组件或者左键组件最右边的“齿轮”按钮,需要注意的是为具体组件的Inspector上下文添加菜单不支持多重层次。
以下代码为Inspector上下文菜单:
public class AddChild
{
[MenuItem("CONTEXT/Transform/Create Child For Selected GameObjects")]
static void MenuAddChild()
{
Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable);
foreach (Transform transform in transforms)
{
GameObject aChild = new GameObject("_Child");
aChild.transform.parent = transform;
}
}
[MenuItem("CONTEXT/Transform/Create Child For Selected GameObjects", true)]
static bool ValidateMenuAddChild()
{
return Selection.activeGameObject != null;
}
}
以上代码实现了把增加子节点的功能注入到Inspector面板上Transform组件的弹出菜单中。
另外有时候我们需要获得Inspector上下文菜单对应的组件,这个时候需要用到MenuCommand,如下:
[MenuItem("CONTEXT/Rigidbody/DoSomething")]
static void DoSomething(OnInspectorGUI command)
{
Rigidbody body=command.context;
body.mass=5;
}
补充:
MenuItem标识可以实现排序和分组的功能
[MenuItem("NewMenu/Option1", false, 1)]
private static void NewMenuOption()
{
}
[MenuItem("NewMenu/Option2", false, 2)]
private static void NewMenuOption2()
{
}
[MenuItem("NewMenu/Option3", false, 3)]
private static void NewMenuOption3()
{
}
[MenuItem("NewMenu/Option4", false, 51)]
private static void NewMenuOption4()
{
}
[MenuItem("NewMenu/Option5", false, 52)]
private static void NewMenuOption5()
{
}
如上述代码所示,最后的阿拉伯数字即排序所用,越小的排在越上,UnityEditor会自动分组,50为单位。
通过继承ScriptableWizard可以创建编辑器向导,Unity3D已经为我们封装好了一些变量、方法、消息。如:
变量:errorString(设置向导的错误提示信息)、helpString(设置向导的帮助提示)、isValid(可以控制向导的Create Button和Other Button能否点击)。
静态方法:DisplayWizard
消息:OnDrawGizmos(当向导可见的时候每帧执行,可用gizmos来实现预览的效果)、OnWizardCreate(当点击Create按钮的时候触发)、OnWizardOtherButton(当点击Other按钮的时候)、OnWizardUpdate(当向导打开的瞬间或者向导中有输入参数的变化时触发)
下面看一个实例代码:
public class CreateACube : ScriptableWizard
{
public float size = 1f;//声明为public可以被向导序列化显示在界面上,从而可以自由改变它的值
[MenuItem("Custom/CreateACube")]
static void CreateACubeWizard()
{
//ScriptableWizard.DisplayWizard<CreateACube>("创建一个Cube", "确定", "取消");//这两种写法均可,要点是提供一个type,type类型为类的名字
ScriptableWizard.DisplayWizard("创建一个Cube", typeof(CreateACube), "确定", "取消");
}
void OnDrawGizmos()
{
if (size < 3)
{
return;
}
else
{
Gizmos.color = Color.red;
Gizmos.DrawCube(Vector3.zero, new Vector3(size, size, size));
}
}
void OnWizardCreate()
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.localScale = new Vector3(size, size, size);
}
void OnWizardOtherButton()
{
Close();
}
void OnWizardUpdate()
{
helpString = "输入Cube的Size,Size要大于等于3,创建一个Cube";
if (size < 3)
{
errorString = "size要大于等于3";
isValid = false;
}
else
{
errorString = "";
isValid = true;
}
}
}
上述代码中OnDrawGizmos()没有生效,我使用的是4.5.2f1版本的Unity,还请知道原因的告知!
下图为上述代码实现的向导:
继承自EditorWindow的类,可以实现编辑器窗口的功能。类似于Inspector、Project界面等等复杂的功能,而且可以自由镶嵌到Unity界面内,构成一定的layout。
如下代码为在集成自EditorWindow的类中实现上述创建一个Size>=3的Cube的功能:
public class CreateACube2 : EditorWindow
{
float size = 1f;
string helpString = "输入Cube的Size,Size要大于等于3,创建一个Cube";
string errorString = "size需要大于等于3";
bool enableConfromButton = false;
static Color originColor;
[MenuItem("Custom/CreateACube2")]
static void Init()
{
//获取已经打开的window,如果不存在则new一个
CreateACube2 window = EditorWindow.GetWindow(typeof(CreateACube2)) as CreateACube2;
originColor = GUI.color;
}
void OnGUI()
{
GUILayout.Label(helpString, EditorStyles.boldLabel);
size = EditorGUILayout.FloatField("size:", size);
enableConfromButton = size >= 3 ? true : false;
if (enableConfromButton)
{
GUI.enabled = true;
}
else
{
GUI.enabled = false;//设置error的时候Button按钮不可点击
GUI.color=Color.red;//设置errorString的颜色为红色
GUILayout.Label(errorString);
}
GUI.color = originColor;
if (GUILayout.Button("确定"))
{
DoCreate();
}
}
void DoCreate()
{
GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
obj.transform.localScale = new Vector3(size, size, size);
}
}
OnGUI消息是继承自EditorWindow类的最重要的消息,在其内部可以自己来创建GUI。另外还有一些比较重要的消息:
重写Inspector视图,是最强大的一种编辑器扩展。下面以一个最简单的实例来说明如何使用。
我们定义一个Player:
public class Player : MonoBehaviour
{
public int armor = 75;
public int damage = 20;
public GameObject gun;
}
把脚本拖拽到GameObject作为Component后其对应的Inspector界面如下:
为了实现重写Inspector视图的目地,我们新建一个脚本PlayerEditor.cs,继承自Editor,且把脚本放到Editor目录下。代码如下:
[CanEditMultipleObjects]
[CustomEditor(typeof(Player))]
public class PlayerEditor : Editor
{
SerializedProperty armorProp;
SerializedProperty damageProp;
SerializedProperty gunProp;
void OnEnable()
{
armorProp = serializedObject.FindProperty("armor");
damageProp = serializedObject.FindProperty("damage");
gunProp = serializedObject.FindProperty("gun");
}
public override void OnInspectorGUI()
{
//更新serializedProperty,一般在OnInspector函数的一开始使用。
serializedObject.Update();
//依次重写Player.cs脚本中的armor,damage,gun
EditorGUILayout.IntSlider(armorProp, 0, 100, new GUIContent("Armor"));
if (!armorProp.hasMultipleDifferentValues)//当该值相同的时候才显示ProgressBar
{
ProgressBar(armorProp.intValue / 100.0f, "ArmorCount");
}
EditorGUILayout.IntSlider(damageProp, 0, 100, new GUIContent("Damage"));
if (!damageProp.hasMultipleDifferentValues)
{
ProgressBar(damageProp.intValue / 100.0f, "DamagePower");
}
EditorGUILayout.PropertyField(gunProp, new GUIContent("Gun Object"));
//使更改生效(对于使用了SerializedProperty、SerializedObject)
serializedObject.ApplyModifiedProperties();
}
private void ProgressBar(float value, string str)
{
Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
EditorGUI.ProgressBar(rect, value, str);
EditorGUILayout.Space();
}
}
下图为以上代码重写后的界面:
实现Inspector重写的方式有多种,上述代码使用了推荐的SerializedProperty和SerializedObject来实现
上述代码需要解释的地方有如下几点:
[CustomEditor(typeof(Player))] 这个编辑器属性可以把Editor脚本和原始cs脚本建立关系,从而实现重写。
[CanEditMultipleObjects] 使用了这个编辑器属性,可以确保当选择的多个物体具有相同组件的时候不会报错“Multi-object editing not supported”,而且可以支持多个物体编辑。
OnInspectorGUI() 重写该方法可以实现对默认Inspector界面的重写。该方法也是最常用的一个方法。
GUILayoutUtility.GetRect(18, 18, “TextField”) 该方法的作用??????
另一种不是通过SerializedProperty、SerializedObject方法来实现的代码如下:
[CanEditMultipleObjects]
[CustomEditor(typeof(Player))]
public class PlayerEditor : Editor
{
Player player;
void OnEnable()
{
player = target as Player;
}
public override void OnInspectorGUI()
{
player.armor = EditorGUILayout.IntSlider("Armor", player.armor, 0, 100);
ProgressBar(player.armor / 100f, "ArmorCount");
player.damage = EditorGUILayout.IntSlider("Damage", player.damage, 0, 100);
ProgressBar(player.damage / 100f, "DamagePower");
bool allowSceneObjects = !EditorUtility.IsPersistent(player);
player.gun = EditorGUILayout.ObjectField("Gun Object", player.gun, typeof(GameObject), allowSceneObjects) as GameObject;
}
private void ProgressBar(float value, string str)
{
Rect rect = GUILayoutUtility.GetRect(18, 18, "TextField");
EditorGUI.ProgressBar(rect, value, str);
EditorGUILayout.Space();
}
}
下图为以上代码重写后的界面:
上述代码中需要说明的地方:
补充一些在OnInspectorGUI()方法中常用到的方法:
DrawDefaultInspector() 绘制默认的对应脚本中的Inspector,常用于扩展一个已经存在的组件:比如想在Camera组件上添加个按钮,实现特殊功能,则可以写个类用[CustomEditor(typeof(Camera))]和Camera建立关系,然后在OnInspector()中先调用DrawDefaultInspector(),然后再添加需要的按钮之类的。
EditorGUILayout.Separator() 一个比较大的空行
EditorGUILayout.Space() 一个比较小的空行
GUILayout.Space(10f) 可控具体空多少行
GUILayout.BeginHorizontal()和GUILayout.EndHorizontal() 二者一起可以让内部GUI水平排列。同理还有Vertical。
Scene界面内的交互在上述Inspector重写的基础上进行,前戏步骤同上,用到的要点姿势有如下一些:
在上述OnEnable()函数中提供一个委托SceneView.OnSceneGUIDelegate=SomeFuction;
在方法SomeFuction(SceneView sceneView)中进行当Scene窗口获得焦点后的具体操作。通常需要获得一个当前交互的事件,比如Event e= Event.current;
然后通过判断e.isKey、e.character==”p”之类的来获得键盘操作。
有时我们会在Inspector界面内点击了某个按钮后触发Scene界面的交互,这个时候需要强制获得Scene的焦点。可以使用(SceneView.sceneViews[0] as SceneView).Focus();
。
必要的地方需要使用SceneView.RepaintAll();
来即时刷新Scene窗口。
Scene界面内获得当前鼠标所在位置发射的射线:Ray ray=HandleUtility.GUIPointToWorldRay(e.mousePosition);
Scene内世界坐标转换为屏幕GUI坐标:HandleUtility.WorldToGUIPoint(worldPos);
OnSceneGUI()是进行Scene界面交互一个重要消息,可以用GUI的形式在其内部绘制Scene界面交互结果信息。一个简单的例子如下,注意必须当选中带有该脚本对应的组件物体时,该消息才触发:
void OnSceneGUI()
{
Handles.BeginGUI();//绘制必须使用BeginGUI()和EndGUI(),否则不显示。
var guiPoint = HandleUtility.WorldToGUIPoint(Vector3.zero);
GUI.Box(new Rect(guiPoint.x - 50, guiPoint.y - 30, 100, 60), "你好");
GUI.Button(new Rect(200, 200, 200, 100), "Button");
Handles.EndGUI();
}
绘制Gizmos一般使用void OnDrawGizmos()方法,继承自MonoBehaviour和Scriptwizard都有该方法。
另外还有一种DrawGizmo方法,也是一种通过编辑器属性来设定的,如下建立一个TestDrawGizmo.cs的脚本,放到Editor目录下。和OnSceneGUI()不同的是可以做到不需要选中物体便可以显示相关信息:
public class TestDrawGizmo
{
[DrawGizmo(GizmoType.NotSelected | GizmoType.Pickable | GizmoType.SelectedOrChild)]
static void RenderLightGizmo(Light light, GizmoType gizmoType)
{
Handles.Label(light.transform.position, light.transform.gameObject.name);
Gizmos.DrawIcon(light.transform.position + Vector3.up, "Light.png");
if ((gizmoType & GizmoType.SelectedOrChild) != 0)
{
if ((gizmoType & GizmoType.Active) != 0)
{
Gizmos.color = Color.red*0.4f;
}
else
Gizmos.color = Color.red * 0.5f;
Gizmos.DrawSphere(light.transform.position, light.range);
}
}
}
结果如下:
补充知识
类的继承关系
ScriptableWizard:EditorWindow:ScriptableObject:Object:UnityEngine
Editor:ScriptableObject:Object:UnityEngine