文章申明:本文来自JacksonDunstan的博客系列文章内容摘取和翻译,版权归其所有,附上原文的链接,大家可以有空阅读原文:C++ Scripting( in Unity)

上一篇文章写完,有同学觉得有点晦涩,其实可以多认真看两遍源码,仔细琢磨一下,就会有一种茅塞顿开的感觉:D。今天继续上文,深入讨论一下C++作为游戏脚本的研究,本文会较长,需要写一些示例代码做讲解。

一、对C#指针(引用)的封装

在上文,我们提到,C++对C#的调用,是基于C#的函数指针(引用)而来的,比如在C++中:

//return transform handle  || function pointer name  || take a handle to the go
int32_t (*GameObjectGetTransform) (int32_t thiz);

为了拓展性,我们都会倾向于对于这种int32_t类型的数据做一个封装,自然容易想到用一个结构体(结构体默认为public)

namespace System
{
struct Object
{
int32_t Handle;
}
}

利用继承的特点,我们可以延伸出其他类型的结构体定义:

namespace UnityEngine
{
struct Vector3 {float x; float y; float z;}; struct Transform:System::Object
{
void SetPosition(Vector3 val)
{
TransformSetPosition(Handle, val);
}
} struct GameObject:System::Object
{
GameObject()
{
Handle = GameObjectNew();
} Transform GetPosition()
{
Transform transform;
transform.Handle = GameObjectGetTransform(Handle);
return transform;
}
}
}

二、对内存管理的控制

在C#部分,对于托管部分,是基于垃圾自动回收机制的,对于C++部分,相对较为简单的回收,可以基于计数的回收机制,当对象的引用计数为零的时候执行垃圾回收,那么对于我们可以定义两个全局变量来做相关的计数统计:

//global
int32_t managedObjectsRefCountLen;
int32_t *managedObjectsRefCounts; //..... //init
managedObjectsRefCountLen = maxManagedObjects;//c#会传入该数据
managedObjectsRefCounts = (int32_t*)calloc(maxManagedObjects, sizeof(int32_t));

这样在GameObject的初始化和解析的时候可以执行相关的内存管理操作:

GameObject()
{
Handle = GameObjectNew();
managedObjectsRefCounts[Handle]++;
} ~GameObject()
{
if(--managedObjectsRefCounts[Handle] == )
{
ReleaseObject(Handle);
}
}

对于其他的结构体,可以利用宏定义来实现类似的结构体定义中的操作。综上,可以实现在传递的时候对int32_t类型数据的封装,其次可以内嵌内存操作。整体代码对于c#的修改不多,对于C++的修改较多。

三、代码部分

对于c#部分的代码,基本不修改,只是修改一下Init函数,添加内存管理相关的数据和函数,具体代码如下:

....
//初始化函数及相关委托的修改
public delegate void InitDelegate(int maxManagedObjects, IntPtr releaseObject,
IntPtr gameObjectNew, IntPtr gameObjectGetTransform, IntPtr transformSetPosition);
....
...
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
...
#elif UNITY_EDITOR_WIN
...
#else
[DllImport("NativeScript")]
static extern void Init(int maxManagedObjects, IntPtr releaseObject,
IntPtr gameObjectNew, IntPtr gameObjectGetTransform,
IntPtr transformSetPosition);
...
#endif //新增释放
delegate void ReleaseObjectDelegate(int handle);
...
//修改Awake函数中对于初始化的操作
void Awake()
{
... //init c++ libraray
const int maxManagedObjects = ;
ObjectStore.Init(maxManagedObjects);
Init(maxManagedObjects,
Marshal.GetFunctionPointerForDelegate(new ReleaseObjectDelegate(ReleaseObject)),
Marshal.GetFunctionPointerForDelegate(new GameObjectNewDelegate(GameObjectNew)),
Marshal.GetFunctionPointerForDelegate(new GameObjectGetTransformDelegate(GameObjectGetTransform)),
Marshal.GetFunctionPointerForDelegate(new TransformSetPositionDelegate(TransformSetPosition)));
} ... //c# function for c++ to call
static void ReleaseObject(int handle)
{
ObjectStore.Remove(handle);
}
...

C++部分的代码修改较多,我就copy一下作者的工程源码吧 :D

// For assert()
#include <assert.h> // For int32_t, etc.
#include <stdint.h> // For malloc(), etc.
#include <stdlib.h> // For std::forward
#include <utility> // Macro to put before functions that need to be exposed to C#
#ifdef _WIN32
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT extern "C"
#endif ////////////////////////////////////////////////////////////////
// C# struct types
//////////////////////////////////////////////////////////////// namespace UnityEngine
{
struct Vector3
{
float x;
float y;
float z; Vector3()
: x(0.0f)
, y(0.0f)
, z(0.0f)
{
} Vector3(
float x,
float y,
float z)
: x(x)
, y(y)
, z(z)
{
}
};
} ////////////////////////////////////////////////////////////////
// C# functions for C++ to call
//////////////////////////////////////////////////////////////// namespace Plugin
{
using namespace UnityEngine; void (*ReleaseObject)(
int32_t handle); int32_t (*GameObjectNew)(); int32_t (*GameObjectGetTransform)(
int32_t thiz); void (*TransformSetPosition)(
int32_t thiz,
Vector3 val);
} ////////////////////////////////////////////////////////////////
// Reference counting of managed objects
//////////////////////////////////////////////////////////////// namespace Plugin
{
int32_t managedObjectsRefCountLen;
int32_t* managedObjectRefCounts; void ReferenceManagedObject(int32_t handle)
{
assert(handle >= && handle < managedObjectsRefCountLen);
if (handle != )
{
managedObjectRefCounts[handle]++;
}
} void DereferenceManagedObject(int32_t handle)
{
assert(handle >= && handle < managedObjectsRefCountLen);
if (handle != )
{
int32_t numRemain = --managedObjectRefCounts[handle];
if (numRemain == )
{
ReleaseObject(handle);
}
}
}
} ////////////////////////////////////////////////////////////////
// Mirrors of C# types. These wrap the C# functions to present
// a similiar API as in C#.
//////////////////////////////////////////////////////////////// namespace System
{
struct Object
{
int32_t Handle; Object(int32_t handle)
{
Handle = handle;
Plugin::ReferenceManagedObject(handle);
} Object(const Object& other)
{
Handle = other.Handle;
Plugin::ReferenceManagedObject(Handle);
} Object(Object&& other)
{
Handle = other.Handle;
other.Handle = ;
}
};
//宏定义操作
#define SYSTEM_OBJECT_LIFECYCLE(ClassName, BaseClassName) \
ClassName(int32_t handle) \
: BaseClassName(handle) \
{ \
} \
\
ClassName(const ClassName& other) \
: BaseClassName(other) \
{ \
} \
\
ClassName(ClassName&& other) \
: BaseClassName(std::forward<ClassName>(other)) \
{ \
} \
\
~ClassName() \
{ \
DereferenceManagedObject(Handle); \
} \
\
ClassName& operator=(const ClassName& other) \
{ \
DereferenceManagedObject(Handle); \
Handle = other.Handle; \
ReferenceManagedObject(Handle); \
return *this; \
} \
\
ClassName& operator=(ClassName&& other) \
{ \
DereferenceManagedObject(Handle); \
Handle = other.Handle; \
other.Handle = ; \
return *this; \
}
} namespace UnityEngine
{
using namespace System;
using namespace Plugin; struct GameObject;
struct Component;
struct Transform; struct GameObject : Object
{
SYSTEM_OBJECT_LIFECYCLE(GameObject, Object)
GameObject();
Transform GetTransform();
}; struct Component : Object
{
SYSTEM_OBJECT_LIFECYCLE(Component, Object)
}; struct Transform : Component
{
SYSTEM_OBJECT_LIFECYCLE(Transform, Component)
void SetPosition(Vector3 val);
}; GameObject::GameObject()
: GameObject(GameObjectNew())
{
} Transform GameObject::GetTransform()
{
return Transform(GameObjectGetTransform(Handle));
} void Transform::SetPosition(Vector3 val)
{
TransformSetPosition(Handle, val);
}
} ////////////////////////////////////////////////////////////////
// C++ functions for C# to call
//////////////////////////////////////////////////////////////// // Init the plugin
DLLEXPORT void Init(
int32_t maxManagedObjects,
void (*releaseObject)(int32_t),
int32_t (*gameObjectNew)(),
int32_t (*gameObjectGetTransform)(int32_t),
void (*transformSetPosition)(int32_t, UnityEngine::Vector3))
{
using namespace Plugin; // Init managed object ref counting
managedObjectsRefCountLen = maxManagedObjects;
managedObjectRefCounts = (int32_t*)calloc(
maxManagedObjects,
sizeof(int32_t)); // Init pointers to C# functions
ReleaseObject = releaseObject;
GameObjectNew = gameObjectNew;
GameObjectGetTransform = gameObjectGetTransform;
TransformSetPosition = transformSetPosition;
} // Called by MonoBehaviour.Update
DLLEXPORT void MonoBehaviourUpdate()
{
using namespace UnityEngine; static int32_t numCreated = ;
if (numCreated < )
{
GameObject go;
Transform transform = go.GetTransform();
float comp = (float)numCreated;
Vector3 position(comp, comp, comp);
transform.SetPosition(position);
numCreated++;
}
}

四、c#和Unity API 的导出

写到上面部分,基本对于c#和c++之间的操作有一个整体的较为完整的讲解,还有一个没有提起,那就是,怎么将 unity 的API导出给C++使用呢?作者给出了一个导出方式:JSON导出这让熟悉c#导出到lua的同学可以发现异曲同工之妙,其基本的导出设计为:

{
"Assemblies": [
{
"Path": "/Applications/Unity/Unity.app/Contents/Managed/UnityEngine.dll",
"Types": [
{
"Name": "UnityEngine.Object",
"Constructors": [],
"Methods": [],
"Properties": [],
"Fields": []
},
{
"Name": "UnityEngine.GameObject",
"Constructors": [
{
"Types": []
}
],
"Properties": [ "transform" ],
"Fields": []
},
{
"Name": "UnityEngine.Component",
"Constructors": [],
"Methods": [],
"Properties": [],
"Fields": []
},
{
"Name": "UnityEngine.Transform",
"Constructors": [],
"Methods": [],
"Properties": [ "position" ],
"Fields": []
}
]
}
]
}

整体设计简介易懂,当然,并不是所有的c#特性都可以被导出,json的导出不支持:Array/out and ref/ delegate/ generic functions and types/ struct types,不知后期作者是否考虑扩展对这些不兼容的特效的导出。

使用json导出,整体的修改和使用非常简单,比如对Component,需要添加对其transform特性的导出,那么只需要修改为:

"Properties":["transform"]

那么,保存后重新导出,就可以得到transform特性。

此外,对于.Net的一些API, 也可以使用JSON导出的方式:

{
"Path": "/Applications/Unity/Unity.app/Contents/Mono/lib/mono/unity/System.dll",
"Types": [
{
"Name": "System.Diagnostics.Stopwatch",
"Constructors": [
{
"Types": []
}
],
"Methods": [
{
"Name": "Start",
"Types": []
},
{
"Name": "Reset",
"Types": []
}
],
"Properties": [ "ElapsedMilliseconds" ],
"Fields": []
}
]
}

基于上面的各个部分,整体的游戏工程,可以分为2个部分:逻辑代码部分和binding相关的部分,作者给出的工程规划:

Assets
|- Game.cpp // Game-specific code. Can rename this file, add headers, etc.
|- NativeScriptTypes.json // JSON describing which .NET types the game wants to expose to C++
|- NativeScriptConstants.cs // Game-specific constants such as plugin names and paths
|- NativeScript/ // C++ scripting system. Drop this into your project.
|- Editor/
|- GenerateBindings.cs // Code generator
|- Bindings.cs // C# code to expose functionality to C++
|- ObjectStore.cs // Object handles system
|- Bindings.h // C++ wrapper types for C# (declaration)
|- Bindings.cpp // C++ wrapper types for C# (definition)
|- BootScript.cs // MonoBehaviour to boot up the C++ plugin
|- BootScene.unity // Scene with just BootScript on an empty GameObject

对于NativeScript来说,相当于基本的binding相关的东西,对于任何工程都适用,对于其他部分,则根据具体的工程来设计。基于这样的设计,需要做到三个基本规范:

1、需要定义一个全局的类:

public static class NativeScriptConstants
{
/// <summary>
/// Name of the plugin used by [DllImport] when running outside the editor
/// </summary>
public const string PluginName = "NativeScript"; /// <summary>
/// Path to load the plugin from when running inside the editor
/// </summary>
#if UNITY_EDITOR_OSX
public const string PluginPath = "/NativeScript.bundle/Contents/MacOS/NativeScript";
#elif UNITY_EDITOR_LINUX
public const string PluginPath = "/NativeScript.so";
#elif UNITY_EDITOR_WIN
public const string PluginPath = "/NativeScript.dll";
#endif /// <summary>
/// Maximum number of simultaneous managed objects that the C++ plugin uses
/// </summary>
public const int MaxManagedObjects = ; /// <summary>
/// Path within the Unity project to the exposed types JSON file
/// </summary>
public const string ExposedTypesJsonPath = "NativeScriptTypes.json";
}

2、NativeScriptConstants.ExposedTypesJsonPath需要指向前面所提到的json导出文件;

3、在C++代码部分,需要定义2个函数用来执行相关的更新

// Called when the plugin is initialized
void PluginMain()
{
} // Called for MonoBehaviour.Update
void PluginUpdate()
{
}

最后,整体的工程可以在github上找到,给出工程的链接:

jacksondunstan/UnityNativeScripting

Over!

Unity使用C++作为游戏逻辑脚本的研究(二)的更多相关文章

  1. Unity使用C++作为游戏逻辑脚本的研究

    文章申明:本文来自JacksonDunstan的博客系列文章内容摘取和翻译,版权归其所有,附上原文的链接,大家可以有空阅读原文:C++ Scripting( in Unity) 一.C#和C++的通信 ...

  2. Chrome自带恐龙小游戏的源码研究(二)

    在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: Cloud.co ...

  3. Chrome自带恐龙小游戏的源码研究(三)

    在上一篇<Chrome自带恐龙小游戏的源码研究(二)>中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替. 昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制. ...

  4. Chrome自带恐龙小游戏的源码研究(一)

    目录 Chrome自带恐龙小游戏的源码研究(一)——绘制地面 Chrome自带恐龙小游戏的源码研究(二)——绘制云朵 Chrome自带恐龙小游戏的源码研究(三)——昼夜交替 Chrome自带恐龙小游戏 ...

  5. Unity 2D游戏开发教程之使用脚本实现游戏逻辑

    Unity 2D游戏开发教程之使用脚本实现游戏逻辑 使用脚本实现游戏逻辑 通过上一节的操作,我们不仅创建了精灵的动画,还设置了动画的过渡条件,最终使得精灵得以按照我们的意愿,进入我们所指定的动画状态. ...

  6. 使用Unity创建塔防游戏(Part1)

    How to Create a Tower Defense Game in Unity - Part1 原文作者:Barbara Reichart 文章原译:http://www.cnblogs.co ...

  7. 使用Unity创建塔防游戏(Part3)—— 项目总结

    之前我们完成了使用Unity创建塔防游戏这个小项目,在这篇文章里,我们对项目中学习到的知识进行一次总结. Part1的地址:http://www.cnblogs.com/lcxBlog/p/60759 ...

  8. 使用Unity做2.5D游戏教程(二)

    最近在研究Unity 3D,看了老外Marin Todorov写的教程很详细,就翻译过来以便自己参考,翻译不好的地方请多包涵. 这是使用Unity 游戏开发工具制作一个简单的2.5D 游戏系列教程的第 ...

  9. 使用Unity做2.5D游戏教程(一)

    最近在研究Unity 3D,看了老外Marin Todorov写的教程很详细,就翻译过来以便自己参考,翻译不好的地方请多包涵. 如果你不了解2.5D游戏是什么,它基本上是个3D游戏而你可以想象是压扁的 ...

随机推荐

  1. (NO.00005)iOS实现炸弹人游戏(九):游戏主角(二)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 上篇介绍了游戏主角的初始化方法,下面我们一次来实现主角的其他方 ...

  2. linu下C语言之BMP图片操作编程(中)

    http://blog.csdn.net/morixinguan/article/details/50719472 关于BMP图的介绍之前已经说过了,最近要用到,又要重新开始学习. 现在实现一个让bm ...

  3. 【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  4. J2EE进阶(九)org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    org.hibernate.LazyInitializationException: could not initialize proxy - no Session 前言 在<many-to-o ...

  5. 安卓一键分享到qq,微信,微博,官方SDK非第三方

    当我们项目中需要集成分享功能时,我们通常会采取一下几个办法: 1.调用系统自带分享 优点:简单快速,几行代码搞定,不需添加任何额外包: 缺点:系统会调出手机内部所有带分享功能的APP,且界面风格跟随系 ...

  6. Android:ADB server didn't ACK或者adb server is out of date. killing解决办法

    欢迎关注公众号,每天推送Android技术文章,二维码如下:(可扫描) 出现这个原因我个人感觉有两个.一.5037端口被别的程序或者进程占用:二.adb占用的不是5037端口.很多人仅仅知道第一种二忽 ...

  7. Http协议处理器——Http11Processor

    Http11Processor组件提供了对Http协议通信的处理,包括对套接字的读取过滤.对http协议的解析并封装成请求对象.http响应对象的生成.套接字的过滤写入等等操作. 喜欢研究java的同 ...

  8. Web开发技术的演变

    原文出处: WildFly   欢迎分享原创到伯乐头条 受到好文<Web开发的发展史>(英文)激发的灵感,写下我对web开发技术的认识. 1. 静态页面时代 大学时候,上机还得换卡穿拖鞋, ...

  9. outlook邮箱邮件与企业邮箱同步(outlook本地文件夹邮件,web邮箱里没有)

    用惯了outlook2010, 问题:今天将邮件放到自定义文件夹后,发现在web邮箱中看不到邮件了.不能同步到企业邮箱. 解决忙了一天,才知道是账户类型问题,pop3类型,只下载不上传.所以outlo ...

  10. 【Android】自定义ListView的Adapter报空指针异常解决方法

    刚刚使用ViewHolder的方法拉取ListView的数据,但是总会报异常.仔细查看代码,都正确. 后来打开adapter类,发现getView的返回值为null. 即return null. 将n ...