2017-04-10 01:55:17 Mogoson 阅读数 890
  • Unity3D入门到精通-(4)Unity热更新

    本次系列课程的目标是让Unity3D初学者掌握C语言,Untiy调用lua脚本深入理解uLua框架实现原理,并根据项目的特点实现相应的热更新方案。 适合对象:Unity初学开发者,Unity中级开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    2205 人正在学习 去看看 张刚

Script Template Toolkit

概述

Unity3D 脚本模板快速编辑,保存。脚本文件添加头注释,自动标注创建日期以及版权时间。

问题

  • 在使用Unity3D创建脚本文件时,希望Unity3D编辑器自动按照个人喜好预定义的模板来创建各类脚本文件,例如添加文件头注释等需求。
  • 希望在脚本头注释中自动标注脚本创建日期,以及根据创建日期标注版权时间(例如:2017-2018)。

条件

  • 实际上,Unity3D将各类脚本文件模板放在了安装目录下(例如:Unity 5.4.1f1\Editor\Data\Resources\ScriptTemplates)。
  • 在Unity3D提供的API中,可以通过AssetModificationProcessor类的OnWillCreateAsset方法捕获Unity3D编辑器创建Asset(包括脚本文件)事件。

方案

编辑

  • 编写Unity3D扩展编辑器代码,通过继承EditorWindow绘制自定义编辑器窗口来编辑/保存Unity3D的各类脚本文件模板(文本文件)。
  • 预定义标注字符串,“#CREATETIME#”标注创建日 期,
    “#COPYRIGHTTIME#”标注版权时间声明,在编辑模板时,在需要插入创建日期的地方使用“#CREATETIME#”标注即可,插入版权时间同理。
  • 使用预定义标注字符串编辑个人喜好风格的脚本文件模板。

创建

  • 在Unity3D编辑器的Project面板通过鼠标右键菜单创建脚本文件(例如:C#)时,Unity3D引擎会将相应的模板文件里边的内容全部拷贝到新建的文件中,且自动将“#SCRIPTNAME#”替换为新建文件时输入的文件名。但是,“#CREATETIME#”,“#COPYRIGHTTIME#”是我们自定义的标注字符串,Unity3D引擎不会识别,也不会将其替换成我们预期的内容。
  • 编写扩展编辑器代码,继承AssetModificationProcessor类并实现其OnWillCreateAsset(string assetPath)方法以捕获Unity3D编辑器创建Asset(包括脚本文件)事件以及处理我们自定义的标注字符串。
  • 捕获到创建Asset事件,检查文件扩展名,如果是.cs或.js或.shader或.compute则说明创建的是脚本文件,也就是说Unity3D引擎会按照相应的模板(例如C#,我们之前已经将其编辑成个人喜好风格的文本)来创建。(扩展编辑器代码)读取新建文件的内容,将
    “#CREATETIME#”替换成当前日期,将“#COPYRIGHTTIME#”替换成当前年份-下个年份。

实现

扩展编辑器

  • ScriptTemplateEditor.cs 读取/编辑/保存 Unity3D各类脚本模板文件。
  • ScriptTemplateModifier.cs 捕获创建事件,替换预定义标注字符串为其具体含义的内容。

脚本模板示例

  • Template 文件夹下存放各类脚本模板文件的示例,以供读者编写自己风格的模板时参考。

源码

源码托管地址

2016-12-19 21:10:01 Lvxuebin 阅读数 442
  • Unity3D入门到精通-(4)Unity热更新

    本次系列课程的目标是让Unity3D初学者掌握C语言,Untiy调用lua脚本深入理解uLua框架实现原理,并根据项目的特点实现相应的热更新方案。 适合对象:Unity初学开发者,Unity中级开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    2205 人正在学习 去看看 张刚

Unity脚本概述

与其他常用的平台有所不同,Unity中的脚本程序如果要起作用,主要途径为将脚本附加到特定的游戏对象上。这样脚本中不同的方法在特定的情况下会被调用,实现特定的功能。
在Unity脚本中,各事件函数的功能和大体的执行顺序如下:
1、Awake

   Awake用于脚本唤醒。此方法为系统执行的第一个方法,用于脚本
   的初始化,在脚本的生命周期中只执行一次。

2、Start

   Start方法在Awake之后执行,在脚本的生命周期中只执行一次。
   由于Awake和Start函数的特性与c#中的构造函数类似(一般情况
   下,在脚本的生命周期中只执行一次),所以在Unity中也常用来
   初始化类的成员变量。
   这个方法在游戏场景加载时被调用,在该方法内可以写一些游戏场
   景初始化之类的代码。

3、FixedUpdate

   FixedUpdate用于固定频率更新。这个方法会在固定的物理时间
   步调调用一次。这里也是基本物理行为代码执行的地方。
   通常情况下下,FixedUpdate()比Update()更频繁地调用。
   当帧率较低时,在某一帧的时间间隔内比Update可能会被调用多
   次;而当帧率很高时,在某一帧的时间间隔内FixedUpdate可能
   根本不会调用。
   还有一点,在Unity中,若设置Time Scale值为0,可以实现动
   力学特性的暂停,即所有的FixedUpdate中的代码都不会执行。

4、Update

    Update用于正常更新,即用于帧更新后同步场景状态。此方法每
    帧都会由系统自动调用一次。大部分的游戏代码在这里执行,除
    了物理部分的代码。
    在使用Update()时,对于一些变量,如速度、移动距离等通常需
    要乘以Time.deltaTime来抵消帧率带来的影响,使物体状态的
    改变看起来比较均匀正常。而在FixedUpdate中,由于其更新频
    率固定,所以不需要采用Time.deltaTime来修正状态改变频
    率。 

5、LateUpdate

    LateUpdate用于延迟更新,此方法在Update()之后执行,每
    一帧调用一次。

6、OnGUI

    OnGUI用来绘制用户交互界面,每一帧会调用多次。其中,与布
    局(Layout)和重汇(Repaint)相关的事件会被优先处理,然
    后是键盘事件和鼠标事件。

7、OnDestroy

    OnDestroy在当前脚本被销毁时调用。若在脚本中动态分配了内
    存,可以再OnDestroy()中进行释放。    

    同时,开发人员在有需要的情况下,还可以重写一些处理特定事
    件的回调方法,这类方法一般以On前缀开头,如
    OnCollisionEnter方法(此方法在系统检测到碰撞开始时回
    调)等。

Unity官方提供的事件顺序图:

这里写图片描述

   1、继承自MonoBehaviour类
         Unity中所有挂载到游戏对象上的脚本中的类必须继承
         MonoBehaviour类(直接的或者间接地),
         MonoBehaviour定义了各种回调方法,如Start、
         Update等。
   2、类名字必须匹配文件名
        C#的类名需要手动编写,而且类名必须和文件名相同,否则当脚本挂载到游戏对象时,在控制台会报错。
   3、使用Awake或Start方法初始化
        用于初始化脚本的代码必须置于Awake或Start方法中。两
        者不同之处在于Awake在加载场景时运行;Start方法是在
        第一次调用Update或FixedUpdate方法之前被调用;
        Awake运行在所有Start方法之前。
   4、Unity脚本中的协同程序有不同的语法规则
        (1)协程的返回值必须是Enumerator。
        (2)协程的参数不能加关键字ref或out。
        (3)在C#脚本中,必须通过StartCoroutine来启动协
        程。
        (4)yield语句要用yield return 来代替。
        (5)在函数Update和FixedUpdate中,不能使用yield
        语句,但可以启动协程。
    5、只有满足特定情况变量才能显示在属性查看器中
         只有序列化的成员才能显示在属性查看器中,如果属性想
         在属性查看器中显示,其必须是public类型的。
    6、尽量避免使用构造函数
         不要在构造函数中初始化任何变量,要用Awake或Start
         方法来实现。即便是在编辑模式,Unity会自动调用构造
         函数。
    7、调试
         Unity中C#代码的调试与传统的C#调试有所不同。Unity
         自带了完善的调试功能,在Unity中的控制台
         (Console)中包含了当前的全部错误,双击这个错误,
         可以自动跳转到默认的脚本编辑器中,然后光标会在错误
         所对应的行首跳动。

Unity中的坐标系

   一般情况下,在Unity中,x轴为红色的轴表示左右,y轴为绿色的
   轴表示上下,z轴为蓝色的轴表示前后。  
2014-05-13 10:12:45 lmzqm 阅读数 983
  • Unity3D入门到精通-(4)Unity热更新

    本次系列课程的目标是让Unity3D初学者掌握C语言,Untiy调用lua脚本深入理解uLua框架实现原理,并根据项目的特点实现相应的热更新方案。 适合对象:Unity初学开发者,Unity中级开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    2205 人正在学习 去看看 张刚

在本章中我们将学习的是Unity3D的脚本编程,Unity3d目前支持javascript,c#,Boo这三种脚本的开发!由于本人开发过C++所以就直接用C#来进行unity3d的脚本开发!在脚本开发的过程中我们很有必要来了解一下Unity对象的生命周期!

所谓的生命周期也就是说这个对象的执行顺序!只有对这个顺序能够很好的理解,才能更加便于我们的开发!因此花点时间了解下还是很有必要的哈!

 using UnityEngine;
 using System.Collections;
 public class FirstScript : MonoBehaviour{
    void Awake()
    {
       //这是进入后的第一个执行函数,只执行一次
    }
    void Start()
    {
       //这是第二个执行的函数,也只执行一次,一般用于初始化的操作
    }
 
    void Update()
    {
        //这是更新操作,一帧一帧的执行
    }
 
    void LateUpdate()
    {
         //这个函数在Update后执行
    }
 
    void FixedUpdate()
    {
         //这个函数在固定时间后执行默认为0.02
    }
 
    void OnGUI()
    { 
         //这是绘制函数,可以再场景中绘制一些控件什么的
    }
 
    void OnDestory()
    {
         //这是销毁函数
    }
}
首先一进入的时候是调用 void Awake()这个函数,这函数值执行一次,然后紧接着进入void Start();这函数也只执行一次,接着就进入Void Update();这个函数时一帧一桢的执行的,当Update()执行完后会马上执行voidLateUpdate();这个函数!

当然我们还有一些函数例如voidFixedUpdate();这是在固定的时间会执行一遍,还有就是OnGUI();这个函数会一直执行着,当然当我们销毁的时候会执行OnDestory()这个函数;

Ok,接着就让我们进入Unity3D脚本开发的世界吧!

1首先是创建对象

对于创建对象有两种方法可以使用,一种是通过用户的赋值来进行的,一种是直接通过代码来进行对象的创建。

这个我们在C#的文件中定义一下就可以啦!

例如:

         Public GameObject obj4;

         Public Texture texture;

这样在绑定这个脚本的文件的属性视图中将会出现

        

接下来我们要做的就是将我们需要的GameObject和对应的Texture拖入就可以使用啦!是不是想当的方便呢?呵呵。。。你也可以试试哈!

接下来我将通过代码来进行讲解!

首先和大家说下我们的目的是什么,我们通过一个按钮来创建一个cube,然后通过一个按钮来实现这个cube的旋转。Ok,直接上代码啦!

using UnityEngine;
using System.Collections;
 
public class cube : MonoBehaviour {
    private GameObject obj;
    void Awake()
    {
    }
 
    void Start(){};
 
    void OnGUI()
    {
         if(GuiLayout.Button(“creatobj”,GuiLayout.height(50),GuiLayout.width(100)))
        {
            obj =GameObject.CreatPrimitive(PrimitiveType.Cube);
        }
         if(GulLayout.Repeat (“Rotate”,GuiLayout.Height(50),GuiLayout.width(100)))
        {
            obj.tranfrom.Rotate(Vector3.up*Time.dalteTime*30);
        }
    }
 
    void OnDestory()
   {
 
   }

OK,这样的操作我们就通过代码来创建了对象啦!创建了对象我们就可以做我们自己想做的事情啦!

2 获得对象

         在我们的游戏开发的过程中获得当前的游戏对象是很重要的一个操作,例如当我们的主角运动的时候后面跟真的也要一起运动,这时候我们就需要获得其他的对象,然后让其跟着我们的主角一起运动,当然还有很多运用哈!这里只是简单的几个例子哈!

         首先我们在游戏对象视图中创建一个cube对象名字为cube2(保证这是一个独一无二的名字哈!)

         然后我们在工程视图中创建一个C#文件,并改名字为Main(在win下的快捷键是按F2就可以啦!),然后将该脚本绑定给摄像机。接下来我们双击Main文件,进行编辑脚本文件!

         在这个脚本中我们主要的操作是,我们有三个按钮,第一个按钮用于获取对象,第二个按钮用于旋转对象,第三个按钮用于销毁对象!

using UnityEngine;
using System.Collections;
public GetObj : Monobehaviour{
  Private GameObject obj;
  void Start()
 {
                           
 }
 
 void OnGUI()
 {
     if(GuiLayout.Button(“GetObject”,GuiLayout.Height(40),GuiLayout.Width(100)))
     {
       obj= GameObject.Find(“cube2”);
     }
 
     if(GuiLayout.RepeatButton(“Rotate”),GuiLayout.height(40),GuiLayout.width(100))
     {  
         obj.tranfrom.Rotate(Vector3.up*Time.deltaTime*30);
     }
 
     if(GuiLayout.Button(“Destory”),GuiLayout.height(40),GuiLayout.width(100))
     {
         Destory(obj);
      }
 }
 
 void OnDestory()
 {
 
 }
        
}

Obj = GameObject.Find();这样我们就可以获得对象啦!

谢谢大家对我博客支持,你们的支持是我的动力!希望大家一起奋斗哈!


2013-08-21 18:32:12 kfqcome 阅读数 19761
  • Unity3D入门到精通-(4)Unity热更新

    本次系列课程的目标是让Unity3D初学者掌握C语言,Untiy调用lua脚本深入理解uLua框架实现原理,并根据项目的特点实现相应的热更新方案。 适合对象:Unity初学开发者,Unity中级开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    2205 人正在学习 去看看 张刚

一 创建和使用脚本

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秒执行一次靠近检查,可以减少大量的计算量。


2013-02-01 09:50:20 yang7474020 阅读数 1440
  • Unity3D入门到精通-(4)Unity热更新

    本次系列课程的目标是让Unity3D初学者掌握C语言,Untiy调用lua脚本深入理解uLua框架实现原理,并根据项目的特点实现相应的热更新方案。 适合对象:Unity初学开发者,Unity中级开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    2205 人正在学习 去看看 张刚

Unity3D教程:Unity制作连连看教程

GameManager.cs  游戏的核心代码,产生图片,判断是否可以销毁等。

[color=#008ef1][font=宋体]using UnityEngine;[/font][/color]
using System.Collections;
using System.Collections.Generic;
public class GameManager : MonoBehaviour
{
public DrawLine drawLine;//画线
public GameObject tilePrefab;//tile的预制
public List<Tile> tiles;//开始实例化的时候存放tile
public List<Tile> _tiles;//存放随机摆放的tile
public List<Tile> tilesEdge;//为了边界处可以有拐点,把棋盘四周的tile放在这里,这里的tile看不到
public int x, y;//棋牌的大小,两个数必须是偶数
private Tile tileA;
private Tile tileB;
private bool destroy;
private Vector3 mousePos;
private enum stepType//控制游戏的状态
{
one,
two,
three
}
private stepType _stepType;
void Start ()
{
this.gameObject.transform.position = Vector3.zero;
Spawn ();
_stepType = stepType.one;
}private void Spawn ()//实例化tile
{
float num = (x * y - (2 * x + 2 * y - 4)) * 0.5f;
for (int i = 0; i <num; i ++) {
int idTex = Random.Range (20, 45);
GameObject obj = Instantiate (tilePrefab) as GameObject;
GameObject obj2 = Instantiate (tilePrefab) as GameObject;
Tile tile = obj.GetComponent<Tile> ();
Tile tile2 = obj2.GetComponent<Tile> ();
tile.Init (idTex);
tile2.Init (idTex);
tiles.Add (tile);
tiles.Add (tile2);
}
for (int i = 0; i<((2*x+2*y) -4); i++) {//实例化边缘的tile
GameObject obj = Instantiate (tilePrefab) as GameObject;
obj.name = "edage";
Tile tile = obj.GetComponent<Tile> ();
tilesEdge.Add (tile);
}
CreatTile ();
for (int i = 0; i < _tiles.Count; i++) {
_tiles [i].transform.name = i.ToString ();
_tiles [i].id = i;
}
this.transform.position = new Vector3 (-(x / 2.0f - 0.5f), -(y / 2.0f - 0.5f), 0);
}private void CreatTile ()//随机摆放tile,如果是边缘的就放在边缘位置
{
int idTex = 0;
float _y = 0.0f;
for (int i = 0; i < y; i ++) {
float _x = 0.0f;
for (int j = 0; j < x; j ++) {
if (i == 0 || j == 0 || i == y - 1 || j == x - 1) {
tilesEdge [0].transform.position = new Vector3 (_x, _y, 0);
tilesEdge [0].pos = new Vector2 (_x, _y);
tilesEdge [0].transform.rotation = new Quaternion (0, 0, 180, 0);
tilesEdge [0].transform.parent = this.gameObject.transform;
_tiles.Add (tilesEdge [0]);
tilesEdge [0].transform.localScale = Vector3.zero;
tilesEdge [0].type = false;
tilesEdge.RemoveAt (0);
} else {
int id = Mathf.FloorToInt (Random.Range (0, tiles.Count));
tiles [id].transform.position = new Vector3 (_x, _y, 0);
tiles [id].pos = new Vector2 (_x, _y);
tiles [id].transform.rotation = new Quaternion (0, 0, 180, 0);
tiles [id].transform.parent = this.gameObject.transform;
_tiles.Add (tiles [id]);
tiles.RemoveAt (id);
}
_x += 1;
}
_y += 1;
}
}private void SelectTile ()//开始选择图片,通过射线方式选中,如果tileA和tileB不相同,则tileA等于tileB开始下一个检测
{
Ray ray = Camera.mainCamera.ScreenPointToRay (mousePos);
RaycastHit hit;
int mask = 1 << 8;
if (Physics.Raycast (ray, out hit, mask)) {
if (tileA == null) {
tileA = hit.transform.GetComponent<Tile> ();
tileA.SetTileTexture (1);
//                print ("tileA = hit.transform.GetComponent<Tile> ();" + tileA.transform.name);
} else {
tileB = hit.transform.GetComponent<Tile> ();
tileB.SetTileTexture (1);
//                print ("tileB = hit.transform.GetComponent<Tile> ();" + tileB.transform.name);
Compare (tileA, tileB);
if (tileA == null && tileB == null) {//                    print ("a and b is null");
}
}
//            hit.transform.GetComponent
}
}

private void Compare (Tile tile1, Tile tile2)//比较两个点是否可以连接到一起
{
// same card
_stepType = stepType.one;
drawLine.waypoints.Add (tile1.transform); //第一个选择的tile是画线的起始位置,
drawLine.transform.position = tile1.transform.position;
destroy = false;
print ("compare");
if (tile1.pos.x == tile2.pos.x && tile1.pos.y == tile2.pos.y) {如果选中的是同一个图片返回
tileA.SetTileTexture (0);
//            tileB.SetTileTexture (0);
tileA = tileB;
tileB = null;
//            tileA.SetTileTexture (1);
return;
} else if (tile1.pos.x == tile2.pos.x && tile1.pos.y != tile2.pos.y) {//如果两点的x相等,竖向检测
print ("check y");
destroy = CheckY (tile1, tile2);
if (destroy)
drawLine.waypoints.Add (tile2.transform);
} else if (tile1.pos.x != tile2.pos.x && tile1.pos.y == tile2.pos.y) {//如果两点的y相等,横向检测
print ("check x");
destroy = CheckX (tile1, tile2);
if (destroy)
drawLine.waypoints.Add (tile2.transform);
}
if (!destroy) {//不符合直线连接方式的开始进行一个拐点的检测
_stepType = stepType.two;
destroy = CheckTwoStep (tile1, tile2);
//            print ("destroy = CheckTwoStep (tile1, tile1);:" + destroy);
print ("check two step");
if (!destroy) {//不符合直线和一个拐点检测的开始进行两个拐点的检测
_stepType = stepType.three;
destroy = CheckThreeStep (tile1, tile2);
print ("check three:" + destroy);
print ("tile1.idTex:" + tile1.idTex + "tile1.idTex:" + tile1.idTex);
}
}
if (destroy) {//如果符合销毁条件销毁图片,并开始画线
tile1.transform.localScale = Vector3.zero;
tile2.transform.localScale = Vector3.zero;
tile1.type = false;
tile2.type = false;
tileA = null;
tileB = null;
drawLine.MoveToWaypoint ();
} else {//不符合的话,清除画线中的路径
drawLine.ClearPath ();
tileA.SetTileTexture (0);
//            tileB.SetTileTexture (0);
tileA = tileB;
tileB = null;
return;
}
}
// one step横向检测
private bool CheckX (Tile a, Tile b)
{
bool compare = true;
int _min, _max;
if (a.idTex == b.idTex) {
CompareID (a, b, out _min, out _max);
_min += 1;
if (_min == _max)
return true;
for (int i = _min; i < _max; i++) {
if (_tiles [i].type == true) {
compare = false;
break;
}
}
return compare;
} else
return false;
}
//竖向检测
private bool CheckY (Tile a, Tile b)
{
bool compare = true;
int _min, _max;
//        int idA = (int)(a.x * x + a.y);
//        int idB = (int)(b.x * x + b.y);
//        print (_tiles [idA].id.ToString () + "idA:" + idA);
//        print (_tiles [idB].id.ToString () + "idB:" + idB);
//        print ("a.idtex:" + a.idTex + "b.idtex:" + b.idTex);
if (a.idTex == b.idTex) {
CompareID (a, b, out _min, out _max);
_min += x;
if (_min == _max)
return true;
for (int i = _min; i < _max; i+=x) {
//                print ("1step");
if (_tiles [i].type == true) {
compare = false;
break;
}
}
//            if (compare) {
//                print ("2step");
//                a.type = false;
//                b.type = false;
//            }
return compare;
} else
return false;
}
// two step一个拐点的检测
private bool CheckTwoStep (Tile a, Tile b)
{
if (a.pos.x == b.pos.x || a.pos.y == b.pos.y)
return false;
int id1 = (int)(a.pos.y * x + b.pos.x);
if (_tiles [id1].type == false) {
_tiles [id1].idTex = a.idTex;

if (CheckY (_tiles [id1], b)) {
if (CheckX (a, _tiles [id1])) {
if (_stepType == stepType.two) {
drawLine.waypoints.Add (_tiles [id1].transform);
drawLine.waypoints.Add (b.transform);
} else if (_stepType == stepType.three) {
drawLine.waypoints.Add (_tiles [id1].transform);
print ("=====================:" + 1);
}
return true;
}
//                else
//                    return false;
}
}
int id2 = (int)(b.pos.y * x + a.pos.x);
if (_tiles [id2].type == false) {
_tiles [id2].idTex = b.idTex;

if (CheckY (a, _tiles [id2])) {
if (CheckX (b, _tiles [id2])) {
if (_stepType == stepType.two) {
drawLine.waypoints.Add (_tiles [id2].transform);
drawLine.waypoints.Add (b.transform);
} else if (_stepType == stepType.three) {
drawLine.waypoints.Add (_tiles [id2].transform);
print ("=====================:" + 2);
}
return true;
}
//                else
//                    return false;
}
}
return false;
}
// three step两个拐点的检测
private bool CheckThreeStep (Tile a, Tile b)
{
print ("a:" + a.idTex + "b:" + b.idTex);
//        if (a.pos.x == b.pos.x || a.pos.y == b.pos.y) return false;
bool returnValue = false;
print ("returnValue:" + returnValue);
List<Tile> _comparrPointsB;
ComparePoints (b, out _comparrPointsB);//返回b点可以横竖直线相连的点
for (int i =0; i<_comparrPointsB.Count; i++) {
returnValue = CheckTwoStep (a, _comparrPointsB [i]);
if (returnValue) {
drawLine.waypoints.Add (_comparrPointsB [i].transform);
drawLine.waypoints.Add (b.transform);
return returnValue;
}
}
if (!returnValue) {
List<Tile> _comparrPointsA;
ComparePoints (a, out _comparrPointsA);
print (a.name);
print (b.name);
for (int i =0; i<_comparrPointsA.Count; i++) {
print ("--------------" + b.idTex);
returnValue = CheckTwoStep (b, _comparrPointsA [i]);
if (returnValue) {
drawLine.waypoints.Add (_comparrPointsA [i].transform);
drawLine.waypoints.Add (b.transform);
return returnValue;
}
}

}
return returnValue;
}
//两个拐点的时候返回可以与a横竖直线相连的点
private void ComparePoints (Tile a, out List<Tile> comparePoints)
{
print ("a.idtex" + a.idTex);
comparePoints = new List<Tile> ();
comparePoints.Clear ();
//        for (int i = 0; i < y; i ++) {
//            if (i != a.y) {
//                int id = (int)(i * x + a.pos.x);
//                if (_tiles [id].type == false) {
//                    comparePoints.Add (_tiles [id]);
//                    _tiles [id].idTex = a.idTex;
//                }
//            }
//        }
for (int i = (int)a.pos.y - 1; i >-1; i--) {
int id = (int)(i * x + a.pos.x);
//            print ("three step :" + id);
if (_tiles [id].type == false) {
comparePoints.Add (_tiles [id]);
_tiles [id].idTex = a.idTex;
print ("_tiles [id].idTex = a.idTex; " + _tiles [id].idTex);
} else
break;
}
for (int i = (int)a.pos.y + 1; i < y; i++) {
int id = (int)(i * x + a.pos.x);
//            print ("three step :" + id);
if (_tiles [id].type == false) {
comparePoints.Add (_tiles [id]);
_tiles [id].idTex = a.idTex;
print ("_tiles [id].idTex = a.idTex; " + _tiles [id].idTex);
} else
break;
}
for (int i = (int)a.pos.x -1; i >-1; i --) {
int id = (int)(a.pos.y * x + i);
if (_tiles [id].type == false) {
comparePoints.Add (_tiles [id]);
_tiles [id].idTex = a.idTex;
print ("_tiles [id].idTex = a.idTex; " + _tiles [id].idTex);
} else
break;
}
for (int i = (int)a.pos.x +1; i < x; i ++) {
int id = (int)(a.pos.y * x + i);
if (_tiles [id].type == false) {
comparePoints.Add (_tiles [id]);
_tiles [id].idTex = a.idTex;
print ("_tiles [id].idTex = a.idTex; " + _tiles [id].idTex);
} else
break;
}
//        for (int i = 0; i < x; i ++) {
//            if (i != a.x) {
//                int id = (int)(a.pos.y * x + i);
//                if (_tiles [id].type == false) {
//                    comparePoints.Add (_tiles [id]);
//                    _tiles [id].idTex = a.idTex;
//                }
//            }
//        }
}
private void CompareID (Tile a, Tile b, out int min, out int max)
{

if (a.id < b.id) {
min = a.id;
max = b.id;
} else {
min = b.id;
max = a.id;
}
}
Vector2 TexSize ()
{
Vector2 size = new Vector2 (1 / x, 1 / y);
return size;
}
Vector2 TexOffset (int _idTex)
{
int a = (int)(_idTex / x);
//        print (a + "a:" + _idTex);
int b = (int)(_idTex % x);
//        print (b + "b:" + _idTex);
Vector2 offset = new Vector2 (b / x, (y - 1 - a) / y);
return offset;
}
void Update ()
{

if (Input.GetMouseButtonUp (0)) {
mousePos = Input.mousePosition;
SelectTile ();
}

}
private void ClearTiles (List<Tile> tiles)
{

tiles.Clear ();
//         this.gameObject.transform.DetachChildren();
}
}
// ari

DrawLine.cs,画线脚本,用的itween。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DrawLine : MonoBehaviour
{
public List<Transform> waypoints = new List<Transform> ();
public float rate = 1;
private int currentWaypoint = 1;
public void MoveToWaypoint ()
{
print ("public void MoveToWaypoint (): " + waypoints.Count);
StartCoroutine ("move");
}
public void ClearPath ()
{
waypoints.Clear ();
print ("path.Clear ();");
}
IEnumerator move ()
{
for (int i = 0; i < waypoints.Count; i++) {
iTween.MoveTo (this.gameObject, waypoints [i].position, rate);
print ("now id:" + i);
yield return new WaitForSeconds(rate);
}
waypoints.Clear ();
}
}

Tile.cs

using UnityEngine;
using System.Collections;
public class Tile : MonoBehaviour
{
public int id;
public int idTex; //通过这个判断两个图片是否相同
public Vector2 pos ;
public bool type = true;//控制图片的状态,当销毁的时候为false,其他判断的时候可以通过该点
public float x, y;
public Texture texA, texB;//鼠标选中的时候可以换贴图
public GameObject mask;//鼠标选中的时候上边显示的框框
public void Init (int _idTex)
{
idTex = _idTex;
Vector2 offset = TexOffset (_idTex);
this.renderer.material.SetTextureOffset ("_MainTex", offset);
this.renderer.material.SetTextureScale ("_MainTex", new Vector2 (0.2f, 0.1f));}
//设置tile显示的贴图和大小
public void SetTileTexture (int i)
{
if (i == 0) {
this.renderer.material.mainTexture = texA;
mask.transform.localScale = Vector3.zero;
}if (i == 1) {
this.renderer.material.mainTexture = texB;
mask.transform.localScale = new  Vector3 (0.1380835f, 0.1380835f, 0.1380835f);
}
}
//这个就是裁剪一张大图,变成一个个小的,贴到tile上
Vector2 TexOffset (int _idTex)
{
int a = (int)(_idTex / x);
int b = (int)(_idTex % x);
Vector2 offset = new Vector2 (b / x, (y - 1 - a) / y);
return offset;
}
Vector2 TexSize ()
{
Vector2 size = new Vector2 (1 / x, 1 / y);
return size;
}
}

Menu.cs,添加两个按钮。

using UnityEngine;
using System.Collections;
public class Menu : MonoBehaviour
{
public GameManager gameManager;
private GameManager _gameManger;
private bool start = true;void OnGUI ()
{
if (start) {
if (GUI.Button (new Rect (10, 10, 100, 50), "start")) {
start = false;
_gameManger = Instantiate (gameManager) as GameManager;
}
}
if (GUI.Button (new Rect (10, 70, 100, 50), "restart")) {
if (_gameManger != null) {
Destroy (_gameManger.gameObject);
print ("Destroy(_gameManger.gameObject);");
}
_gameManger = Instantiate (gameManager) as GameManager;
}}
}
没有更多推荐了,返回首页