2016-10-08 18:06:30 xzr1526 阅读数 1049

通过直接修改unity的脚本模版(改成自己定义的)
每次创建的新脚本就是你自己定义的模板
修改文件在:
你本机安装unity3d的路径\Editor\Data\Resources\ScriptTemplates
在ScriptTemplates目录下找到你想要修改的脚本种类直接打开修改就可以了

2016-10-30 21:45:44 u010832643 阅读数 987

原文地址:Unity3D中脚本的执行顺序和编译顺序

在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行。与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题。

事件函数的执行顺序

先说一下执行顺序吧。 
官方给出的脚本中事件函数的执行顺序如下图: 
exeOrder

我们可以做一个小实验来测试一下: 
在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示,然后按照顺序将脚本绑定到对应的游戏对象上: 
struct 

三条脚本的代码完全一样,只是做了一点名称上的区分: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;
using System.Collections;
public class Scring0 : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Script0=======Awake");
    }
 
    bool isUpdate = false;
    void Update()
    {
        if(!isUpdate)
        {
            Debug.Log("Script0=======Update");
            isUpdate =true;
        }
    }
 
    bool isLateUpdate =false;
    void LateUpdate()
    {
        if(!isLateUpdate)
        {
            Debug.Log("Script0=======LateUpdate");
            isLateUpdate =true;
        }
    }
}

播放游戏,看看它们的执行顺序。如下图所示,Awake、Update、LateUpdate,无论运行游戏多少次,它们的执行顺序是完全一样的。 
exeOrderInstance01



接着我们再做一个测试,把Script0的Update方法注释掉!! 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
  
    void Awake ()
    {
        Debug.Log("Script0========Awake");
    }
  
//  bool isUpdate = false;
//  void Update ()
//  {
//      if(!isUpdate)
//      {
//          Debug.Log("Script0 ======== Update");
//          isUpdate = true;
//      }
//  }
  
    bool isLateUpdate=false;
    void LateUpdate()
    {
        if(!isLateUpdate)
        {
            Debug.Log("Script0========LateUpdate");
            isLateUpdate=true;
        }
    }
}

再次运行游戏,看看它的结果。脚本的执行顺序和以前完全一样,Script0即便删除掉了Update方法,但是它也不会直接执行LateUpdate方法,而是等待Script1和Script2中的Update方法都执行完毕以后,再去执行所有的LateUpdate方法。 
exeOrderInstance02 



通过这两个例子,我们就可以很清楚地断定,Unity后台是如何执行脚本的了。每个脚本的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在后台都会被汇总到一起: 

1
2
3
4
5
6
7
后台的Awake()
{
    // 这里暂时按照上图中的脚本执行顺序,后面会谈到其实可以自定义该顺序的
    脚本2中的Awake();
    脚本1中的Awake();
    脚本0中的Awake();
}

后台的方法Awake、Update、LateUpdate等等,都是按照顺序,等所有游戏对象上脚本中的Awake执行完毕之后,再去执行Start、Update、LateUpdate等方法的。 

1
2
3
4
5
6
7
后台的Update()
{
    // 这里暂时按照上图中的脚本执行顺序,后面会谈到其实可以自定义该顺序的
    脚本2中的Update();
    脚本1中的Update();
    脚本0中的Update();
}

脚本的执行顺序

然后我们来看看这样一种情况:在脚本0的Awake方法中创建一个立方体对象,然后在脚本2的Awake方法中去获取这个立方体对象。代码如下: 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Script0.cs
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
    void Awake ()
    {
        GameObject.CreatePrimitive(PrimitiveType.Cube);
    }
}
 
// Script2.cs
using UnityEngine;
using System.Collections;
public class Script2 : MonoBehaviour
{
    void Awake ()
    {
        GameObject go = GameObject.Find("Cube");
        Debug.Log(go.name);
    }
}

如果脚本的执行顺序是先执行Script0,然后再执行Script2,那么Script2中的Awake就可以正确地获取到该立方体对象;可是如果脚本的执行顺序是先执行Script2,然后是Script0,那么Script2肯定会报空指针错误的。

那么实际项目中的脚本会非常多,它们的先后执行顺序我们谁也不知道(有人说是按照栈结构来执行的,即后绑定到游戏对象上的脚本先执行。这一点可以从上面的例子中得到,但官方并没有这么说,还得进一步深入研究)。但一般的,建议在Awake方法中创建游戏对象或Resources.Load(Prefab)对象,然后在Start方法中去获取游戏对象或者组件,因为事件函数的执行顺序是固定的,这样就可以确保万无一失了。 
另外,Unity也提供了一个方法来设置脚本的执行顺序,在Edit -> Project Settings -> Script Execution Order菜单项中,可以在Inspector面板中看到如下图所示: 
Inspector

点击右下角的"+"将弹出下拉窗口,包括游戏中的所有脚本。脚本添加完毕后,可以用鼠标拖动脚本来为脚本排序,脚本名后面的数字也越小,脚本越靠上,也就越先执行。其中的Default Time表示没有设置脚本的执行顺序的那些脚本的执行顺序。 
Inspector2

按照上面这张图的设置,我们再来看一下控制台的输出结果,来确认一下我们的设置是否起作用(注意:把Script0脚本中的Update方法取消注释): 
exeOrderInstance03

补充说明:

下面我们针对每一个方法进行详细的说明(摘自http://www.unitymanual.com/thread-14461-1-1.html):

  1.Awake:用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如GameObject.FindWithTag()这样的函数搜索它们。每个游戏物体上的Awake以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息Awake总是在Start之前被调用。它不能用来执行协同程序。

  2.Start:仅在Update函数第一次被调用前调用。Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。在所有脚本实例中,Start函数总是在Awake函数之后调用。
  3.FixedUpdate:固定帧更新,在Unity导航菜单栏中,点击“Edit”-->“Project Setting”-->“Time”菜单项后,右侧的Inspector视图将弹出时间管理器,其中“Fixed Timestep”选项用于设置FixedUpdate()的更新频率,更新频率默认为0.02s。
4.Update:正常帧更新,用于更新逻辑。每一帧都执行,处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)FixedUpdate,每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。
  5.LateUpdate:在所有Update函数调用后被调用,和fixedupdate一样都是每一帧都被调用执行,这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。LateUpdate,在每帧Update执行完毕调用,他是在所有update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有update操作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。

  6.OnGUI:在渲染和处理GUI事件时调用。比如:你画一个button或label时常常用到它。这意味着OnGUI也是每帧执行一次。

  7.Reset:在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个默认值。

  8.OnDisable:当物体被销毁时 OnDisable将被调用,并且可用于任意清理代码。脚本被卸载时,OnDisable将被调用,OnEnable在脚本被载入后调用。注意: OnDisable不能用于协同程序。

  9.OnDestroy:当MonoBehaviour将被销毁时,这个函数被调用。OnDestroy只会在预先已经被激活的游戏物体上被调用。注意:OnDestroy也不能用于协同程序。





脚本的编译顺序

关于脚本的编译顺序很是头疼,官方的说法有点模糊,请看官方的解释: 
phaseFour

由于脚本的编译顺序会涉及到特殊文件夹,比如上面提到的Plugins、Editor还有Standard Assets等标准的资源文件夹,所以脚本的放置位置就非常重要了。下面用一个例子来说明不同文件夹中的脚本的编译顺序:

fileStruct

实际上,如果你细心的话会发现,如果在你的项目中建立如上图所示的文件夹层次结构时,编译项目之后会在项目文件夹中生成一些文件名中包含Editor、firstpass这些字样的项目文件。比如按照上图的文件夹结构,我们打开项目文件夹来看一下产生的项目文件是什么样的? 
fileStruct01

下面就来详细探讨一下这些个字样是什么意思?它们与脚本的编译顺序有着怎样的联系?


1、首先从脚本语言类型来看,Unity3d支持3种脚本语言,都会被编译成CLI的DLL

如果项目中包含有C#脚本,那么Unity3d会产生以Assembly-CSharp为前缀的工程,名字中包含”vs”的是产生给Vistual Studio使用的,不包含”vs”的是产生给MonoDevelop使用的。 

项目中的脚本语言 工程前缀 工程后缀
C# Assembly-CSharp csproj
UnityScript Assembly-UnityScript unityproj
Boo Assembly-Boo booproj

如果项目中这三种脚本都存在,那么Unity将会生成3种前缀类型的工程。

2、对于每一种脚本语言,根据脚本放置的位置(其实也部分根据脚本的作用,比如编辑器扩展脚本,就必须放在Editor文件夹下),Unity会生成4中后缀的工程。其中的firstpass表示先编译,Editor表示放在Editor文件夹下的脚本。

在上面的示例中,我们得到了两套项目工程文件:分别被Virtual Studio和MonoDevelop使用(后缀包不包含vs),为简单起见,我们只分析vs项目。得到的文件列表如下: 
Assembly-CSharp-filepass-vs.csproj 
Assembly-CSharp-Editor-filepass-vs.csproj 
Assembly-CSharp-vs.csproj 
Assembly-CSharp-Editor-vs.csproj

根据官方的解释,它们的编译顺序如下: 
(1)所有在Standard Assets、Pro Standard Assets或者Plugins文件夹中的脚本会产生一个Assembly-CSharp-filepass-vs.csproj文件,并且先编译; 
(2)所有在Standard Assets/Editor、Pro Standard Assets/Editor或者Plugins/Editor文件夹中的脚本产生Assembly-CSharp-Editor-filepass-vs.csproj工程文件,接着编译; 
(3)所有在Assets/Editor外面的,并且不在(1),(2)中的脚本文件(一般这些脚本就是我们自己写的非编辑器扩展脚本)会产生Assembly-CSharp-vs.csproj工程文件,被编译; 
(4)所有在Assets/Editor中的脚本产生一个Assembly-CSharp-Editor-vs.csproj工程文件,被编译。

之所以按照这样建立工程并按此顺序编译,也是因为DLL间存在的依赖关系所决定的。

好了,到此为止,我们可以很容易地判断出上面举的实例中,脚本的编译顺序(实际上,我已经把顺序写在了脚本的文件名中了)

2017-01-07 15:36:40 u013108312 阅读数 4662

本文固定链接:http://blog.csdn.net/u013108312/article/details/54174757
Unity3D 创建新脚本自动添加文件名,作者,创建时间等


定位Unity3D安装路径到 Unity5.3.5\Editor\Data\Resources\ScriptTemplates\81-C# Script-NewBehaviourScript.cs.txt
修改文件如下:

#region 模块信息
// **********************************************************************
// Copyright (C) 2017 The company name
//
// 文件名(File Name):             #SCRIPTNAME#.cs
// 作者(Author):                  #AuthorName#
// 创建时间(CreateTime):           #CreateTime#
// 修改者列表(modifier):
// 模块描述(Module description):
// **********************************************************************
#endregion
using UnityEngine;
using System.Collections;

public class #SCRIPTNAME# : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

本文固定链接:http://blog.csdn.net/u013108312/article/details/54174757


新建脚本:SpriteTitleChange.cs

#region 模块信息
// **********************************************************************
// Copyright (C) 2017 The company name
//
// 文件名(File Name):             SpriteTitleChange.cs 
// 作者(Author):                  Circle
// 创建时间(CreateTime):           2017/1/7 15:34:45
// 修改者列表(modifier):
// 模块描述(Module description):   创建脚本自动修改文件名、作者、创建时间
// **********************************************************************
#endregion
using UnityEngine;
using System.Collections;
using System.IO;

public class SpriteTitleChange : UnityEditor.AssetModificationProcessor
{
    private static void OnWillCreateAsset(string path)
    {
        path = path.Replace(".meta", "");
        if (path.EndsWith(".cs"))
        {
            string allText = File.ReadAllText(path);
            allText = allText.Replace("#AuthorName#", "Circle")
                              .Replace("#CreateTime#", System.DateTime.Now.Year + "/" + System.DateTime.Now.Month
                + "/" + System.DateTime.Now.Day + " " + System.DateTime.Now.Hour + ":"
                + System.DateTime.Now.Minute + ":" + System.DateTime.Now.Second);

            File.WriteAllText(path, allText);
        }

    }
}

本文固定链接:http://blog.csdn.net/u013108312/article/details/54174757

2013-08-21 18:32:12 kfqcome 阅读数 19764

一 创建和使用脚本

1 概述

GameObject的行为都是被附加到其上面的组件控制,脚本本质上也是一个组件。

在unity中创建一个脚本,默认内容如下:

using UnityEngine;
using System.Collections;

public class MainPlayer : MonoBehaviour {

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}



一个脚本通过实现一个派生自”MonoBehaviour”的类来与unity的内部工作机制建立联系。可以将新创建的组件类型的类作为一个蓝图,该类作为一个新类型的组件被附加到游戏对象。每次将一个脚本组件附加到游戏对象,都会创建一个该蓝图定义的对象的实例。创建的脚本文件的文件名必须与里面的类名相同,这样才能将其附加到游戏对象上。


需要注意到的主要事情是,该类中定义的两个函数。Update函数处理游戏对象的帧更新相关的操作。这个可能包括移动,触发动作以及对用户输入的反馈,基本上在游戏过程期间需要处理的任何事情都可以在这里面处理。Start函数在游戏开始前被unity调用(例如,在Update被第一次调用之前),因而是一个进行初始化操作的理想位置。


可能会疑惑为什么不把初始化操作放在类的构造函数中,这是因为对象的构造是由编辑器处理的,在游戏开始的时候并不如想象中那样会发生。如果为一个脚本组件定义构造器,它会和unity的正常操作发生干扰从而导致一些问题。


脚本被创建之后是处于不激活状态的,只有将它的一个实例附加到一个游戏对象之后代码才会被激活。同时,一个游戏对象的一个类型的组件只能有一个,也就是可以附加到游戏对象的脚本组件不能超过一个。


2 变量

就是指的类的成员变量,只不过在unity这里将成员变量设为公有的时候,将其附加到游戏对象后,可以在游戏对象的监视面板中的脚本组件那栏里面看到该公有变量,也就是说可以在编辑器里面直接对该公有变量进行赋值,同时在debug状态下也可以在面板中看到它的值。

unity在编辑器中显示类的成员变量的时候,在类名中遇到(不是第一个字母的)大写字母的时候,会在该字母前加入一个空格,例如在类里面定义个公有成员变量名为”TestVar”,在编辑器中显示的变量名为”Test Var”,中间加了一个空格,但是这只是一个unity显示变量的方式,真正在代码中访问这个变量仍然应该使用”TestVar”这个名字。


3 事件函数

unity中的脚本并不像传统意义上的程序,即在一个循环中连续的运行直到完成任务后再退出,而是unity通过调用定义在脚本内的某个函数,间断的将控制权交给一个脚本。一旦一个函数完成执行,控制权被交还给unity。这些函数就是所知的事件函数,因为它们由unity调用用于响应发生在游戏过程中的事件。unity支持的所有事件都定义在MonoBehaviour这个里面,可以在该类的参考文档中看到一系列的事件。下面介绍几个最常见、最重要的事件。

<1> 常规更新事件(Regular Update Events)

一个游戏更像一个在线生成动画帧的动画。游戏编程中的一个关键观念就是在每帧渲染前对游戏对象的位置、状态和行为进行改变。Update函数是unity中放置这类代码的主要地方,它在每帧被渲染前、同时也在动画被计算前调用。

update事件函数

void Update() {
float distance = speed * Time.deltaTime * Input.GetAxis("Horizontal");
transform.Translate(Vector3.right * speed);
}

物体引擎也以一个和帧渲染类似的方式每隔一个离散时间段进行更新。另一个叫FixedUpdate的函数在每次物理更新之前调用。由于物理更新和帧更新并不使用相同的频率更新,如果将上面的代码放到FixedUpdate函数而不是Update函数的话,可以得到更加精确的结果(FixedUpdate会以一个比Update更加稳定的帧率运行)。

void FixedUpdate() {
Vector3 force = transform.forward * driveForce * Input.GetAxis("Vertical");
rigidbody.AddForce(force);
}

同时在场景中所有对象的Update和FixedUpdate调用之后,以及所有的动画计算之后仍然可能有需要进行一些额外的改变。一个例子就是在一个摄像头需要监视一个目标对象的时候,对摄像机朝向的调整必须在目标对象已经移动之后。另一个例子是在在需要用脚本代码来覆盖动画效果的场合(比如,使角色的头看向场景中的目标对象)。LateUpdate函数可以用于这些场合。


<2>  初始化事件

在游戏中的任何更新函数之前调用初始化代码是经常用到的,初始化函数Start在物体的第一帧更新或者物理更新之前被调用。Awake函数在场景被加载的时候,场景中的每个对象上的该函数都会被调用,所有的Awake函数都会在第一个Start函数(有好多对象的话就有好多个Start函数)调用之前被调用。这就意味着Start函数中的代码也可以使用发生在Awake阶段里面的初始化后的数据。


<3> GUI事件

unity有一个用于渲染GUI控件的系统,这些GUI控件处于场景之上并且响应鼠标事件。这个代码处理起来和常规的帧更新有些不大一样,因而它需要放到OnGUI函数中,周期性的被调用。

void OnGUI() {
GUI.Label(labelRect, "Game Over");
}

同时当发生鼠标单击场景中的游戏对象的时候可以检测这些消息。这个可以用于调整武器或者显示当前在鼠标下面的角色信息。一系列的OnMouseXXX事件函数可以用于和用户的鼠标行为交互。


<4>物理事件

物理引擎会通过调用物体上的事件函数来报告发生的与另一物体的碰撞事件。OnCollisionEnter、OnCollisionStay和OnCollisionExit函数分别在刚接触,持续和离开(broken)的时候调用。当碰撞器被配置成触发器的时候发生碰撞后对应的OnCollisionEnter、OnCollisionStay和OnCollisionExit会被调用。假如在物理更新期间有多于一个接触被检测到这些函数可能会被多次按顺序调用(即调用完一轮再来一轮)。


二 控制游戏对象


在unity中,可以在监视器面板中修改物体的组件属性,但是更多的时候,需要使用脚本来进行这些操作。


1 访问组件

最常见的一个情形是需要使用脚本访问附加到相同游戏对象上的另一个组件(当前脚本就是一个组件,其他的组件也就是另一个组件了)。在引言中提到过,一个组件实质上是一个类的实例,因而首先需要做的是获取想要操作的组件实例的引用。这个通过GetComponent函数来实现。典型的,可能会想要将一个组件赋值给一个变量,如下代码所示

void Start () {
Rigidbody rb = GetComponent<Rigidbody>();
}

一旦获取了组件实例的引用,就可以对它的属性进行想要的操作,同时也可以调用它之上的一些功能函数。

如果想要访问另一个的脚本文件,也可以使用GetComponent,只需使用脚本的类名作为该函数的组件类型参数(因为脚本本来就也是一个组件)。

如果想要去获取一个并没有添加到当前游戏对象的组件,GetComponent函数会返回null,如果试图去改变一个null对象上的任何值,将会发生null引用错误。

由于一些组件类型经常使用,unity提供了一些内置的变量来访问它们,例如可以使用下面的代码

void Start () {
transform.position = Vector3.zero;
}


而不用使用GetComponent去获得Transform组件,所有的内置组件变量列表在MonoBehaviour类的参考手册里面有。


2 访问其他对象

虽然游戏对象有的时候都是各自处理,使用代码进行跟踪其他物体是常有的事。例如,一个追赶的敌人可能需要知道玩家的位置,unity提供了一系列不同的方法来获取其他对象,各适合不同的场合。


<1> 将对象链接到变量

最直接的办法是将一个游戏对象添加到脚本的公有成员变量上,直接在编辑器中将需要访问的游戏对象拖到对应脚本组件的那个公有成员变量上,unity会自动根据变量的类型将添加的游戏对象中相同的组件类型映射到该变量。

例如将一个游戏对象拖给一个Transform的成员变量,就会自动的将游戏对象的Transform组件和该变量映射起来。

直接将对象和变量链接起来在处理需要有永久链接的对象的时候是最有用的方法。同时也可以使用一个数组变量和几个相同类型的对象链接起来,但是这种链接必须在unity编辑器中完成,而不能在运行时进行。通常使用下面的两种方法来在运行时定位对象。


<2> 查找子物体

有的时候,一个游戏场景中可能会用到很多同一类型的对象,例如敌人、路点(waypoints)和障碍物。这些对象在游戏中需要由一个特定的脚本来监视和响应。这个时候使用变量来链接这些对象太过麻烦不好操作。对于这种情况,通常更好的方法是将一系列的对象添加到一个父对象下面,这些子对象可以通过使用父对象的Transfrom组件来获得。

public class WaypointManager : MonoBehaviour {
public Transform waypoints;

void Start() {
waypoints = new Transform[transform.childCount];
int i = 0;

for (Transform t in transform) {
waypoints[i++] = t;
}
}
}



同时也可以使用Tranfrom.Find来查找某个具体的子对象。使用Transform来进行对象查找操作是因为每一个游戏对象都有Transfrom组件。


<3> 通过名称或标签访问对象

只要有一些信息,在层级场景中的任何位置定位到该游戏对象是可能的。单个对象可以通过GameObject.Find函数进行查找。如下:

GameObject player;

void Start() {
player = GameObject.Find("MainHeroCharacter");
}


某个对象或者一系列的对象也可以分别通过GameObject.FindWithTag和GameObject.FindObjectsWidthTag函数进行定位。


<4>查找特定类型的对象

staticObjectFindObjectOfType(Type type)

返回指定类型对象中的第一个活动的加载对象

需要注意的是这个函数很慢(可能是由于要在整个场景中进行遍历),不推荐每一帧都使用这个函数,在大多数情况下可以使用单件模式

例如

Camera cam = FindObjectOfType(typeof(Camera)) as Camera;

由于该函数返回的类型是Object,所以需要使用as进行一下强制转换。


static Object[] FindObjectsOfType(Type type);

返回指定类型的加载活动对象的列表,速度也慢

HingeJoint[] hinges = FindObjectsOfType(typeof(HingeJoint)) as HingeJoint[];



三 创建和销毁对象

在运行时创建和销毁对象是常有的事。在unity中,可以使用Instantiate函数对现有的一个对象做一个拷贝来创建一个新的游戏对象。

public GameObject enemy;

void Start() {
for (int i = 0; i < 5; i++) {
Instantiate(enemy);
}
}

值得注意的是用于进行拷贝的对象并不一定需要放置在场景中。更普遍的做法是将一个预设(Prefab)拖到脚本的对应公有成员变量上,实例化的时候直接对这个成员变量进行实例化即可。


同时也有一个Destroy函数在帧更新函数完成后或设定的一个延时时间后销毁一个对象。

void OnCollisionEnter(otherObj: Collision) {
if (otherObj == "Missile") {
Destroy(gameObject,.5f);
}
}

注意到Destroy函数可以销毁单独的组件而不对游戏对象本身产生影响,一个通常范的错误是

Destroy(this);

这句代码仅仅效果脚本组件,而不会销毁该脚本所附加在的对象


四 协程(Coroutines)

当调用一个函数的时候,它会在返回前运行到结束位置。

根据前面的知识可以知道,几乎所有的函数都由unity在帧更新的时候调用一次,然后到下一帧后再调用一次,那这个时候在函数中写一些循环体类的代码想要达到产生动画的效果可能会失败。如下

void Fade() {
for (float f = 1f; f <= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.alpha = f;
renderer.material.color = c;
}
}


这是想要产生一个透明渐变的效果,但是由于Fade函数会在一个frame update时间片中全部执行完,达不到渐变效果。要达到这个效果,可以在帧更新函数中进行处理即可。但是,对于这类情形更方便的做法是使用coroutine。

一个coroutine就像一个可以暂停执行并将控制权返回给unity的函数,但是在下一帧的时候又可以在它停止的位置继续执行。在C#中,这样声明一个coroutine:

IEnumerator Fade() {
for (float f = 1f; f <= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.alpha = f;
renderer.material.color = c;
yield return;
}
}


实质上它是一个返回类型为IEnumerator的函数,同时在函数体中增加了yield return这句代码。yield return这行就是会在执行的时候暂停、在下一帧的时候恢复执行的位置。要启动coroutine,需要使用StartCorutine函数。

void Update() {
if (Input.GetKeyDown("f")) {
StartCoroutine("Fade");
}
}


默认的情况下,一个coroutine在它暂停后的下一帧恢复,但是也可以使用WaitFroSeconds来引入一个延时。

IEnumerator Fade() {
for (float f = 1f; f <= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.alpha = f;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}


这个可以用于产生一个随时间变化的效果,同时也是一个用于进行优化的有效方法。游戏中的许多人物需要周期性的执行,最常用的做法是将它们包含在Update函数中。但是Update函数通常每秒会调用多次。当有的任务并不需要这么频繁的被调用的时候,可以将它放在一个coroutine中按一定时间进行调用,而不是每一帧都调用。一个这样的例子就是用于在游戏中如果有敌人接近的话就提示玩家,代码如下

function ProximityCheck() {
for (int i = 0; i < enemies.Length; i++) {
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}

return false;
}
IEnumerator DoCheck() {
for(;;) {
ProximityCheck;
yield return new WaitForSeconds(.1f);
}
}


当有很多敌人的时候,使用coroutine0.1秒执行一次靠近检查,可以减少大量的计算量。


2016-09-13 13:49:41 chy_xfn 阅读数 3645

相信很多人都喜欢在创建脚本时添加这样的注释说明,以前做Android开发用Eclipse设置过这样的脚本模板,所以也想在Unity上添加,从网上搜索资料得,首先找到Unity安装目录下的(注意:修改的是Unity脚本模板而不是vs或者momo里的)

“Unity\Editor\Data\Resources\ScriptTemplates ”

打开“81-C# Script-NewBehaviourScript.cs”文件,如下:

using UnityEngine;
using System.Collections;

public class #SCRIPTNAME# : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

    // Update is called once per frame
    void Update () {

    }
}

看到了吧,这个和我们在Unity创建C#脚本时自动生成的是不是一样,唯一区别的是宏#SCRIPTNAME#,这个宏会跟随我们创建的脚本名变化,这个功能也是我们自定义脚本模板的重要功能,后来我在这个文件的最上面添加了我的自定义注释,然后加了个自定义的#CreateTime#,是想在创建脚本时显示创建时间。结果发现这个时间一直显示为#CreateTime#不会变的。原来#SCRIPTNAME#是Unity自定义的一个宏,所以你想添加时间,必须也要自定义。然后自己找了好久也没找到在哪修改,最后找了一个直接在项目里修改的办法。如果修改本地文件的话,每次创建新项目都会生成这个新脚本模板,同时还要在项目里修改添加时间自定义。再加上我只喜欢在正式项目里才想加这个注释模板,有些并不想加,所以干脆把其他注释全部的添加到项目里,方便管理,具体如下:

该脚本需放到Editor文件夹内

using UnityEngine;
using System.Collections;
using System.IO;

public class ChangeScriptTemplates : UnityEditor.AssetModificationProcessor
{
     // 添加脚本注释模板
     private static string str = 
     "// ========================================================\r\n"
     +"// 描 述:\r\n"
     +"// 作 者:xfn \r\n"
     +"// 创建时间:#CreateTime#\r\n"
     +"// 版 本:v 1.0\r\n"
     +"// ========================================================\r\n";

     // 创建资源调用
     public static void OnWillCreateAsset(string path)
     {
        // 只修改C#脚本
        path = path.Replace(".meta", "");
        if (path.EndsWith(".cs"))
        {
            string allText = str;
            allText += File.ReadAllText(path);
            // 替换字符串为系统时间
            allText = allText.Replace("#CreateTime#",System.DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
            File.WriteAllText(path, allText);
        }
     }
}
没有更多推荐了,返回首页