刨根问底U3D---从一个空类说起

这篇文章包含哪些内容

这篇文章从一个Empty的MonoBehaviour入手,首先讨论一下C#的修饰符internal,default,virtual,sealed

接着讨论一下MonoBehaviour,Component,Tranform,GameObject之间的关系 及脚本之间的如何互相关联

从一个空类说起

using UnityEngine;
using System.Collections;

public class EmptyClass : MonoBehaviour {

    // Use this for initialization
    void Start () {

    }

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

    }
}

随便在工程中建立一个C#脚本,Unity就替我们生成了这个空类. 里面包含有两个方法Start,Update.

首先让我们抛开这两个方法的作用不谈,先来谈谈这两个函数的写法.

EmptyClass : MonoBehaviour 这个很简单,表示Empty继承自MonoBehaviour 。在Java或者AS中用extends这里用分号 这个无所谓了.

不过 void Start () {} 这行代码就很奇怪了. 前面没有任何的修饰符(priate,protected,public). 那这到底什么意思呢?

不加scope修饰符则为private(不建议这样使用)

google一下有篇文章给出了很好地解释

Should private members be declared explicitly as private in idiomatic C#?
http://www.4byte.cn/question/543979/should-private-members-be-declared-explicitly-as-private-in-idiomatic-c.html

文章说的很清楚,在C#中如果不加任何的scope修饰符则默认为private,不过几个Answer(我也赞同)不要使用这种方式,还是显示的加上private为好。

internal的作用域为一个工程

internal这个应该不太能用到,不过还是提一句吧. 也是在我无意中搜索到的,网上文章说的不是很清楚 所以我重新做了一个Demo,

正好也验证了defult的作用域

这张图就是整个workspace的结构,每个Solution就是一个个的工程,

而按图来说如果在LibSolution中定义了internal则MainSolution是访问不到的

LibA中定义了internal的变量及方法

在同一个Solution内,其子类和非子类对其的访问权限

在不同的Solution下对其的访问权限

virtual表示子类可以override,sealed不行,C#不写virtual则默认为sealed

这个很好理解,不过正好和Java反过来,Java是需要受到final掉才可以,如果有兴趣可以阅读这篇文章

http://stackoverflow.com/questions/1327544/what-is-the-equivalent-of-javas-final-in-c

Unity会自动调用Start,Update等方法即使他们为private

让我们再回头看Unity为我们创建的那个空类,里面只有两个方法 并且两个方法都没有写作用域,我们刚才也提到 没写作用域就是private的

那Unity为何能调用到这个函数呢?

http://stackoverflow.com/questions/24772681/c-sharp-when-do-i-need-override-when-dont-i-need-it

万能的stackoverflow 有人已经给出了解释 :单从语言的角度来说是不行的,除非你使用反射(反射可以访问private?),不过Unity并没有这么使用

我也不知道他们怎么做的...

不管怎样 就可以理解为Unity搞定了这个问题 :)

MonoBehaviour,Component,Tranform,GameObject

老实的说 搞定了上面的一坨东西 其实对于我们理解Unity没有任何的帮助...(万恶的刨根问底啊)

那所建立的脚本和场景上的对象(GameObject)到底是什么关系呢?

了解GameObject

网上的教程 里面都会出现GameObject,Transfomr等等 然后让你新建一个脚本往一个对象上一拽 ok 开始操作写代码

可以到底脚本是怎么和那个对象关联起来的呢?

其实 GameObject是一个纯粹的容器,一副骨架 什么都没有 甚至连显示都不行。其唯一的作用就是可以往上添加Component,

想成一个人的模型 那就是 添加了眼睛 你就看得见了,添加了嘴 你就能说话了。

同理想要碰撞就加BoxCollieder,想要声音就加AudioSource,即使我们用菜单创建出一个Cube它本身也是由

Transform+Cube+BoxCollider+MeshRender 这几个Component构成的.

我想你一定会发现,当你创建一个GameObject时候Unity已经为你自动添加了一个Component,那就是Transform。

从Inspector中也可以看出,每个GameObject都包含了一个Transform组件。

把GameObject连接起来的Transform

这个Component并不像他在Inspector中表现的那么简单 只负责x,y,z

Transform还有另外一个作用 就是连接GameObject, 比如说有两个Cube A B, 把B拖到A的下面 就变成了A得子

当改变A在世界坐标中的位置时候B也随之改变,这其中的功劳就是Transform。

在Unity中写的脚本就是Component

在我们进一步的研究Transform之前还是回过头来看一下在文章开头提到的那个空类

public class EmptyClass : MonoBehaviour

也就是说EmptyClass(我自己起的名字) 是继承自MonoBehaviour , 查看Assmbly Browser 会看到 MonoBehaviour是继承自Behaviour

而Behaviour呢 自然就是继承自Component喽。

也就是说 每个建立的脚本都是捆绑在GameObject上面的一个Component.

脚本之间如何互相关联

有了以上只是的铺垫 就可以进入这片文章的主题了....

还是由问题入手:

我在一个GameObject上面挂在两个Class的实例(注意是实例而不是类,挂载就相当于new出来一个对象)ClassA,ClassB

那我如何能让ClassA访问到ClassB呢?

Unity里面拖拽

这个也是Unity很牛逼的一个地方,只要在ClassA中建立一个public的变量类型是ClassB 同理在ClassB中建立一个public的变量类型为ClassA

在编辑器中直接互相拖拽一下....

突然发现,咦 没法拖拽啊... 恩 是的 因为Unity编辑器里面的拖拽绑定方式是GameObject级别的.在编辑器里面可以把两个GameObject通过这种方式

进行互相或者单方向的引用,但是GameObject内部的Component是不可以的.

GameObject内部的互相引用

当写的两个脚本在同一个GameObject内部时候想互相引用就需要用到gameObject这个变量了

注意在脚本内部可以访问到两个gameObject, 一个是大写的GameObject 一个是小写的gameObject 大写的GameObject相当于场景级的

也就是刚才想尝试直接在Unity里面拖拽时候操作一样,通过他提供的函数可以找到当前场景中所有的GameObject, 而小写的gameObject呢

其实就是指的是当前Component被捆绑上的GameObject

当确定同一个类型的Component只有一个时候及可以使用

ClassA AInstance = gameObject.GetComponent<ClassA> ();
ClassB BInstance = gameObject.GetComponent ("ClassB") as ClassB;

两种方式都行,如果有多个Component的话则需要使用gameObject.GetComponents() 然后再进一步的for循环查找等. 相关的可以看下官方的文档

这样也就实现了之前说的目的,在同一个GameObject内部 实现了脚本(Component)之间的互相引用。

两个互相连接的GameObject之间的内部脚本互相引用

把问题引申一步,还是那两个脚本ClassA,ClassB,不过这回不是绑在同一个GameObject上面 而是分辨绑定在两个GameObject

Parent(ClassA),Child(ClassB)

首先还是来尝试拖拽,虽然无法在Unity的编辑器中通过拖拽互相引用脚本(Componet),不过绑定GameObject是可以.

所以只需要建立两个public的变量 然后类型都是GameObject,在Unity里面互相拖拽引用,

最后在Start函数时候通过已经绑定好的gameObject调用其GetComponent方法即可

problem solved~

的确 这个方法是可行,不过有个更好的方法就是使用Transform.

刚才有提到Transform是一个很特殊的Component,其内部保留着GameObject之间的显示树结构.

所以就上面的例子来说 当要从Child访问到Parent 只需要在Child对应的脚本里面写

transform.parent.gameObject.GetComponent<ClassA>() 即可

返过来就相对麻烦一点,因为无法保证一个parent只有一个child,所以无法简单的使用

transform.child.gameObject这样访问, 但是Unity给我们提供了一个很方便的函数,那就是Find.(Find=FindChild,FindChild 也许即将废弃? API中并无该函数说明)

需要注意的是Find只能查找其Child,举个复杂点的例子

Parent->ChildA->ChildB->ChildC

当在Patent中想要找到ChildC中的一个Component时候 调用 transform.Find("ChildA/ChildB/ChildC").gameObject;

但是如果在ChildA中 同样需要找到ChildC的一个Component时候 需要调用 transform.FindChild ("ChildB/ChildC").gameObject;

总结

脚本之间需要互相引用的话,在不借助Unity编辑器帮忙的情况下 就需要首先通过Transform找到对应的GameObject节点,

在通过GameObject的GetComponent方法找到对应的脚本

That‘s All

Best
Eran

时间: 2024-10-07 14:58:56

刨根问底U3D---从一个空类说起的相关文章

一个空类被编译器编译后产生了哪些默认函数

为何空类的大小不是0呢? 为了确保两个不同对象的地址不同,必须如此. 类的实例化是在内存中分配一块地址,每个实例在内存中都有独一无二的二地址. 同样,空类也会实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化后就有独一无二的地址了. 所以,空类的sizeof为1,而不是0. 一个空的class在C++编译器处理过后就不再为空,编译器会自动地为我们声明一些member function, 如果你写 class A{}; 编译器处理后,就相当于: class A { public: A()

C++语法小记---经典问题之一(一个空类包含什么)

问题:一个空类包含什么 空的构造函数 拷贝构造函数(浅拷贝) 重载赋值操作符函数(浅拷贝) 析构函数 取址运算符 取址运算符const 注意 所有的这些默认函数,只有在代码中调用了才会生成,否则也不会生成 例子 1 class Test1 2 { 3 4 }; 5 6 // Test1等价于Test2 7 8 class Test2 9 { 10 public: 11 Test2() // 空的构造函数 12 13 Test2(Test2& t) // 拷贝构造函数(浅拷贝) 14 15 Tes

【C++缺省函数】 空类默认产生的6个类成员函数

1.缺省构造函数. 2.缺省拷贝构造函数. 3. 缺省析构函数. 4.缺省赋值运算符. 5.缺省取址运算符. 6. 缺省取址运算符 const. <span style="font-size:18px;"> class A { public: A(){}//缺省构造函数 A(const A&){}//拷贝构造函数 ~A(){}//析构函数 A&operator=(const A&){}//赋值运算符 A*operator&(){}//取址运算

C++ 空类及类的大小(C++面向对象模型有提及)

初学者在学习面向对象的程序设计语言时,或多或少的都些疑问,我们写的代码与最终生编译成的代码却 大相径庭,我们并不知道编译器在后台做了什么工作.这些都是由于我们仅停留在语言层的原因,所谓语言层就是教会我们一些基本的语法法则,但不会告诉我们为什么这么做?今天和大家谈的一点感悟就是我在学习编程过程中的一点经验,是编译器这方面的一个具体功能. 首先:我们要知道什么是类的实例化,所谓类的实例化就是在内存中分配一块地址. 那我们先看看一个例子: #include<iostream.h> class a {

为什么C++空类的实例大小为1

[为什么C++空类的实例大小为1] 每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以大小为1. 参考:http://bbs.csdn.net/topics/370134485 为什么C++空类的实例大小为1,布布扣,bubuko.com

C++空类大小

class a {};class b{};class c:public a{ virtual void fun()=0;};class d:public b,public c{}; 类a,b明明是空类,它的大小应该为为0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1. 而类c是由类a派生而

C++空类编译器自动生成的6个成员函数

一.问题 在C++中,编译器会为空类提供哪些默认成员函数?分别有什么样的功能呢? 二.详解 1.空类,声明时编译器不会生成任何成员函数 对于空类,编译器不会生成任何的成员函数,只会生成1个字节的占位符. 有时可能会以为编译器会为空类生成默认构造函数等,事实上是不会的,编译器只会在需要的时候生成6个成员函数:一个缺省的构造函数.一个拷贝构造函数.一个析构函数.一个赋值运算符.一对取址运算符和一个this指针. 代码: [html] view plaincopy #include <iostream

堆(stack) 之 c 和 c++模板实现(空类默认成员函数 初谈引用 内联函数)

//stack 的基本操作 #include <iostream> using namespace std; const int maxn = 3; typedef struct Stack { //NumType num; int num; }Stack; int top = 0;//当前元素位置的上一个元素 Stack stack[maxn]; bool is_empty(); bool is_full(); int pop(); void push(const int &key)

C++空类以及没有成员变量的类的大小

关于C++中空类的大小为1,我们大家都有所了解,但是除了空类之外的其他一些没有成员变量的类的大小,还是有很多不明之处的. 我们来看如下一个例子: #include<iostream> using namespace std; class a {}; class b{}; class c :public a{ virtual void fun() = 0; }; class d :public b, public c{}; int main() { cout << "siz