uinty 关于调用父类的方法Start的方法的问题

扫一扫,访问微社区
后使用快捷导航没有帐号?
签到成功!您今天第{todayrank}个签到,签到排名竞争激烈,记得每天都来签到哦!已连续签到:{constant}天,累计签到:{days}天
当前位置: &
查看: 2075|回复: 3
Unity50个技巧 2016版本
7排名<font color="#FF昨日变化2主题帖子积分
在线时间428 小时
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
才可以下载或查看,没有帐号?
本帖最后由 一曲流心 于
13:29 编辑
前:自己翻译了一遍,然后参考了房大大的14年翻译的版本http://blog.csdn.net/neil3d/article/details/
我在4年前发布了最初的0技巧(http://devmag.org.za//50-tips-for-working-with-unity-best-practices/) 。
尽管很多东西还是有意义的,但是也有很多已经改变了:
Unity更出色了。例如,我现在相信FPS计数。可以用属性绘制器而不太必要去写自定义编辑器。预制工作的方式使得显示嵌套预制或者相应替代方案的需求更少。脚本化对象也比较友好。Visual Studio集成变得更好,更容易调试和减少gorilla调试的需求。第三方工具和库更好了。在Asset Store里有很多可用资源来处理诸如可视化调试和更好记录的方面的东西。我们自己的Extensions 插件(免费)有很多代码设计在之前的文章里(这里当然也有很多描述)。 版本控制更好了。(或者可能是我现在更了解怎样有效使用它)。比如不需要多个拷贝或者预制的备份。我更有经验了。过去的四年我经历了很多Unity项目;包括大量的游戏原型(http://www.gamasutra.com/blogs/JonathanBailey/337/How_we_made_30_games_in_30_days.PHP),产品游戏如Father.IO,我们的旗舰Unity资源Grids。
这篇文章是初始版的修订版,考虑了上述情况。
在开始这些技巧之前,这里是一个免责声明(基本和初版一样)。
这些技巧不适用于所有Unity工程:
它们是基于我的项目经验,在3到20人的小团队里。结构、重用、条理等等都需要代价,团队规模、项目规模和项目目标决定是否应该付出这个代价。比如,你不需要在游戏开发中用到所有这些(http://www.gamasutra.com/blogs/HermanTulleken/901/Rapid_Game_Prototyping_Tips_for_Programmers.php)。许多技巧看个人偏好(它们是相对的,但这里所列出的同样是好的技术)。
Uinty也有一些好的经验在它们自己的网站上(尽管这些基本都是从性能角度的观点)。
· http://.com/learn/tutorials/topics/best-practices· Best practices for physically based content creation https://youtu.be/OeEYEUCa4tI·
Best practices in Unity https://youtu.be/HM17mAmLd7k· Internal Unity tips and tricks https://youtu.be/Ozc_hXzp_KU· Unity Tips and Tricks https://youtu.be/2S6Ygq58QF8· http://docs..com/Manual/HOWTO-ArtAssetBestPracticeGuide.html
1.从一开始就确定比例并把构建所有东西在相同比例上。否则,你可能需要之后重做资源(比如,动画播放尺寸不总是正确)。对于3D游戏来说,使用1个unity单元等于1米最好。没使用光照或者物理的2D游戏,1个unity单元等于1像素(在主流的分辨率下)刚好。对于UI(和2D游戏),选择设计好的主流分辨率(我们使用HD或者2xHD)并设计所有的资源尺寸在这个分辨率下。
2.让每个场景都可以启动运行。这样可以避免必须切换场景才能运行你的游戏,从而快速测试。如果你的对需要在场景加载中要持久化到所有场景,这就麻烦了。一种解决方案是让持久化对象变成单例,当它们不在场景中时被调用就能加载它们自身。单例在另一个技巧中详细描述。
3.使用资源控制并学会怎样有效使用。· 序列化你的资源为文本。它实际并没有真的让场景和预制更好的合并,但可以更容易看出什么被改变了。· 采取场景和预制的共享策略。一般来说,最多一个人来操作同样的场景或者预制。对小团队来说,开始工作前问下身边的人有没有在操作相应场景和预制的就行了。切换物理令牌来表示场景所有权是很有用的(只有在你的电脑上有场景令牌才能允许修改场景)。· 使用标签作为书签。· 决定并贯彻一个分支策略。因为场景和预制不能很好的合并,分支就稍显复杂。无论你选择怎样使用分支,应该有效于你的场景和预制的共享策略。· 慎用子模组。子模组(Submodules)在保持代码重用上很有用。但有些警告:
& && && && &&&o Meta文件一般在多个项目中不一致。这在非Monobehaviour或者非脚本化对象代码上一般不是,但在MonoBehaviours和脚本化对象使用子模组就会产生代码丢失了。& && && && & o 如果你工作在多个工程(包括一个或多个子模组),******没有用过github,概念不是太懂,待翻译4.让测试的场景及代码分离。提交临时资源和脚本到版本库中,并在用完时从项目中移除。
5.如果你升级了工具(特别是Unity),记得同步。当你用不同版本打开一个工程时,Unity在保留链接上比以前做的好了很多,但是有时仍会因不同版本产生丢失。
6.导入第三方资源到一个干净的工程里并且从中导出一个自用的package。直接导入资源到工程有时可能会出现问题。· 可能会有冲突(文件或名字),特别是资源本身有文件在Plugins文件夹下,或在案例中使用了标准资源中的资源。· 把它们都放到你自己的工程里可能是杂乱无章的。这在你决定不用它并删除时就是个麻烦的问题了。遵循下面几步来让导入的资源更安全:
创建一个新项目并导入资源。运行案例确保有效。组织资源到一个更合适的文件夹结构里。(我通常不把我自己的文件架构直接放在Asset下。而是确保所有的文件在一个单独的文件夹里,这样所有导入的文件就不可能会覆盖我工程里已经存在的文件了)再运行例子并确保仍正常工作。(有时,当我移动东西的时候会破坏资源,但一般这不是问题)现在移除所有不需要的东西(比如案例)。确保资源仍可编译并且预制保留了所有自身的链接。如果还有能运行的东西,就测试下。现在选择所有的资源,并导出包。导入你的工程。
7.编译自动化。小工程同样有用,但下面情况尤为有用:
你需要为游戏编译很多不同版本,其他技术水平不一的团队成员需要去编译,或者在你编译之前需要对工程做微调.
参见Unity Builds Scripting: Basic and advanced possibilities(http://www.gamasutra.com/blogs/EnriqueJGil/440/Unity_Builds_Scripting_Basic_and_advanced_possibilities.php) 这是一个不错的指导。
8.文档化你的设置。大部分文档在代码里,但是某些东西的文档应该在代码之外。让设计人员通过代码来设置很耗时。文档化的设置可以提升效率(如果文档最新的)。文档化下面的东西:
Tag的使用。Layer的使用(碰撞,裁剪,射线——本质上说,什么该在什么层)。不同Layer的GUI深度(什么该显示在什么上面)。 场景设置。 复杂预制的结构。风格偏好设置。编译设置。
基本的编码
9.把你所有的代码放到一个命名空间下。这防止你自己的库里的代码和第三方代码的冲突。但不要依赖通过命名空间来避免和导入的类冲突。甚至就算你使用了命名空间,也不要使用Object或者Action或Event作为类名。
10.使用断言(assertions)。断言在代码里测试变量是很有用的并有助于找出逻辑bug。断言在Unity.Assertions.Assert 类中。它们都是测试一些条件,如果条件不满足就会写入错误信息到控制台上。如果你不了解断言为啥有用,参见The Benefits of programming with assertions (a.k.a. assert statements).(ps:就类似于平时我们写代码判断是否为空,如果为空就怎样,这里就是如果出现异常值就写入错误到控制台。)
11.除了显示文本不要使用字符串。特别地,不要用字符串来识别对象或者预制。也有例外(Unity里有很少一部分东西只能通过名字来访问)。这种情况下,把这些字符串定义成文件里的常量,如Names或者AudioModuleNames。如果这些类变得难以管理,就使用像AnimationNames.Player.Run这样的嵌套类。
12.不要使用Invoke和SendMessage。这两个MonoBehaviour的方法通过名字来调用其他方法。通过名称调用的方法很难在代码里追踪到(你不能找到调用方,而SendMessage的范围就更大更难被追踪了)。
通过使用协程和c#的action你很容易封装实现你自己的Invoke调用。
[C#] 纯文本查看 复制代码public static Coroutine Invoke(this MonoBehaviour monoBehaviour, Action action, float time)
return monoBehaviour.StartCoroutine(InvokeImpl(action, time));
private static IEnumerator InvokeImpl(Action action, float time)
yield return new WaitForSeconds(time);
你可以在monoBehaviour这样使用:[C#] 纯文本查看 复制代码this.Invoke(ShootEnemy); //where ShootEnemy is a parameterless void method.
如果你实现了你自己的MonoBehaviour基类,就可以把你自己写的Invoke加入里面。
一个更安全的SendMessage代替选项更难实现。我的替代方案通常是使用GetComponent变量来获取父类、当前游戏对象或者子类的组件然后直接调用。
13. 当游戏运行时,不要让生成的对象把你的层级面板弄的杂乱无章。把它们的父类设置为一个场景中的对象,就能够在游戏运行时方便找到所需对象。你可以使用一个空游戏对象,或者甚至是一个没有行为的单例类(稍后介绍)使得可以轻易从代码中访问。把对象命名为动态对象。
14.注意关于使用null作为一个合法值,并尽量避免。Null在检测错误代码时很有用。但是,如果你习惯了对null不管不问,错误代码可能会运行的很好并直到很久之后你才会发现bug。进一步来说,它能把自身在代码里传到更深层,因为每一层都pass过了null变量。我试着完全避免使用null作为一个合法的变量。
我更偏好的方式式不做任何null检查,当出现null问题时就让代码失败。当方法作为接口到更深层次,我将检查变量是否为null并抛出异常,而不是把它传递到其他方法中而出现失败。
一些情况下,某个值为null可以合法的,并且需要另作处理。这种情况下,添加一个注释来说明什么时候和为什么可以为null。
一个常见的情景经常被用作检视面板配置得值。用户可以指定一个值,如果没有指定就使用默认值。一个更好的方法是用一个Optional&T&类来包裹T值。(有点Nullable&T&的感觉)。你可以用一个特定的属性渲染器来渲染一个勾选框,并在勾选框选中的时候才能限制数值框。(不幸地是,你不能直接使用这个泛型类,而应该用特定的类型T来扩展类)
[C#] 纯文本查看 复制代码[Serializable]
public class Optional&T&
public bool useCustomV
然后你可以在代码中这么使用[C#] 纯文本查看 复制代码health = healthMax.useCustomValue ? healthMax.Value : DefaultHealthM15.如果使用协程,学着高效的使用它们。协程在解决一些问题时很强大。但是很难调试,你很容易就把代码弄得乱到你自己都不认识了。
你应该知道:
怎样并行执行协程怎样按序执行协程怎样从一个协程中创建一个新的协程怎样使用CustomYieldInstruction来创建自定义携程(http://blogs.unity3d.com//custom-coroutines/)。
[C#] 纯文本查看 复制代码//This is itself a coroutine
IEnumerator RunInSequence()
yield return StartCoroutine(Coroutine1());
yield return StartCoroutine(Coroutine2());
public void RunInParallel()
StartCoroutine(Coroutine1());
StartCoroutine(Coroutine1());
Coroutine WaitASecond()
return new WaitForSeconds(1);
16.使用扩展方法来处理共享同一个接口的组件。(现在GetComponent之类的方法也可以用于接口,这条建议多余了)。
下面的实现使用了typeof而不是其泛型版本。泛型版本不适用于接口,typeof适用。下面方法把它整洁的包裹在一个泛型放过发中。
[C#] 纯文本查看 复制代码public static TInterface GetInterfaceComponent(this Component thisComponent)
where TInterface : class
return thisComponent.GetComponent(typeof(TInterface)) as TI
17.用扩展方法来使得语法更方便一点。例如:
[C#] 纯文本查看 复制代码public static class TransformExtensions
public static void SetX(this Transform transform, float x)
Vector3 newPosition =
new Vector3(x, transform.position.y, transform.position.z);
transform.position = newP
18.使用带有防御的组件获取作为代替。有时通过RequiredComponent对组件的强制依赖要求不总能实现或达到期望,特别当你调用GetComponent得到其他对象的类上时。甚至当你真的使用了RequiredComponent ,这有利于表明你在代码里期望得到这个组件,并在没有挂载组件时报设置错。为了实现带有防御的组件,需要使用一个扩展方法,在组建没有找到时打印错误信息并抛出更有帮助的异常,如下:
[C#] 纯文本查看 复制代码public static T GetRequiredComponent(this GameObject obj) where T : MonoBehaviour
T component = obj.GetComponent();
if(component == null)
Debug.LogError(&Expected to find component of type &
+ typeof(T) + & but found none&, obj);
19.避免使用不同的风格来做同一件事情。在许多情况下有不止一种风格的方式来达到目的。在这种情况下,在整个项目过程中只选择一种来使用。原因如下:
一些风格不能协同工作。一种风格进行的设计不适合另一种。自始自终使用同种风格使得团队成员易于理解在干什么。结构和代码也便于理解,同时减少出错的可能。
风格组的例子:
协程和状态机。嵌套预设和链接的预设和万能预设)。 数据分离策略。2D游戏里使用精灵表示状态的方法。预设的结构。对象生成策略。定位对象的方式:通过[url=]类型、名字、标签、层、引用[/url]。对象分组的方式:通过类型、名字、标签、层、引用数组。调用其它组件方法的方式。查找对象组和对象自注册。控制执行顺序(使用Unity的执行顺序vs使用yield逻辑vs使用Awake/Start和Update/LateUpdate vs手动方法vs顺序无关架构)。在游戏中用鼠标选择对象/位置/目标:选择管理vs本地对象自管理(一个管理类或是每个可选对象都带一个脚本实现自管理)。场景切换时的数据留存:通过PlayerPrefs,或者通过加载新场景时不销毁对象(DontDestroyOnLoad)。动画组合的方式:混合,叠加和分层。输入处理:(central vs. local)
20.持有你自己的时间类来让实现暂停更容易。封装Time.DeltaTime和Time.TimeSinceLevelLoad来负责暂停和时间比例。使用它需要遵循一定的规则,但是却能让一些事情变得很容易,特别当对象运行在不同时钟下(比如界面动画和游戏动画)。
21.需要更新的自定义类不应该访问全局静态时间。而应该把帧间隔时间作为它们的Update方法的参数。这使得这些类在你实现上面所说的暂停系统时就可以直接使用,或者在你想要加速或者减速自定义类的行为的时候使用。
22.使用一个通用的结构来进行WWW调用。游戏里有很多和服务器的通信,有上那么几十个WWW调用很是正常。无论你用的Unity原声WWW类或是一个插件,为它们写个模板化的上层调用是有有好处的。
我通常定义一个Call方法(为每个Get和Post都定义一个),一个CallImpl协程和一个MakeHandler方法。基本说来,Call方法包装使用了MakeHandler方法来处理parser,on-success和 on-failure。它同时调用了CallImpl协程,这里面构建了一个URL并调用www直到完成后再调用传入handler。
粗略的样子如下:
[C#] 纯文本查看 复制代码public void Call(string call, Func parser, Action onSuccess, Action onFailure)
var handler = MakeHandler(parser, onSuccess, onFailure);
StartCoroutine(CallImpl(call, handler));
public IEnumerator CallImpl(string call, Func handler)
var www = new WWW(call);
handler(www);
public Func MakeHandler(Func parser, Action onSuccess, Action onFailure)
if(NoError(www))
var parsedResult = parser(www.text);
onSuccess(parsedResult);
onFailure(&error text&);
This has several benefits.这有几点好处。
避免写太多重复代码。在特定的地方处理特定的事情(如显示一个加载的UI或者处理某个的泛型错误)。
23.如果有很多文本,放到一个文件里。不要放到检视面板上编辑。这样可以不打开Unity编辑器就能够做出更改,特别不需要保存场景了。
24.如果你想要本地化(根据国家和区域选择不同语言字符串),不同语言的字符串要对应同一个位置。有很多方式来实现。一种方法是为每个字符串在一个文本类中定义一个公有字符串字段,例如默认设置为英语。其他语言继承它并用译后的语言内容来重置这些字段。
更复杂的技术(在文本内容很多或是语言种类很多时比较适合采用)将从数据表中读取,可以根据所选择语言选择正确的字符串。
25.决定怎样实现可检视字段,并标准化。有两种方式:把字段public公有化,或者把它们私有化并标记为 [Serializable]。后一种更“准确”但是麻烦点(而且这也不是Unity自身所推广的方法)。不论选择哪个,让它成为一个标准,这样你团队里的开发者就能知道怎样解释一个公共字段。
可检视字段是public。这个情景下,公共的意味着“变量在运行时由设计人员改变是安全的。避免在代码中设置变量的值”。 可检视字段是私有并标记为可序列化的。这种情况,公共的意味着“在代码中改变变量的值是安全的”(并因此你不应该看到也不应该在MonoBehaviours和ScriptableObjects里有任何[url=]public[/url]字段)。
26.对于组件来说,绝不把不需要在检视面板更改的变量公有化。否则它们就会被设计者修改,特别在不知道它有什么用的时候。在很少的情况下它是不可避免的(比如,一些其他编辑器脚本需要持有它)。这种情况下就可以使用HideInInspector特性来把它隐藏在检视面板。
27.使用属性绘制器来让字段更友好。属性绘制器能够用于自定义检视面板上的控件。这可以让控件更贴近数据原貌,并把特定的安全性保障做到位(比如限制变量的范围)。使用Header特性来对字段分组,Tooltip特性来为设计人员提供额外的文档说明。
28.相对于自定义编辑器更倾向于属性绘制器。属性绘制器实现每个字段类型,所需实现工作更少。一旦实现了一个类型就可以用于任何类中的这个类型,更具重用性。而自定义编辑器实现的单位是每个MonoBehaviour脚本,所以要做更多的工作且重用性较低。
29.默认MonoBehaviours为密封类。一般来说Unity的MonoBehaviours继承性并不是很友好。
Unity调用如Start和Update这样的消息方法的方式在子类中有点小棘手。一不小心就调用错误了,或者是忘记调用了基类方法。当使用自定义编辑器时,你通常需要为编辑器复制继承层次结构。无论是谁想要扩展你的类就需要提供他们自己的编辑器或者实现所以你提供的方法。
在继承方法被调用的情况下,尽可能避免提供任何Unity消息方法。如果不能避免,不要把它们定义为virtual。如果必须是virtual的话,你可以定义一个空的虚函数,在消息方法中调用,通过在子类中覆写这个虚函数来增加扩展内容。
[C#] 纯文本查看 复制代码public class MyBaseClass
public sealed void Update()
CustomUpdate();
... // This class's update
//Called before this class does its own update
//Override to hook in your own update code.
virtual public void CustomUpdate(){};
public class Child : MyBaseClass
override public void CustomUpdate()
//Do custom stuff
这防止了类意外覆写你的代码,但是仍然可以挂到Unity的消息方法下。我不喜欢这种模式的原因之一就是顺序问题。上面例子的子类也许可能想要的是紧接着父类方法调用完成后才调用自身的更新。
30.分离游戏逻辑和界面。界面组件在使用时一般来说不应该知道任何游戏相关的信息。显示所给的数据,在用户和它们交互的时候找出订阅的事件。界面组件不应该去做游戏逻辑。它们可以过滤输入来确保有效输入,但主要的规则处理在别的地方进行。在许多益智游戏里,棋子是一个界面扩展的,本身并不包含规则(例如,国际象棋的棋子不应该计算自身的移动是否合法)。
类似地,输入也要和输入所执行的逻辑相分离。使用一个输入控制器来通知你的角色去移动的意图消息;角色自己处理是否真的要移动。
下面是一个摘取出的UI脚本组件,它(WeaponButton)允许用户从一个选择列表里选择武器。这些类所知道的跟游戏相关的就只有Weapon类(而且只是因为Weapon有它的容器需要显示出来的有用的资源数据)。同时游戏对容器也一无所知;它只需要完成OnWeaponSelect事件的注册。
[C#] 纯文本查看 复制代码//Game
public WeaponSelector : MonoBehaviour
public event Action OnWeaponSelect { }
//the GameManager can register for this event
public void OnInit(List
foreach(var weapon in weapons)
var button = ... //Instantiates a child button and add it to the hierarchy
buttonOnInit(weapon, () =& OnSelect(weapon));
// child button displays the option,
// and sends a click-back to this component
public void OnSelect(Weapon weapon)
if(OnWepaonSelect != null) OnWeponSelect(weapon);
public class WeaponButton : MonoBehaviour
private Action&& onC
public void OnInit(Weapon weapon, Action onClick)
... //set the sprite and text from weapon
this.onClick = onC
public void OnClick() //Link this method in as the OnClick of the UI Button component
Assert.IsTrue(onClick != null);
//Should not happen
onClick();
配置变量是在检视面板中通过属性来定义对象的可调变量。比如,最大生命值。状态变量是决定你对象当前状态的变量,如果游戏支持保存的话就需要保存。例如,当前生命值。记录变量用于速度,方便或者过渡状态。他们总是由状态变量所决定。比如,之前生命值。
通过分离这些种类的变量,可以很容易知道能去改变什么,需要保存什么,在网络上收发什么,并允许你在一定程度上来操作。下面是这种设置的一个简单例子。
[C#] 纯文本查看 复制代码public class Player
[Serializable]
public class PlayerConfigurationData
public float maxH
[Serializable]
public class PlayerStateData
public PlayerConfigurationD
private PlayerState stateD
//book keeping
private float previousH
public float Health
public get { return stateData. }
private set { stateData.health = }
32.避免使用公共的序号耦合的多个数组。例如,不要定义一组武器,一组子弹,一组粒子特效,不然就会像下面这样:
[C#] 纯文本查看 复制代码public void SelectWeapon(int index)
currentWeaponIndex =
Player.SwitchWeapon(weapons[currentWeapon]);
public void Shoot()
Fire(bullets[currentWeapon]);
FireParticles(particles[currentWeapon]);
这里倒不是代码出问题,而是在检视面板设置时容易出错。
相对而言,定义一个封装了三个变量的类,然后用它构建数组:
[C#] 纯文本查看 复制代码[Serializable]
public class Weapon
public GameO
public ParticleS
代码不仅整洁,而且最重要的是,在检视面板中设置这些数据很难出错。
33.除非是用作序列,避免使用结构数组。例如,玩家可能有三种攻击模式。每种使用当前的武器,但是生成不同的子弹和不同的行为。
你可能想要把三种子弹存到数组里,然后看起来逻辑如下:
[C#] 纯文本查看 复制代码public void FireAttack()
/// behaviour
Fire(bullets[0]);
public void IceAttack()
/// behaviour
Fire(bullets[1]);
public void WindAttack()
/// behaviour
Fire(bullets[2]);
枚举数能让它们再代码里看起来更好看点
[C#] 纯文本查看 复制代码public void WindAttack()
/// behaviour
Fire(bullets[WeaponType.Wind]);
。。。但是在检视面板上然并卵
最好使用不同的变量,变量名有助于显示放入的内容。使用类来让它变得整洁:
[C#] 纯文本查看 复制代码[Serializable]
public class Bullets
public Bullet fireB
public Bullet iceB
public Bullet windB
这假设了没有其他的Fire,Ice和Wind子弹。
34.在可序列化类里对数据进行分组可以让事物在检视面板中变得整洁。一些实体可能有许多可调变量。如果可调变量很多的话,在检视面板中找到正确的变量如同噩梦一般。为了让事情变得简单点,跟着下面的步骤:
为变量分组定义单独的类。是类公有化和序列化。在主类中,把之前定义的类都定义为公有化变量。不要再Awake或者Start里初始化这些变量;因为Unity会处理这些序列化变量。你仍旧可以通过在定义时分配值来指定默认值。
这将把变量分组到检视面板的可折叠单元,便于管理。
[C#] 纯文本查看 复制代码[Serializable]
public class MovementProperties //Not a MonoBehaviour!
public float movementS
public float turnSpeed = 1; //default provided
public class HealthProperties //Not a MonoBehaviour!
public float maxH
public float regenerationR
public class Player : MonoBehaviour
public MovementProperties movementP
public HealthPorperties healthP
35.把不是MonoBehaviours的类可序列化,哪怕它们不是用作公有字段。这使得你可以在检视面板的debug模式下浏览类的字段。对嵌套类也有用(私有或者公有)。
36.避免在代码里改变那些在检视面板中可调量。检视面板中的可调量就是一个配置量,应该被看作是一个运行时常量,而不能又被用作一个状态变量。遵循这种做法,可以轻易写一个方法把组件状态重置为初始状态,变量的作用也更加清晰。
[C#] 纯文本查看 复制代码public class Actor : MonoBehaviour
public float initialHealth = 100;
private float currentH
public void Start()
ResetState();
private void Respawn()
ResetState();
private void ResetState()
currentHealth = initialH
模式& && && && &&&& && && && && && && && &&&
模式是解决常见问题的标准方法。Bob Nystrom的Game Programming Patterns(支持在线免费阅读)是一本好书,可以了解模式怎样适用于游戏编程中出现的问题。Unity自身用了很多这些模式:Instantiate就是原型模式的例子;MonoBehaviours是模板模式的一个版本,UI和动画使用了观察者模式,新的动画引擎使用了状态机模式。
使用这些模式的技巧是针对Unity的。
37.使用单例模式。下面的类将让任何继承自它的类自动变成单例:[C#] 纯文本查看 复制代码
public class Singleton : MonoBehaviour where T : MonoBehaviour
protected static T
//Returns the instance of this singleton.
public static T Instance
if(instance == null)
instance = (T) FindObjectOfType(typeof(T));
if (instance == null)
Debug.LogError(&An instance of & + typeof(T) +
& is needed in the scene, but there is none.&);
单例有助于管理,比如ParticleManager 或AudioManager 或者GUIManager。
(许多程序员都提醒不要含糊的把类命名为XManager,因为命名不好而且设计了很多不相关的任务。总的来说,我赞同这个观点。但是,但是每个游戏里只有少数几个管理类,并且在每个游戏里都做相同的工作,所以这些类实际上是一种风格习惯了)
避免为一个只有一个实例的非管理的预制的实例来使用单例,如Player。不遵循这个原则的话就会让继承层次结构更复杂,让某些类型的变化变得更困难。最好在你的GameManager里保存这些唯一预制实例的引用(或者其他合适的万能类里)为经常从外部访问的类中的方法和属性定义成静态公有的。你就可以写GameManager.Player来代替GameManager.Instance.player。
像在其他技巧中说明的一样,单例也能用于创建默认的生成点和对象,可以在场景之间加载中持久存在,从而持续追踪全局数据。38.使用状态机来在不同状态下得到不同的行为或者在状态转换中执行代码。一个轻量的状态机有一些状态,每个状态允许你在进入、离开执行指定的功能,并且具备update功能。这能让代码变得整洁和减少出错的可能。在什么情况下选择状态机有好处呢?如果你的Update方法里的代码有if或者switch结构来改变update里的工作,或者有诸如hasShownGameOverMessage的这种变量,这就是一些标志。
[C#] 纯文本查看 复制代码public void Update()
if(health &= 0)
if(!hasShownGameOverMessage)
ShowGameOverMessage();
hasShownGameOverMessage = //Respawning resets this to false
HandleInput();
状态一旦更多,这种类型的代码就变得非常乱;状态机可以让它变得整洁的多。
39.使用UnityEvent类型的字段在检视面板中设置观察者模式。就像Buttton的事件UI接口一样,UnityEvent类让你可以在检视面板接入一个有4个参数的方法。这在处理输入时特别有用。
40.用观察者模式来检测字段值的变化。只在变量改变时执行代码的问题频繁出现在游戏中。我们设计了一个通用的解决方案,在泛型类中你可以为值的变化注册事件。下面是生命值的例子以及它怎样构建的:
[C#] 纯文本查看 复制代码/*ObservedValue*/ health = new ObservedValue(100);
health.OnValueChanged += () =& { if(health.Value &= 0) Die(); };
现在你可以在任何地方改变它,而不用再每个地方都去检查它的值,如下:[C#] 纯文本查看 复制代码if(hit) health.Value -= 10;
只要health降到0或以下,Die方法就被调用。想要进一步讨论和实现,参见http://gamelogic.co.za//the-new-class-in-extensions-observedvalue-what-is-it-for-and-how-to-use-it/。
41.在预制上使用Actor设计模式。(这不是一个“标准”的设计模式。基本思想来自Kieran Lord的这篇描述https://gamedevacademy.org/lessons-learned-in-unity-after-5-years/)
Actor是预制上的主要组件;通常这个组件就提供了预制的“身份”,并且提供了一个更高级的代码以供常用的交互。Actor使用了在同一个对象(有时在子物体上)上使用其他组件协同来完成工作。
如果你在unity里的菜单里创建了一个按钮对象,就创建了一个带有Sprite和Button组件(并且添加一个Text对象到子物体上)的游戏对象。这种情况下,Button就是Actor组建。相似的,主摄像机通常有几个组件(GUI Layer,Flare Layer,Audio Listener)额外添加到Camera组件所在的对象上。Camera就是Actor。
一个actor可能需要其他组件才能正确工作。你可以通过在你的actor组件上使用下面的特性来让预制变得更健壮和有用。
· 使用RequiredComponent 来表明actor所在游戏对象上所需的全部组件。(你的actor就能一直安全的调用GetComponent方法,而无需检查是否返回值为空。)· 使用DisallowMultipleComponent 来防止同一种组件的多个实例被添加。你的actor就可以调用GetComponent组建而无需担心组件重复添加的问题了。· 如过你的actor有子物体就使用SelectionBase 。这样在场景视图中选择这个组件所在物体就很容易。
[C#] 纯文本查看 复制代码[RequiredComponent(typeof(HelperComponent))]
[DisallowMultipleComponent]
[SelectionBase]
public class Actor : MonoBehaviour
42.为随机数和模式化的数据流使用生成器。(这不是一个标准模式,但是确实极其有用。)
生成器类似于随机数生成器:是一个带有Next方法的对象,当调用Next方法时候得到一个新特定类型的项。生成器能在构造时被操作来产生很多种模式或者不同种类的随机。它们很有用,因为生成一个新项的逻辑和你调用的地方是分离的,所以代码很干净。
这里是一些例子
[C#] 纯文本查看 复制代码var generator = Generator
.RamdomUniformInt(500)
.Select(x =& 2*x); //Generates random even numbers between 0 and 998
var generator = Generator
.RandomUniformInt(1000)
.Where(n =& n % 2 == 0); //Same as above
var generator = Generator
.Iterate(0, 0, (m, n) =& m + n); //Fibonacci numbers
var generator = Generator
.RandomUniformInt(2)
.Select(n =& 2*n - 1)
.Aggregate((m, n) =& m + n); //Random walk using steps of 1 or -1 one randomly
var generator = Generator
.Iterate(0, Generator.RandomUniformInt(4), (m, n) =& m + n - 1)
.Where(n &= 0); //A random sequence that increases on average
我们用生成器来生成障碍,改变背景色,程序生成音乐,生成单词游戏中的字母序列等等。生成器还可以很好的来控制协程来让它重复在一个非固定间隔下,如下结构:
[C#] 纯文本查看 复制代码while (true)
//Do stuff
yield return new WaitForSeconds(timeIntervalGenerator.Next());
这里可以找到更多关于生成器的东西。(http://gamelogic.co.za//new-generators-designed-from-scratch-for-use-in-procedural-content/)
预制和脚本化对象
43.啥都用预制。你场景中唯一一个不需要成为预制(或者预制的一部分)的游戏对象应该是文件夹。就算是一个单独的对象只使用了一次都该变成预制。这就可以很容易对对象做出改变而无需切换场景.
<font color="#.预制关联预;而不要把实例关联实例。关联的预制可以保证拖预制到场景中时关联的预制会被保留;而实例就不会。尽可能关联预制可以减少场景设置和切换场景的需求。
尽量自动建立实例之间的关联。如果你需要关联实例,请程序化建立关联。例如,Player预制的实例可以在开始时注册自身到GameManager中,或者GameManager能在一开始查找到Player预制的实例。
45.在你想要往预制的根物体上加其他脚本时,就不要把网格放到根物体上。当你为网格物体建立预制时,把网格物体放到一个空游戏对象下,并把空游戏对象作为根物体。把脚本都放到根物体上,而不是放到有网格的节点上。这样就方便你用一个网格对象替换另一个,而不会丢失检视面板的任何设置值。
46.为共享的配置数据使用脚本化对象,而不是预制。
如果这样做了:
场景更小你就不会误修改单独的场景(在预制实例上就可能会出错)。
47.为关卡数据使用脚本化对象。关卡数据经常被存储在XML或者JSON里,但是用脚本化对象代替的话有一些好处:
可以在编辑器下编辑。更容易验证数据和对没技术的设计人员很友好。进一步说,你可以使用自定义编辑器来让它更好编辑。你不需要担心数据的读写和解析。更容易分离和嵌套,管理生成的资源,并且从建造的模块中构建场景而非混乱的配置。
48.使用脚本化对象来配置检视面板的行为。脚本画对象通常用来和配置数据相联系,但是也可以用“方法”作为数据。(ps:http://blog.csdn.NET/candycat1992/article/details/)
请考虑这种情况,你有一个Enemy类,每个敌人都有一堆超能力。你可以把它们实现为普通类并在Enemy类中创建个列表。。。但如果没有自定义编辑器的话就不能在检视面板中设置不同超能力(每个都有自己的属性)的列表。但如果你把这些超能力变成资源(即实现为脚本化对象)就可以了。
如下所示:
[C#] 纯文本查看 复制代码public class Enemy : MonoBehaviour
public SuperPower superP
public UseRandomPower()
superPowers.RandomItem().UsePower(this);
public class BasePower : ScriptableObject
virtual void UsePower(Enemy self)
[CreateAssetMenu(&BlowFire&, &Blow Fire&)
public class BlowFire : SuperPower
override public void UsePower(Enemy self)
///program blowing fire here
遵循这个模式需要注意一下几点:
把脚本化对象abstract是不可靠的。而应该使用一个实体基类,并在想要抽象的方法下抛出NotImplementedExceptions 未实现方法异常。你也可以定义一个抽象属性并且标记需要抽象自它的类和方法。泛型脚本化对象不能被序列化。但是,你能使用泛型基类并只序列化指定了所有泛型的子类。
49.使用脚本化对象来区分预制。如果两个对象的配置只有某些属性不同,通常在场景中放两个实例并调整实例上的属性。通常更好的做法是让可以用来区分两种东西的属性类变成一个单独的脚本化对象类。
这样灵活性更好:
你可以继承区分类来给不同种类对象更多特殊的属性。场景设置更安全(仅仅选择合适的脚本化对象,而不是调整所有的属性来让对象变成想要的种类)。很容易在运行时通过代码来操作这些对象。如果你有两种类型对象的多个实例时,它们的属性和更改始终一致。
这里是个例子。
[C#] 纯文本查看 复制代码[CreateAssetMenu(&HealthProperties.asset&, &Health Properties&)]
public class HealthProperties : ScriptableObject
public float maxH
public float resotrationR
public class Actor : MonoBehaviour
public HealthProperties healthP
如果特殊属性的数量很多,你可能想要把特殊的属性定义为一个普通类,在脚本化对象中用列表引用它们,再在把脚本话对象链接到合适的地方来持有(比如你的GameManager)。一点必要的粘合来让它安全快速和便捷;最简单的代码如下:
[C#] 纯文本查看 复制代码public enum ActorType
Vampire, Wherewolf
[Serializable]
public class HealthProperties
public ActorT
public float maxH
public float restorationR
[CreateAssetMenu(&ActorSpecialization.asset&, &Actor Specialization&)]
public class ActorSpecialization : ScriptableObject
public List healthP
public this[ActorType]
get { return healthProperties.First(p =& p.type == type); } //Unsafe version!
public class GameManager : Singleton
public ActorSpecialization actorS
public class Actor : MonoBehaviour
public ActorT
//Example usage
public Regenerate()
+= GameManager.Instance.actorSpecialization[type].resotrationR
50.使用CreateAssetMenu 特性来添加自动化创建脚本化对象到Asset/Create菜单。
51.学习怎样有效使用Unity的调试工具。
添加上下文对象到Debug.Log语句来看生成log的地方。使用Debug.Break在暂停编辑器下运行的游戏(这很有用,比如,你想要检测错误情况发生时的那一帧的组件的属性)。使用Debug.DrawRay和Debug.DrawLine功能来可视化调式(比如DrawRay在调试为什么射线没有碰撞时很有用)。使用Gizmos做可视化调试。你也可以在monobehavours之外使用DrawGizmo特性来提供gizmo渲染器。使用检视面板的debug(可以在Unity运行时使用检视面板来看私有字段的值)。
52.学习怎样有效使用你的IDE调试器。参见例子https://unity3d.com/cn/learn/tutorials/topics/scripting/debugging-unity-games-visual-studio
53.使用随时间绘制的值的图表的可视化调试器。这在测试物理、动画和其他动态过程时极其有用,特别一些小故障。那你能在图像里看到故障,同事能看到其他值的变化。可视化检查让怪异的行为看起来明显,比如值改变太频繁或者没有明显原因的偏移。我们使用Monitor Components*(assetstore里的),有很多版本。
54.使用改进的控制台调试。使用编辑器扩展让你能根据分类来把输出显示不同颜色,并可以根据分类过滤输出。我们使用Editor Conssole Pro(AssetStore),有多个版本。
55.使用UNity的测试工具,特别是测试算法和数学上的代码的时候。参看Unity Test Tools 教程的例子或者Unit testing at the speed of light with Unity Test Tools文章。
56.使用Unity的test tools插件来进行快捷测试。Unity的测试工具不仅仅适合常规的测试。还能被用来做便捷测试,可以直接在编辑器运行而无需运行场景。
57.实现截屏的快捷键。许多bug都是可视的,当截图时更容易检查。理想的系统是在PlayerPrefs里持有一个计数器来使得后面的截图不被覆盖。截图应该被保存在工程外部来防止有人意外的把它们提交到版本库中。
58.实现打印显示重要变量的快捷键。这样在游戏发生一些异常时,你可以很容易记录一些信息来进行检查。当然具体什么变量取决于游戏。可以遵循一些发生在游戏里的典型bug。比如玩家和敌人的位置,或者AI角色的“思考状态”(比如它想走的路径)。
59.实现调试选项来让测试更容易。一些例子:l 解锁所有物品l 禁用敌人l 禁用GUIl 玩家无敌l 禁用所有游戏设置小心不要意外提交了调试选项(到版本库);这样可能让你队里的开发者在不知道的情况下困惑。
60.定义调试快捷键常量,并集中到一个地方。调试键不能正常地(或方便地)像其他游戏输入一样处理在同一个地方。为了避免快捷键冲突,定义(调试键)常量在中心位置。另一种做法就是把所有键都在一个地方处理,而不管它是不是一个调试功能.(缺点是这个类需要额外的对象引用)。
61.当程序生成网格时在顶点绘制或生成小球。这有助于在你弄混三角面和UV之前,确保顶点在它们该在的地方并且网格也是正确的大小。
62.就性能原因要警惕的关于设计和构建的一般建议。
这些建议经常基于经验而不支持测试。有时建议支持测试但是会测试失败。有时建议支持正确的测试,但是不是在实际应用或者在不同场景。(例如,很容易说明使用队列是比泛型列表要快的。但是,就真实的游戏中而言这种差异几乎总是微不足道。相似的,如果测试用在你的目标设备之外的硬件上,这种测试结果对你来说就没有意义。)有时建议是合理的,但是过期了。有时建议有用,但是,需要权衡。发布慢的游戏有时是优于发布快的游戏。重度优化的游戏更可能包含复杂的代码,会延迟发布。
记住性能建议有助于帮你更快的追踪到实际问题的源头,使用下面的处理概述。
63.从一开始就定期在目标设备上测试。设备有很不同的性能特征;不要惊讶。越早知道问题就越能有效的定位它们。
64.学习怎样有效的使用一个profiler(分析器)来追踪性能问题的原因。·
如果没有接触过分析器,参见Introduction to the Profiler.(https://unity3d.com/learn/tutorials/topics/interface-essentials/introduction-profiler)学习怎样定义你自己的帧(使用Profiler.BeginFrame 和Profiler.EndFrame)。学习使用特定平台的分析器,比如内置的IOS分析器。学习在编译的平台上使用分析写入文件(https://docs.unity3d.com/ScriptReference/Profiler-logFile.html)并在分析器上显示文件数据(https://docs.unity3d.com/ScriptReference/Profiler.AddFramesFromFile.html)。
65.必要时使用自定义的分析器来更精确的分析。有时,Unity的分析器提供一个清晰的图片来显示发生了什么;它可能会耗尽分析帧,或者深入分析会减慢游戏使得测试没有意义。我们使用的是我们内部的分析器,但你可以在AssetStore上找到替代方案。
66.评估性能增强的影响。当你为提升性能做了改变时,评估它以保障改变确实增强了性能。如果改变微乎其微,就撤销改变。
67.不要为了更好的性能来写不易读的代码。除非:在用分析器识别源头的时候会有问题,在改变之后你检测到性能的提升,并且这个提升相对于你失去的可维护性是足够高的。你知道你在做什么。
命名标准和文件夹结构
68.遵循一个文件命名预定和文件夹结构。固定的明明和文件夹结构更容易找到东西和表明它们的作用。你可能想要创建你自己的命名约定和文件夹结构。下面是一个范例:
命名的一般准则
是什么就叫什么。bird就叫Bird。选择易读和易记的名字。如果你想制作一个玛雅人的游戏,不要命名你的场景为QuetzalcoatisReturn。一致性。当你选了一个名字后,就坚持用它。不要在一个地方叫buttonHolder而另一个地方叫buttonContainer。使用帕斯卡Pascal命名法,如:ComplicatedVerySpecificObject。不用使用空格,下划线或者连字符,有一个例外(看下面命名同一个事物的不同方面)。不要使用版本号或者词语来显示他们的进展(WIP,final)。不用使用缩写:DVamp@W应该携程DarkVampire@Walk。使用设计文档中的术语:如果文档中把死亡动画叫做Die,那么就用useDarkVampire@Die,而不是 DarkVampire@Death。让具体描述保持在左边:DarkVampire而不是VampireDark;PauseButton而不是ButtonPaused。例如这样就容易在检视面板找到暂停按钮,而不是所有按钮都以Button开头。[一些人更喜欢另一种方式,因为这让分组更明显。名字不是用来分组到文件夹。名字是用来区分同种对象,从而可以被快速可靠定位]。一些名字构成序列。在这些名字里使用数字,例如, PathNode0, PathNode1.总是从0开始,而非1.不要给不构成序列的东西使用序号。例如,Bird0, Bird1, Bird2 应该是Flamingo, Eagle, Swallow。用双下划线给临时对象加前缀__Player_Backup。
命名同一个事物的不同方面
在核心名和描述“方面”之前使用下划线。例如:
GUI按钮状态EnterButton_Active, EnterButton_Inactive 纹理DarkVampire_Diffuse, DarkVampire_Normalmap天空盒JungleSky_Top, JungleSky_NorthLOD组DarkVampire_LOD0, DarkVampire_LOD1
不要用这个约定来区别不同种类的物体,比如Rock_Small, Rock_Large应该是SmallRock, LargeRock。
你场景,工程文件夹和脚本文件夹的组织应该遵循相似的模式。这里是一些删减的例子方便你开始。文件夹结构MyGame& &Helper& && &Design& && &Scratchpad& &Materials& &Meshes& && &Actors& && && &DarkVampire& && && &LightVampire& && && &...& && &Structures& && && &Buildings& && && &...& && &Props& && && &Plants& && && &...& && &...& &Resources& && &Actors& && &Items& && &...& &Prefabs& && &Actors& && &Items& && &...& &Scenes& && &Menus& && &Levels& &Scripts& &Tests& &Textures& && &UI& && &Effects& && &...& &UIMyLibray& &...PluginsSomeOtherAsset1SomeOtherAsset2...场景结构MainDebugManagersCamerasLightsUI& &Canvas& && &HUD& && &PauseMenu& && &...World& &Ground& &Props& &Structures& &...Gameplay& &Actors& &Items& &...Dynamic Objects脚本文件夹结构DebugGameplay& &Actors& &Items& &...FrameworkGraphicsUI...原文链接:原文作者:Herman Tulleken
每日推荐:
61386/1500排名<font color="#FF昨日变化1主题帖子积分
蛮牛粉丝, 积分 1386, 距离下一级还需 114 积分
蛮牛粉丝, 积分 1386, 距离下一级还需 114 积分
在线时间339 小时
好长啊,看完还有好好消化吸收。。。
每日推荐:
3176/300排名<font color="#FF昨日变化4主题帖子积分
偶尔光临, 积分 176, 距离下一级还需 124 积分
偶尔光临, 积分 176, 距离下一级还需 124 积分
在线时间47 小时
这个我必须留名,回家继续消化
每日推荐:
135/50排名<font color="#FF昨日变化12主题帖子积分
注册看看, 积分 35, 距离下一级还需 15 积分
注册看看, 积分 35, 距离下一级还需 15 积分
在线时间15 小时
谢谢楼主的分享,具有重要的参考价值!
需要反复学习、消化、吸收、应用
每日推荐:
经过游戏蛮牛认证的蛮牛小翻译

我要回帖

更多关于 调用父类的方法 的文章

 

随机推荐