2015-11-23 10:21:23 qinyuanpei 阅读数 13393
  • 3D游戏开发基础理论速学教程

    为了让大家更容易掌握3D游戏开发所涉及的理论基础,本课程采用理论+代码实战的模式为大家精心讲解。学习完本课程可以快速提升3D游戏开发技能。本课程涉及的理论基础适合Cocos2d-x,Unity3D等常用的手机游戏开发引擎。想学习本课程的同学需要一定的高中数学基础,及初级的游戏开发基础。本课程演示代码采用Cocos2d-x V3.8 C++。

    134 人正在学习 去看看 刘永

  各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com。虽然Unity3D引擎依靠强大的跨平台能力睥睨高手林立的游戏引擎世界,我们在使用Unity3D游戏引擎的时候基本上不会去接触底层的东西,可是有时候面对某些奇葩的要求的时候,我们就不得不考虑使用C++这样的语言来为其编写相关的插件。你如果问我是什么样的奇葩要求,比如接入蓝牙手柄来控制游戏、接入类似街机的设备来控制游戏、接入同一个游戏到两个不同的设备上并响应不同的控制……诸如此类的种种问题,可能目前在Unity3D引擎中找不到解决方案,这个时候写C++插件就变成了一种刚性需求,这就是我们今天要来一起探讨的问题。

  Unity3D主要使用C#进行开发,所以为Unity3D编写插件本质上就是让C#调用C++代码。目前主要有C++ CLR和C++ Native两种实现方法,其中C++ CLR可以理解为运行在.Net CLR即公共语言运行库上的C++代码,这种代码是托管的C++代码,目前并没有被C++标准承认,因为它更像是C++和C#两种语言的混合代码,这种代码的优势是可以像普通的.NET库一样被C#调用,考虑到Unity3D建立在和.Net类似的Mono上,因此这种方式应该是我们的最佳实践方案;C++ Native则是指传统的C++ 动态链接库,通过DllImport在C#中进行包装后在C#中进行调用,相对地这种方式调用的是非托管的C++代码,这种方式相信接触过Windows开发的朋友应该不会感到陌生啦,它是一种更为普遍的方法,例如我们要接入苹果官方SDK的时候,需要对Object C的代码进行封装后交给C#去调用,而这里使用的方法就是DllImport了。

  好了,下面我们来看看两种方式各自是如何实现的吧!这里博主使用的开发环境是Windows 8.1 32bit 和 Visual Studio 2012,Unity3D的版本为4.6版本。

C++ CLR

创建一个C++ CLR类库项目

  首先我们按照下图中的步骤创建一个C++ CLR项目:

截图是件讨厌的事情,虽然懒惰的人们都喜欢

请注意.Net版本问题,重要的事情说三遍,不认真看这里的人出现问题就不要到我这里来评论了,我最讨厌连文章都没有看明白就来和你纠缠不清的人,谢谢。创建好项目后请打开项目属性窗口设置【公共语言运行时支持】节点的值为【安全 MSIL 公共语言运行时支持(/clr:safe)】好了,下面我们找到CLR4Unity.h文件,添加ExampleClass声明:

/// <summary>
/// 一个简单的托管C++示例类
/// </summary>
public  ref  class ExampleClass
{
    public:

    /// <summary>
    /// 产生一个介于min和max之间的整型随机数
    /// <returns>整型随机数</returns>
    /// <param name="min">最小值</param>
    /// <param name="max">最大值</param>
    /// </summary>
    static int  Random(int min,int max)
    {
        //注意在托管的C++中使用gcnew来代替new
        //我承认C++写CLR代码略显奇葩像是C++和C#语法的混合
        return (gcnew System::Random)->Next(min,max);
    }

    /// <summary>
    /// 计算一个整数的平方
    /// <returns>整型数值</returns>
    /// <param name="a">需要平方的数值</param>
    /// </summary>
    static int Square(int a)
    {
        return a * a;
    }

    /// <summary>
    /// 返回两个数中的最大值
    /// <returns>整型数值</returns>
    /// <param name="a">参数1</param>
    /// <param name="b">参数2</param>
    /// </summary>
    static int Max(int a,int b)
    {
        if(a<=b){
            return b;
        }else{
            return a;
        }
    }
};

显然我们这里定义了三个简单的方法,注意到第一个方法Random依赖于System.Rnadom类,而在托管的C++中是使用gcnew来代替new这个关键字的,所以请尽情感受C#和C++的混搭语法风格吧!这样我们就可以编译得到CLR4Unity.dll这个类库,将这个文件复制到Unity3D项目中的Plugins目录下下,然后将其加入项目引用列表。如果你以为引用就是:

using CLR4Unity;

呵呵,我严重怀疑你对.Net的熟悉程度。你没有添加对CLR4Unity.dll的引用,你到底在using什么啊?

先添加引用然后using

如果你对.NET熟悉到足以无视这里的一切,请闭上眼接着往下看,哈哈!

在C#中添加引用及方法调用

  接下来我们在Unity3D中创建一个脚本PluginTest.cs,然后在OnGUI方法增加下列代码。可是你要以为这些代码就应该写在OnGUI方法中,抱歉请你先去了解MonoBehaviour这个类。什么?添加了这些代码报错?没有using的请自行面壁:

//调用C++ CLR中的方法
if(GUILayout.Button("调用C++ CLR中的方法", GUILayout.Height (30))) 
{
    Debug.Log("调用C++ CLR中的方法Random(0,10):" + ExampleClass.Random(0,10));
    Debug.Log("调用C++ CLR中的方法Max(5,10):" + ExampleClass.Max(5,10));
    Debug.Log("调用C++ CLR中的方法Square(5):" + ExampleClass.Square(5));
}

C++ Native

创建一个C++动态链接库项目

  首先我们按照下图中的步骤来创建一个C++ Win32项目:

不要问我从哪里来

我的故乡在远方

好了,接下来我们找到Native4Unity.cpp写入下列代码:

// Native4Unity.cpp : 定义 DLL 应用程序的导出函数。
//

#include "stdafx.h"
//为了使用rand()函数引入C++标准库
#include "stdlib.h"

/// <summary>
/// 产生一个介于min和max之间的整型随机数
/// <returns>整型随机数</returns>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
/// </summary>
extern "C" __declspec(dllexport) int Random(int min,int max)
{
    return rand() % (max - min + 1) + min;
}

/// <summary>
/// 返回两个数中的最大值
/// <returns>整型数值</returns>
/// <param name="a">参数1</param>
/// <param name="b">参数2</param>
/// </summary>
extern "C" __declspec(dllexport) int Max(int a ,int b)
{
    if(a<=b){
       return b;
    }else{
       return a;
    }
}

/// <summary>
/// 计算一个整数的平方
/// <returns>整型数值</returns>
/// <param name="a">需要平方的数值</param>
/// </summary>
extern "C" __declspec(dllexport) int Square(int a)
{
    return a * a;
}

和C++ CLR类似,我们使用标准的C++语言来实现同样的功能。注意到rand()这个函数是C++标准库里的内容,所以我们在文件开头增加了对stdlib.h这个头文件的引用。这里需要注意的一点是:所有希望使用DllImport引入C#的C++方法都应该在方法声明中增加__declspec(dllexport)关键字,除非它在.def文件中对这些方法进行显示声明。关于.def文件的相关定义大家可以到MSDN上检索,这些都是属于C++编译器的内容,这里不再详细说了。

在C#中使用DllImport封装方法

  将编译好的Native4Unity.dll复制到Plugins目录中后,下面我们要做的事情就是在C#里对这些方法进行封装或者说是声明:

 [DllImport("Native4Unity")]
 private extern static int Random(int min, int max);

 [DllImport("Native4Unity")]
 private extern static int Max(int a, int b);

 [DllImport("Native4Unity")]
 private extern static int Square(int a);

然后就是简单地调用啦:

//调用C++ Native中的方法
if(GUILayout.Button("调用C++ Native中的方法", GUILayout.Height (30))) 
{
    Debug.Log("调用C++ Native中的方法Random(0,10):" + Random(0, 10));
    Debug.Log("调用C++ Native的方法Max(5,10):" + Max(5, 10));
    Debug.Log("调用C++ Native中的方法Square(5):" + Square(5));
}

最终程序的运行效果如图:

这个结果来之不易请大家珍惜

2017-01-20 20:16:59 zzlyw 阅读数 4846
  • 3D游戏开发基础理论速学教程

    为了让大家更容易掌握3D游戏开发所涉及的理论基础,本课程采用理论+代码实战的模式为大家精心讲解。学习完本课程可以快速提升3D游戏开发技能。本课程涉及的理论基础适合Cocos2d-x,Unity3D等常用的手机游戏开发引擎。想学习本课程的同学需要一定的高中数学基础,及初级的游戏开发基础。本课程演示代码采用Cocos2d-x V3.8 C++。

    134 人正在学习 去看看 刘永

前言

Unity3D是一款非常优秀的游戏引擎,可以使用C#作为脚本语言进行编程。但是有的时候我们需要运行一些第三方的程序,例如C++实现的一些算法。我们需要将C++的代码以DLL的形式嵌入到Unity3D的程序中。这时候就要用到C#调用C++ DLL的方法。本文给出一个最简单的调用方式,实现了一些简单数据类型的传递。

 

1  C++中DLL的生成

在Windows平台上,首先使用VisualStudio2013来创建一个C++的Win32控制台应用程序。工程名设置为TestDLL。


在应用程序设置向导中,选择应用程序类型为DLL。


由于我们使用的Unity3D分32位和64位,所以我们需要在配置管理器中设置与Unity一致的位数。我的Unity3D是64位的,所以我做了如下设置。


然后将下面的代码粘贴到TestDll.cpp中。

#include "stdafx.h"

extern "C"_declspec(dllexport) void  fun0(int &a)
{
	a = 111;
}
extern "C"_declspec(dllexport) void  fun1(int *b)
{
	*b = 222;
}

extern "C"_declspec(dllexport) void  fun2(int c[])
{
	c[0] = 333;
}

extern "C"_declspec(dllexport) void  fun3(char *d)
{
	*d = 'B';
}


然后点击生成解决方案,就可以在“工程根目录\x64\Release”路径下看到生成的TestDll.dll。


2  在Unity3D中调用C++的DLL

创建一个新工程和一个新场景,并创建一个脚本UseDll.cs。脚本的内容如下:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class UseDll : MonoBehaviour {

    [DllImport("TestDll")]
    private static extern void fun0(ref int a);
    [DllImport("TestDll")]
    private static extern void fun1(ref int b);
    [DllImport("TestDll")]
    private static extern void fun2(int[] c);
    [DllImport("TestDll")]
    private static extern void fun3(ref char d);

	void Start () {

        //初始化a、b、c、d 几个数据
        int a = 0;
        int b = 0;
        int[] c=new int[5];
        for(int i=0;i<5;i++)
        {
            c[i] = -1;
        }
        char d = 'A';
        

        //调用dll中的函数,对数据进行修改
        fun0(ref a);
        Debug.Log(a);
        fun1(ref b);
        Debug.Log(b);
        fun2(c);
        Debug.Log(c[0]);
        fun3( ref d);
        Debug.Log(d);
	
	}

	void Update () {
	
	}
}


将这个脚本绑定到摄像机上,并且要把上一步中生成的TestDll.dll拷贝一份放到“Unity工程根目录\Assets\Plugins”目录下。默认的时候Assets下是没有Plugins文件夹的,需要自己创建。一切就绪后,可以运行程序,在控制台显示如下信息,表示C#中初始化的数据已经在C++的DLL中被修改,即C#成功调用了C++的DLL。


2018-11-16 13:54:29 shujianlove0 阅读数 180
  • 3D游戏开发基础理论速学教程

    为了让大家更容易掌握3D游戏开发所涉及的理论基础,本课程采用理论+代码实战的模式为大家精心讲解。学习完本课程可以快速提升3D游戏开发技能。本课程涉及的理论基础适合Cocos2d-x,Unity3D等常用的手机游戏开发引擎。想学习本课程的同学需要一定的高中数学基础,及初级的游戏开发基础。本课程演示代码采用Cocos2d-x V3.8 C++。

    134 人正在学习 去看看 刘永

假设我们要调用一个C#中的静态方法,如Debug.Log方法

public static void DebugCallback(int mode, string text)
{
    UnityEngine.Debug.Log(text);
}
public delegate void CSDebugCallback(int debugMode,string text);//声明Callback类型
[DllImport(Dllname)]
public static extern void SZVRPluginInit(CSDebugCallback debugCallback);
//在任何一个初始化插件的地方调用
SZVRPluginInit(DebugCallback);//传递函数指针,注意要传入静态或者有对象的方法

C++里面我们需要保留一个函数指针,但是也要先声明类型

typedef void(__stdcall *DebugCallback)(int,char* str);//声明一个函数指针类型作为Callback
DebugCallback g_UnityDebugLog;//声明Callback对象
void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SZVRPluginInit(DebugCallback callback)
{
	g_UnityDebugLog = callback;//赋值函数指针
	g_UnityDebugLog(0, "Unity DebugLog Call from C++");//后面就可以一直使用这个进行调用了
}

但是经过实际调用发现,不要在渲染线程中调用这个方法,否则会导致编辑器无法被关闭。

2014-04-04 16:30:03 spiritring 阅读数 15012
  • 3D游戏开发基础理论速学教程

    为了让大家更容易掌握3D游戏开发所涉及的理论基础,本课程采用理论+代码实战的模式为大家精心讲解。学习完本课程可以快速提升3D游戏开发技能。本课程涉及的理论基础适合Cocos2d-x,Unity3D等常用的手机游戏开发引擎。想学习本课程的同学需要一定的高中数学基础,及初级的游戏开发基础。本课程演示代码采用Cocos2d-x V3.8 C++。

    134 人正在学习 去看看 刘永

由于最近开发游戏寻路A*使用非常频繁.所以是逻辑上的瓶颈. c#又比c++慢一倍.所以决定上c++.这样算一种优化吧.哈哈.

关于vs上的vc++.分托管和非托管模式.  托管模式就是 C++/CLI 也就说可以c++使用.net库. 和 CLR是一起的.  还有就是 非托管模式就是传统的c++直接调用win32 API.

两种方式都可以创建dll. 但是如果是和c# 连用肯定是 托管模式的更方便一些.下面就讲解下如何 unity3d 使用 CLR的C++/CLI.网上充斥着 使用非托管模式c++和unity连用的方式,这里本尊就不在阐述了..

书归正传:


环境:

1. vs2012 或者 vs2013.

2. unity4.3.0


首先说一下CLR项目的创建.

打开vs.然后新建. 这时候选择. CLR库项目.



之后就进入了项目.然后加入如下代码 用来测试 是否dll 被使用. a 和 b 相加返回结果.很简单的程序.



之后就是需要改一些项目的属性. 因为unity使用mono的原因. 

1.Framework3.5 是 unity的版本. 这里本人的vs是2012/2013 使用fw 4.5了. 所以高了这里需要降下来.由于IDE里面没有选项所以需要打开 proj文件来修改版本.



把v4.5 改成 v3.5就ok.


2.clr也是很多版本的. unity支持 safe的clr. 所以这个也需要改.


项目上面 右键属性.


选成 safe的clr.就可以了. 然后编译出来 dll.


最后就是.把dll copy到unity里面去使用.unity需要建一个plugin的文件夹来存放dll.


放到这个文件夹里 之后.unity就可以使用了.

然后我们创建一个unity的c#脚本.来调用TSEngineCLI.


之后把这个c#脚本拖到随便一个GameObject上面. 运行一下. 会看到Console框 里面输出了3. 说明大功告成.哇嘎嘎.


好了.祝大家看的开心.编的愉快. 拜拜~...




2015-02-28 15:32:54 cp790621656 阅读数 8780
  • 3D游戏开发基础理论速学教程

    为了让大家更容易掌握3D游戏开发所涉及的理论基础,本课程采用理论+代码实战的模式为大家精心讲解。学习完本课程可以快速提升3D游戏开发技能。本课程涉及的理论基础适合Cocos2d-x,Unity3D等常用的手机游戏开发引擎。想学习本课程的同学需要一定的高中数学基础,及初级的游戏开发基础。本课程演示代码采用Cocos2d-x V3.8 C++。

    134 人正在学习 去看看 刘永

在为公司的Unity3d游戏从Mono迁移到IOS 64过程中,出现了许多问题。

在看到Xcode提示Build Success时会有一种成就感油然而生,但是噩梦才刚刚开始。

由于IL2CPP的不完善或者说这种把C#代码转换成C++的方式太粗暴,不仅在Build阶段会出现很多错误,在运行期出现的异常崩溃更是数不胜数。


解决掉Build阶段的错误之后,昨天终于把游戏运行起来,但是在进入主城之后就黑屏了,What Fuck!

不过还好在Xcode中有异常爆出。

Method not found: 'Default constructor not found...ctor() of System.ComponentModel.Int64Converter'.
  at System.Activator.CreateInstance (System.Type type, System.Object[] args) [0x00000] in <filename unknown>:0
  at System.ComponentModel.TypeDescriptor.GetConverter (System.Type type) [0x00000] in <filename unknown>:0
  at Pathfinding.Serialization.JsonFx.TypeCoercionUtility.CoerceType (System.Type targetType, System.Object value) [0x00000] in <filename unknown>:0
  at Pathfinding.Serialization.JsonFx.JsonReader.ReadNumber (System.Type expectedType) [0x00000] in <filename unknown>:0
  at Pathfinding.Serialization.JsonFx.JsonReader.Read (System.Type expectedType, Boolean typeIsHint) [0x00000] in <filename unknown>:0
  at Pathfinding.Serialization.JsonFx.JsonReader.PopulateObject (System.Object& result, System.Type objectType, System.Collections.Generic.Dictionary`2 memberMap, System.Type genericDictionaryType) [0x00000] in <filename unknown>:0 

从字面上来看,没有默认的构造函数?

什么鬼。

但是根据异常来看,异常指向了系统函数……


没办法只有谷歌。

在Unity官方论坛终于看到有开发者反应这个问题,怀疑是因为Unity的代码裁剪优化,消减了这一部分的代码支持!

官方技术支持回答说,不大可能是这个优化造成的,但是也还是给出了一个解决方案。

对于提示出异常的类,创建一个脚本,在这个脚本里面使用new 来调用一下这个类的构造函数,这样就避免被优化裁剪。


I actually doubt this is a stripping issue. It is more likely related to an AOT limitation. IL2CPP can only construct and instance of a type at runtime that it knows about at compile time. So for a call to System.Activator.CreateInstance to succeed, IL2CPP needs to have previously generated code for the System.Type passed as the first argument to CreateInstance.

You may be able to work around this issue by declaring an unused field of type System.ComponentModel.Int64Converter in a class that will not be stripped. A MonoBehaviour in one of your scripts is likely a good candidate. Maybe try something like this:

    class AotTypes : MonoBehaviour
    {
        private static System.ComponentModel.Int64Converter _unused = new System.ComponentModel.Int64Converter();
    }

然后呢对于所有你需要用到的一些类都这样用一下。

private static System.ComponentModel.Int64Converter _unused = new System.ComponentModel.Int64Converter();
    private static System.ComponentModel.DecimalConverter _unused2 = new System.ComponentModel.DecimalConverter();
    private static System.ComponentModel.ByteConverter _unused3 = new System.ComponentModel.ByteConverter();
    private static System.ComponentModel.CollectionConverter _unused4 = new System.ComponentModel.CollectionConverter();
    private static System.ComponentModel.CharConverter _unused5 = new System.ComponentModel.CharConverter();
    private static System.ComponentModel.SByteConverter _unused6 = new System.ComponentModel.SByteConverter();
    private static System.ComponentModel.Int16Converter _unused7 = new System.ComponentModel.Int16Converter();
    private static System.ComponentModel.UInt16Converter _unused8 = new System.ComponentModel.UInt16Converter();
    private static System.ComponentModel.Int32Converter _unused9 = new System.ComponentModel.Int32Converter();
    private static System.ComponentModel.UInt32Converter _unused10 = new System.ComponentModel.UInt32Converter();
    private static System.ComponentModel.Int64Converter _unused11 = new System.ComponentModel.Int64Converter();
    private static System.ComponentModel.UInt64Converter _unused12 = new System.ComponentModel.UInt64Converter();
    private static System.ComponentModel.DoubleConverter _unused13 = new System.ComponentModel.DoubleConverter();
    private static System.ComponentModel.SingleConverter _unused14 = new System.ComponentModel.SingleConverter();
    private static System.ComponentModel.BooleanConverter _unused15 = new System.ComponentModel.BooleanConverter();
    private static System.ComponentModel.StringConverter _unused16 = new System.ComponentModel.StringConverter();
    private static System.ComponentModel.DateTimeConverter _unused17 = new System.ComponentModel.DateTimeConverter();
    private static System.ComponentModel.EnumConverter _unused18 = new System.ComponentModel.EnumConverter(typeof(<any your enum>));
    private static System.ComponentModel.TimeSpanConverter _unused19 = new System.ComponentModel.TimeSpanConverter();

这样就避免代码被裁剪。


游戏成功运行。

没有更多推荐了,返回首页