zoukankan      html  css  js  c++  java
  • Unity使用C++作为游戏逻辑脚本的研究(二)

    文章申明:本文来自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] == 0)
         {
             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 = 1024;
          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 >= 0 && handle < managedObjectsRefCountLen);
            if (handle != 0)
            {
                managedObjectRefCounts[handle]++;
            }
        }
     
        void DereferenceManagedObject(int32_t handle)
        {
            assert(handle >= 0 && handle < managedObjectsRefCountLen);
            if (handle != 0)
            {
                int32_t numRemain = --managedObjectRefCounts[handle];
                if (numRemain == 0)
                {
                    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 = 0;
            }
        };
     //宏定义操作
    #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 = 0; 
            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 = 0;
        if (numCreated < 10)
        {
            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 = 1024;
     
        /// <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!

  • 相关阅读:
    yum -y list java* 查看当前java的版本
    max number of threads [1024] for user [lish] likely too low, increase to at least [2048]
    max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
    max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]
    linux文件描述符open file descriptors与open files的区别
    samba 报错
    检测端口是否开启
    sqlserver2008出现数据库主体在该数据库中拥有架构,无法删除的解决方案
    sqlserver生成表结构文档的方法
    胡萝卜周博客 软件下载
  • 原文地址:https://www.cnblogs.com/zblade/p/8990750.html
Copyright © 2011-2022 走看看