2014年9月5日星期五

Unity中实现全局管理类的几种方式 - andong777

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Unity中实现全局管理类的几种方式 - andong777  阅读原文»

(搬运自我在SegmentFault的博客)

如何在Unity中实现全局管理类?由于Unity脚本的运行机制和面向组件编程(COP)的思想,实现起来和普通的方式略有差别。

第一种方式是使用静态类。适合存储一些全局的变量,如游戏当前关卡、玩家得分等。
实现方式和普通的C#静态类没有差别。注意使用静态类就没有必要继承MonoBehaviour了。

如果要实现复杂一些的全局控制,如切换游戏关卡等操作,更常用的方式是使用单例类。
单例类的实现又分为两种:

  • 继承自MonoBehaviour的单例类
  • 纯C#的单例类

前者的优点是:

  • 可以在Inspector中显示,便于赋值和查看变量等;
  • 可以利用MonoBehaviour的接口;
  • 可以使用Coroutine。
  • 等等。

缺点也很多,主流的观点是能不继承MonoBehaviour就不要继承。

纯C#的单例类

实现起来简洁,易于理解。

普通的写法,不考虑多线程

public class MyClass
{
private static readonly MyClass _instance = new MyClass();
public static Class Instance {
get {
return _instance;
}
}

private MyClass() {}
}

线程安全的写法

检查两次。C#中使用lock关键字。

public class MyClass
{
private static volatile MyClass _instance;
private static object _lock = new object();

public static MyClass Instance
{
get
{
if (_instance == null)
{
lock(_lock)
{
if (_instance == null)
_instance = new MyClass();
}
}
return _instance;
}
}

private MyClass() {}
}

基于MonoBehaviour的单例类

普通的写法

利用了Unity的运行机制,从Awake处获取Unity创建的对象作为单例。
注意在Unity中不要使用new来创建MonoBehaviour实例。

public class MyClass : MonoBehaviour
{
static MyClass _instance;

void Awake () {
_instance = this;
}

public static MyClass Instance {
get {
// 不需要再检查变量是否为null
return _instance;
}
}
}

持久化的写法

在多个场景中保存单例。又有两种方法。

第一种是使用DontDestroyOnLoad方法,告诉Unity不要销毁实例所在的对象,然后将脚本挂到某个GameObject上:

public class MyClass : MonoBehaviour
{
static MyClass _instance;

void Awake () {
_instance = this;
// 防止载入新场景时被销毁
DontDestroyOnLoad(_instance.gameObject);
}

public static MyClass Instance {
get {
return _instance;
}
}
}

上面这个方法有个弊端,必须要从挂载了这个单例的GameObject所在的场景启动,否则会找不到GameObject对象。但是开发和测试时我们经常会单独启动一个场景。

另一种方法会创建一个GameObject,然后将单例挂载到其上:

public class MyClass : MonoBehaviour {

static MyClass _instance;

static public MyClass Instance
{
get
{
if (_instance == null)
{
// 尝试寻找该类的实例。此处不能用GameObject.Find,因为MonoBehaviour继承自Component。
_instance = Object.FindObjectOfType(typeof(MyClass)) as MyClass;

if (_instance == null)// 如果没有找到
{
GameObject go = new GameObject("_MyClass");// 创建一个新的GameObject
DontDestroyOnLoad(go);// 防止被销毁
_instance = go.AddComponent<MyClass>();// 将实例挂载到GameObject上
}
}
return _instance;
}
}
}

本文链接:Unity中实现全局管理类的几种方式,转载请注明。

小菜汇编基础和学习技巧小结(一) - 凌晨的搜索者  阅读原文»

以下小结纯属小菜自学过程产生的dump,大神请飘过!

汇编是一门庞大复杂的学问,在计算机的世界里差不多无所不入。很多编程领域都会或多或少跟汇编打交道。本人不是科班出身的程序员,所以很多基础都为零,学历也很低。因此学习汇编的难度可想而知。不过还是凭自己的耐力,掌握了少许的知识。下面做个小小的总结,分享给和我一样想入门汇编的朋友们。

1.参数直接传值和传入数值变量的区别

void SetX(int x)
{
x
= 6;
}

int _tmain(int argc, _TCHAR* argv[])
{
SetX(
7);//1.直接传数值

int x = 7;
SetX(x);
//2.传一个有初始值的变量
return 0;
}

这两种情况运行的结果没有任何区别,但是汇编代码却有些细微的区别:

第一种情况:

push 7
call SetX (01011E5h) //1.直接传值
add esp,4 //栈指针向下移动

第二种情况多了一个将及时数缓存于内存x的步骤:

mov dword ptr [x],7 //首先将及时数7传入双字型内存地址为x的内存中
mov eax,dword ptr [x] //然后再将x里的值传到eax寄存器里
push eax //开始压入参数值
call SetX (01011E5h)
add esp,4 //栈指针向下移动,清除变量
xor eax,eax //清空eax的值

2.有返回值与没返回值的区别
在c++或其他高级语言,我们可以一眼看出返回值和返回类型。但是汇编里貌似有些乱。在上面的例子里,Call完SetX之后,后面就紧跟着开始清除变量值。这个还不能说明有没有返回值。再来看一个例子就能明显分辨有没有返回值了。

int SetX(int x)
{
x =
6;
return x;
}

int _tmain(int argc, _TCHAR* argv[])
{
SetX(
7);//1.有返回值,但是没有引用
int x=SetX(7);//2.有返回值,并且引用了
return 0;
}

这个例子里,第一个函数调用没有赋值的动作,且看对应的汇编代码:

push 7
call SetX (012811EAh)
add esp,4

这个情况看起来就如果本身就不带返回值的情况。看起来没法这样分辨了,但是其实如果是这样调用,不就个没带返回值效果是一样的了。判别有没有返回值已经没有意义了。

push 7
call SetX (012811EAh)
add esp,4
mov dword ptr [x],eax
xor eax,eax

上面的第二种情况,则明显多了一个mov指令。在调用完函数之后,立马来个mov,显然是返回了一个值存于eax,需要立即保存在地址x的内存里。很明显是带返回值的。

3.参数传递中值传递和引用传递的区别

void SetX(int x)
{
x
++;
}
void SetY(int &y)
{
y
++;
}

int _tmain(int argc, _TCHAR* argv[])
{
int x = 0;
int y = 0;
SetX(x);
//1.值传递,x的值不会变
SetY(y);//2.引用传递,y的值会产生变化
return 0;
}

在汇编码里,更容易看出两个函数的不同
对于第一个函数:

mov eax,dword ptr [x]
push eax
call SetX (01611F4h)
add esp,4

明显是将地址为x的内存里的值传入eax,再经由eax传给函数。而对于第二个函数,先是用lea取一块有效的内存区域用于存储计算过程中生成的结果。然后将这个刚刚建立的内存区域的地址传给eax,以便可以直接操作y区域的内存。

lea eax,[y]
push eax
call SetY (01611EFh)
add esp,4
xor eax,eax


这几个小细节在汇编,甚至反汇编的时候非常有使用价值。


本文链接:小菜汇编基础和学习技巧小结(一),转载请注明。

阅读更多内容

没有评论:

发表评论