游戏脚本怎么制作从基础做起?

您当前的位置:
> 掌握脚本秘籍!《零基础学按键精灵》给你答案
掌握脚本秘籍!《零基础学按键精灵》给你答案
[导读]游戏时反复刷同样的怪物枯燥无聊?工作中成千上万次重复粘贴动作单调乏味?电脑的发明本意是为了给人带来方便,当各种软件重复操作成为日常生活的累赘,...
游戏时反复刷同样的怪物枯燥无聊?工作中成千上万次重复粘贴动作单调乏味?电脑的发明本意是为了给人带来方便,当各种软件重复操作成为日常生活的累赘,我们就迫切需要一款软件用各种方法替我们完成重复的工作,于是按键精灵就应运而生了。按键精灵官方网站:
新软件的产生总要伴随着教程,作为按键精灵首本官方教材&&《零基础学按键精灵》自7月7日正式发售以来,销量空前火爆,得到了众多按键精灵粉丝的支持,对书中的内容更是好评不断。
按键精灵国内最具影响力的动态脚本开发软件。软件支持动作录制功能,可以让不懂编程的用户轻松做出模拟键盘鼠标动作的软件。只要在电脑前用双手可以完成的动作,按键精灵都可替你完成,常见的游戏功能有:键鼠连按,繁琐任务,智能跑商,刷图助手等。常见的日常工作内容:重复粘贴、批量处理文档。一个脚本,帮你一劳永逸
用户可以根据需求编写出适合自己的脚本,运用到工作游戏中。文件批量处理、自动统计数据、游戏自动打怪等等功能通过按键精灵都能轻松完成。
在《零基础学按键精灵》一书中,读者将了解到什么是按键精灵,什么是脚本,如何使用脚本。通过了解按键精灵软件,学习Q语言和按键精灵命令,最终编写出自己想要的脚本。书中还会以通俗的语言讲解按键精灵软件和Q语言。通过系统全面的知识体系引导读者学习按键精灵,运用丰富、简洁的范例和配图,帮助读者更好地理解每一个知识点。同时运用大量的表格帮助用户对命令建立直观的印象,每章后面配有适量习题供读者巩固知识、提高技能、灵活运用,非常适合刚接触按键精灵的读者。
团购特权卡
News &Channel
新闻资讯--
游戏类型--
游戏题材--
热门频道--如何做游戏脚本_百度知道
如何做游戏脚本
有高手麽我玩天龙八部在淘宝买了个脚本30元还不可以后台这脚本好烂要是自己制作脚本需要什么专业知识吗?我想学!我看这脚本有点好玩?留下QQ私聊
提问者采纳
驱动,还有一门可以把劳动成果编写成软件的工具,无语了,那种编程你只要有语文基础,居然还卖30块钱,反汇编那种只能运行在前台的估计是按键精灵之类的工具弄的。你要的脚本需要的知识我给你几个关键字,脱壳,稍微学下就能写
其他类似问题
为您推荐:
您可能关注的推广回答者:回答者:
游戏脚本的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁ma_haile 的BLOG
用户名:ma_haile
文章数:38
评论数:23
访问量:410995
注册日期:
阅读量:9151
阅读量:2203
[匿名]51cto游客:
51CTO推荐博文
& & 前面几张我们讲解了unity的基本使用, 知道了如何去建立一个简单游戏。 但这不不够, 因为这些需要给游戏添加更多的行为,这样游戏才算有血有肉 &好了不说那么多废话了 下面开始讲正题吧 &咔咔&
& & unity 支持javascript,C#, boo script 语言 &但官方推荐使用 javascript 语言, 对于初学者来说 ,最好使用javascrip 语言,因为简单 易学 ,更容易入门,到了后期推荐使用C# 因为c# &语言更贴近于面向对象的编程思想. 我们先用javascrip来学习吧
下面我们先来了解下unity 脚本的命名规范
& 变量 - 首写为小写字母.变量用来存储游戏状态中的任何信息.
& 函数 - 首写为大写字母.函数是一个代码块,在需要的时候可以被重复调用.
& 类 - 首写为大写字母.可以被认为是函数的库.
&当阅读范例时注意首写字母,将有助于你更好的理解对象之间的关系.
这一章 我们来做一个 如何使用脚本 让一个游戏组件移动的demo 通过学习这一章来让我们的游戏更加完善
新建项目 -& 场景 & 这一步可以不需要导入什么 资源包(不导入是因为在这个示例里面用不上)
新建一个项目后 默认会有一个新的场景 并且在场景里面还有一个摄像机组件
1:首先创建一个用于行走的 plane & 菜单栏 GameObject-&Create Other -&Plane
把Hierarchey&中的plane和摄像机的组件的Inspector属性的 position 的x,y,z 属性的值都设置为0 方便在编辑框编辑
2:创建一个用于移动的 cube 菜单栏 GameObject-&Create Other -&Cube
把Hierarchey中的cube组件的Inspector属性的 position 的x,y,z 属性的值都设置为0
这里如果觉得场景不好看 可以给场景的plane 和 cube添加上图片纹理,和灯光,这边不在阐述了 , 前几章有讲过,呵呵
现在我们来 开始我们第一个脚本
在Project 中点右键 create -&javascript 创建一个js 脚本 如图
重命名该js为MovePlayer.js 并双击该js unity会自己启动 MonoDevelop 编辑器并打开该js&
在脚本中 我们看到一个Update()函数 这个函数中的代码将在游戏中的每一帧调用 所以我们将移动的代码放到这个函数中
在unity 中移动一个对象需要使用 transform 来改变位置(transform 表示游戏组件本身), 那我们就需要通过transfrom的Translate函数 修改 的x,y,z 坐标, 我们先来个简单的哈 使用键盘的 a s w d 这几个键来移动箱子 看看 下面的Update函数代码
function Update(){
& transfrom.Translate(Input.GetAxis(&Horizontal&),0,Input.GetAxis(&Vertical&));
Input.GetAxis() 函数返回-1到1之间的一个值 例如 上面代码Translate函数中的第一个参数 往左移动键映射为-1 右移动键映射为1
在上面的代码中 参数0 代表着y 轴 因为我们不需要cube 上下移动
Horiaontal和Verical 为 unity 输入按键映射 这几个按键映射是 unity 默认给我们预制的 在菜单 edit-&Project Settings-&input 的inspector 窗口下面 axes 下面可以很清楚看到按键映射关系 同样你可以修改它
3: &连接脚本
&我们想移动那个游戏组件,在这里当然是cube 啦 那我们就把刚刚写的那个MovePlayer.js连接到我们要移动的那个游戏组件上 ,那这个游戏组件就拥有了这个脚本的行为了&
那如何连接呢
& 在Hierarchy 中选中想要拥有这个脚本行为的的游戏组件 然后在菜单栏中 & & & &Componect-& scripts 就可以看到你刚写的脚本文件了 如图
如上图 scripts &下面的move Player就是我们刚刚写的脚本文件了 点击它 就可以附加到 游戏组件上了 &
还有种方法是 可以直接把Project 中的Move Player脚本文件 直接选中拖到Horiaontal 下面的游戏组件上 &这种方法更方便 直观 快捷
&给游戏组件添加脚本行为后 就可以再这个游戏组件的inspector 窗口看到我们添加的脚本文件了
小提示: 上图中 右下侧那个方框内 script &后面的那个 小三角形 点击它可以选择别的脚本文件哦 &那个齿轮样的 图标可以 移除脚本文件和重置这个脚本文件的值等
到这一步 可以就可以使游戏组件 cube 动起来了 运行游戏 使用 a,d,w,s 来控制方向 看看效果吧 &但我们发现 貌似这个组件运行的太快了,那我们有办法处理吗? &当然有了 各位盆友继续往下看哈
上面我们 是 按帧数去移动 现在我们按秒去移动 即每秒移动 多少个距离
如果按这种方式去实现 &我们需要将Input.GetAxis()函数的返回值乘于Time.deltaTime 在乘于 一个移动距离 & 重新编辑MovePlayer.js
var speed=5.0;
function Update () {
var x=Input.GetAxis(&Horizontal&)*Time.deltaTime*
var z=Input.GetAxis(&Vertical&)*Time.deltaTime*
transform.Translate(x,0,z);
上面的代码就实现了 每秒 移动的多少距离 这样就和帧数无关了
保存上面代码 重新运行游戏 试试效果是不是看着顺畅很多
注意:移动的速度除了 在代码中修改, 还可以再这个脚本所连接的游戏组件的检视面板中修改 , 这就称为变量暴露 &这种方式修改会比直接在代码中修改方便的多 如下图
如何调试脚本
Log()函数允许用户向控制台发送信息
例如: &把 Debug.Log(&show cube&); 这断代码放到Update() 函数中 允许游戏就可以再 unity 的下边看到 show cube 字样 &双击字样就可以看到控制台日志了
各位童鞋 如果想了解脚本中常用的方法 可以 进入下面地址查看
&各位童鞋 这章就到这里了 & 欢迎各位大小盆友 留言讨论 &
&本文出自 “” 博客,请务必保留此出处
了这篇文章
类别:┆阅读(0)┆评论(0)
16:24:47 08:33:35从游戏脚本语言说起,剖析Mono所搭建的脚本基础
来源:cnblog&&&
阅读:19&&&时间:
在日常的工作中,我偶尔能遇到这样的问题:&为何游戏脚本在现在的游戏开发中变得不可或缺?&。那么这周我就写篇文章从游戏脚本聊起,分析一下游戏脚本因何出现,而mono又能提供怎样的脚本基础。最后会通过模拟Unity3D游戏引擎中的脚本功能,将Mono运行时嵌入到一个非托管(C/C++)程序中,实现脚本语言和&引擎&之间的分离。
0x01 Why?从为何需要游戏脚本开始
首先聊聊为何现在的游戏开发需要使用游戏脚本这个话题。
为何需要有脚本系统呢?脚本系统又是因何而出现的呢?其实游戏脚本并非一个新的名词或者技术,早在暴雪的《魔兽世界》开始火爆的年代,人们便熟知了一个叫做Lua的脚本语言。而当时其实有很多网游都不约而同的使用了Lua作为脚本语言,比如网易的大话西游系列。但是在单机游戏流行的年代,我们却很少听说有什么单机游戏使用了脚本技术。这又是为什么呢?因为当时的硬件水平不高,所以需要使用C/C++这样的语言来尽量压榨硬件的性能,同时,单机游戏的更新换代并不如网游那么迅速,所以开发时间、版本迭代速度并非其考虑的第一要素,因而可以使用C/C++这样开发效率不高的语言来开发游戏。
但是随着时间的推移,硬件水平逐年水涨船高,压榨硬件性能的需求已经不再迫切。相反,此时网游的兴起却对开发速度、版本更迭提出了更高的要求。所以开发效率并不高效,且投资巨大风险很高的C/C++便不再适应市场的需求了。而更加现实的问题是,随着java、.net甚至是javascript等语言的流行,程序员可以选择的语言越来越多,这更加导致了优秀的C/C++程序员所占比例越来越小。而网游市场的不断扩大,这种对人才的需求也同样越来越大,这就造成了大量的人才空缺,也就反过来提高了使用C/C++开发游戏的成本。而由于C/C++是门入门容易进阶难的语言,其高级特性和高度灵活性带来的高风险也是每个项目使用C/C++进行开发时,所不得不考虑的问题。
而一个可以解决这种困境的举措便是在游戏中使用脚本。可以说游戏脚本的出现,不仅解决了由于C/C++难以精通而带来的开发效率问题,而且还降低了使用C/C++进行开发的项目风险和成本。从此,脚本与游戏开发相得益彰,互相促进,逐渐成为了游戏开发中不可或缺的一个部分。
而到了如今手游兴起的年代,市场的需求变得更加庞大且变化更加频繁。这就更加要求需要有脚本语言来提高项目的开发效率、降低项目的成本。而作为游戏脚本,它具体的优势都包括哪些呢?
易于学习,代码方便维护。适合快速开发。
开发成本低。由于上述第一点,因为易于学习,所以可以启用新人,同时开发速度快,这些都是降低成本的方法。
因此,包括Unity3D在内的众多游戏引擎,都提供了脚本接口,让开发者在开发项目时能够摆脱C/C++(注:Unity3D本身是用C/C++写的)的束缚,这其实是变相的降低了游戏开发的门槛,吸引了很多独立开发者和游戏制作爱好者。
0x02 What?Mono提供的脚本机制
首先一个问题:Mono是什么?
Mono是一个由Xamarin公司所赞助的开源项目。它基于通用语言架构(Common Language Infrastructure ,缩写为CLI)和C#的ECMA 标准(Ecma-335、Ecam-334),提供了微软的.Net框架的另一种实现。与微软的.Net框架不同的是,Mono具备了跨平台的能力,也就是说它不仅能运行在Windows系统上,而且还可以运行在Mac OSX、Linux甚至是一些游戏平台上。
所以把它作为跨平台的方案是像Unity3D这种开发跨平台游戏的游戏引擎的一个不错的选择。但Mono又是如何提供这种脚本的功能的呢?
如果需要利用Mono为应用开发提供脚本功能,那么其中一个前提就是需要将Mono的运行时嵌入到应用中,因为只有这样才有可能使得托管代码和脚本能够在原生应用中使用。所以,我们可以发现,将Mono运行时嵌入应用中是多么的重要。但在讨论如何将Mono运行时嵌入原生应用中去之前,我们首先要搞清楚Mono是如何提供脚本功能的,以及Mono提供的到底是怎样的脚本机制。
Mono和脚本
本小节将会讨论如何利用Mono来提高我们的开发效率以及拓展性而无需将已经写好的C/C++代码重新用C#写一遍,也就是Mono是如何提供脚本功能的。
常常使用一种编程语言开发游戏是比较常见的一种情况。因而游戏开发者往往需要在高效率的低级语言和低效率的高级语言之间抉择。例如一个用C/C++开发的应用的结构如下图:
可以看到低级语言和硬件打交道的方式更加直接,所以其效率更高。
可以看到高级语言并没有和硬件直接打交道,所以其效率较低。如果以速度作为衡量语言的标准,那么语言从低级到高级的大体排名如下:
C/C++,编译型静态不安全语言
C#、Java,编译型静态安全语言
Python, Perl, Javascript,解释型动态安全语言
开发者在选择适合自己的开发语言时,的确面临着很多现实的问题。
高级语言对开发者而言效率更高,也更加容易掌握,但高级语言也并不具备低级语言的那种运行速度、甚至对硬件的要求更高,这在某种程度上的确也决定了一个项目到底是成功还是失败。
因此,如何平衡两者,或者说如何融合两者的优点,便变得十分重要和迫切。脚本机制便在此时应运而生。游戏引擎由富有经验的开发人员使用C/C++开发,而一些具体项目中功能的实现,例如UI、交互等等则使用高级语言开发。
通过使用高级脚本语言,开发者便融合了低级语言和高级语言的优点。同时提高了开发效率,如同第一节中所讲的,引入脚本机制之后开发效率提升了,可以快速的开发原型,而不必把大量的时间浪费在C/C++上。
脚本语言同时提供了安全的开发沙盒模式,也就是说开发者无需担心C/C++开发的引擎中的具体实现细节,也无需关注例如资源管理和内存管理这些事情的细节,这在很大程度上简化了应用的开发流程。
而Mono则提供了这种脚本机制实现的可能性。即允许开发者使用JIT编译的代码作为脚本语言为他们的应用提供拓展。
目前很多脚本语言的选择趋向于解释型语言,例如cocos2d-js使用的javascript。因此效率无法与原生代码相比。而Mono则提供了一种将脚本语言通过JIT编译为原生代码的方式,提高了脚本语言的效率。例如,Mono提供了一个原生代码生成器,使你的应用的运行效率尽可能高。同时提供了很多方便的调用原生代码的接口。
而为一个应用提供脚本机制时,往往需要和低级语言交互。这便不得不提到将Mono的运行时嵌入到应用中的必要性了。那么接下来,我将会讨论一下如何将Mono运行时嵌入到应用中。
Mono运行时的嵌入
既然我们明确了Mono运行时嵌入应用的重要性,那么如何将它嵌入应用中就成为了下一个值得讨论的话题。
这个小节我会为大家分析一下Mono运行时究竟是如何被嵌入到应用中的,以及如何在原生代码中调用托管方法,相应的,如何在托管代码中调用原生方法。而众所周知的一点是,Unity3D游戏引擎本身是用C/C++写成的,所以本节就以Unity3D游戏引擎为例,假设此时我们已经有了一个用C/C++写好的应用(Unity3D)。
将你的Mono运行时嵌入到这个应用之后,我们的应用就获取了一个完整的虚拟机运行环境。而这一步需要将&libmono&和应用链接,一旦链接完成,你的C++应用的地址空间就会像下图一般:
而在C/C++代码中,我们需要将Mono运行时初始化,一旦Mono运行时初始化成功,那么下一步最重要的就是将CIL/.NET代码加载进来。加载之后的地址空间将会如下图所示:
那些C/C++代码,我们通常称之为非托管代码,而通过CIL编译器生成CIL代码我们通常称之为托管代码。所以,将Mono运行时嵌入我们的应用,可以分为三个步骤:
编译C++程序和链接Mono运行时
初始化Mono运行时
C/C++和C#/CIL的交互
让我们一步一步的进行。首先我们需要将C++程序进行编译并链接Mono运行时。此时我们会用到pkg-config工具。在Mac上使用homebrew来进行安装,在终端中输入命令&brew install pkgconfig&,可以看到终端会有如下的输出内容:
==& Downloading https:///bottles/pkg-config-0.28.mavericks.bottle.2.tar.gz
######################################################################## 100.0%
==& Pouring pkg-config-0.28.mavericks.bottle.2.tar.gz
/usr/local/Cellar/pkg-config/0.28: 10 files, 604K
结束之后,证明pkg-config安装完毕。接下来,我们新建一个C++文件,命名为unity.cpp,作为我们的原生代码部分。我们需要将这个C++文件进行编译,并和Mono运行时链接。在终端输入:
g++ unity.cpp
-framework CoreFoundation -lobjc -liconv `pkg-config --cflags --libs mono-2`
此时,经过编译和链接之后,我们的unity.cpp和Mono运行时被编译成了可执行文件。到此,我们需要能够将Mono的运行时初始化。所以再重新回到刚刚新建的unity.cpp文件中,我们要在C++文件中来进行运行时的初始化工作,即调用mono_jit_init方法。代码如下:
#include &mono/jit/jit.h&
#include &mono/metadata/assembly.h&
#include &mono/metadata/class.h&
#include &mono/metadata/debug-helpers.h&
#include &mono/metadata/mono-config.h&
MonoDomain*
domain = mono_jit_init(managed_binary_path);
mono_jit_init这个方法会返回一个MonoDomain,用来作为盛放托管代码的容器。其中的参数managed_binary_path,即应用运行域的名字。除了会返回MonoDomain之外,这个方法还会初始化默认框架版本,即2.0或4.0,这个主要由使用的Mono版本来决定。当然,我们也可以手动指定版本。只需要调用下面的方法即可:
domain = mono_jit_init_version ("unity", ""v2.0.50727);
到此,我们获取了一个应用域&&domain。但是当Mono运行时被嵌入一个原生应用的时候,它显然需要一种方法来确定自己所需要的运行时程序集以及配置文件。默认情况下它会使用在系统中定义的位置。
如图,可以看到,在一台电脑上可以存在很多不同版本的Mono,如果我们的应用需要特定的运行时的话,我们显然也需要指定其程序集和配置文件的位置。
为了选择我们所需要的Mono版本,可以使用mono_set_dirs方法:
mono_set_dirs("/Library/Frameworks/Mono.framework/Home/lib", "/Library/Frameworks/Mono.framework/Home/etc");
这样,我们就设置了Mono运行时的程序集和配置文件路径。当然,Mono运行时在执行一些具体功能的时候,可能还需要依靠额外的配置文件来进行。所以我们有时也需要为Mono运行时加载这些配置文件,通常我们使用mono_config_parse 方法来进行加载这些配置文件的工作。当mono_config_parse 的参数为NULL时,Mono运行时将加载Mono的配置文件。当然作为开发者,我们也可以加载自己的配置文件,只需要将我们自己的配置文件的文件名作为mono_config_parse方法的参数即可。Mono运行时的初始化工作到此完成。接下来,我们就需要加载程序集并且运行它了。这里我们需要用到MonoAssembly和mono_domain_assembly_open这个方法。
const char* managed_binary_path = "./ManagedLibrary.dll";
MonoAssembly *
assembly = mono_domain_assembly_open (domain, managed_binary_path);
if (!assembly)
上面的代码会将当前目录下的ManagedLibrary.dll文件中的内容加载进已经创建好的domain中。此时需要注意的是Mono运行时仅仅是加载代码而没有立刻执行这些代码。
如果要执行这些代码,则需要调用被加载的程序集中的方法。或者当你有一个静态的主方法时(也就是一个程序入口),你可以很方便的通过mono_jit_exec方法来调用这个静态入口。
下面我将为各位举一个将Mono运行时嵌入C/C++程序的例子,这个例子的主要流程是加载一个由C#文件编译成的DLL文件,之后调用一个C#的方法输出Hello World。
首先,我们完成C#部分的代码。
namespace ManagedLibrary
public static class MainTest
public static void Main()
System.Console.WriteLine("Hello World");
在这个文件中,我们实现了输出Hello World的功能。之后我们将它编译为DLL文件。这里我也直接使用了Mono的编译器&&mcs。在终端命令行使用mcs编译该cs文件。同时为了生成DLL文件,还需要加上-t:library选项。
mcs ManagedLibrary.cs -t:library
这样,我们便得到了cs文件编译之后的DLL文件,叫做ManagedLibrary.dll。
接下来,我们完成C++部分的代码。嵌入Mono的运行时,同时加载刚刚生成ManagedLibrary.dll文件,并且执行其中的Main方法用来输出Hello World。
#include &mono/jit/jit.h&
#include &mono/metadata/assembly.h&
#include &mono/metadata/class.h&
#include &mono/metadata/debug-helpers.h&
#include &mono/metadata/mono-config.h&
MonoDomain *
int main()
const char* managed_binary_path = "./ManagedLibrary.dll";
//获取应用域
domain = mono_jit_init (managed_binary_path);
//mono运行时的配置
mono_set_dirs("/Library/Frameworks/Mono.framework/Home/lib", "/Library/Frameworks/Mono.framework/Home/etc");
mono_config_parse(NULL);
//加载程序集ManagedLibrary.dll
MonoAssembly* assembly = mono_domain_assembly_open(domain, managed_binary_path);
MonoImage* image = mono_assembly_get_image(assembly);
//获取MonoClass
MonoClass* main_class = mono_class_from_name(image, "ManagedLibrary", "MainTest");
//获取要调用的MonoMethodDesc
MonoMethodDesc* entry_point_method_desc = mono_method_desc_new("ManagedLibrary.MainTest:Main()", true);
MonoMethod* entry_point_method = mono_method_desc_search_in_class(entry_point_method_desc, main_class);
mono_method_desc_free(entry_point_method_desc);
//调用方法
mono_runtime_invoke(entry_point_method, NULL, NULL, NULL);
//释放应用域
mono_jit_cleanup(domain);
之后编译运行,可以看到屏幕上输出的Hello World。但是既然要提供脚本功能,将Mono运行时嵌入C/C++程序之后,只是在C/C++程序中调用C#中定义的方法显然还是不够的。脚本机制的最终目的还是希望能够在脚本语言中使用原生的代码,所以下面我将站在Unity3D游戏引擎开发者的角度,继续探索一下如何在C#文件(脚本文件)中调用C/C++程序中的代码(游戏引擎)。
0x03 How?如何模拟Unity3D中的脚本机制
首先,假设我们要实现的是Unity3D的组件系统。为了方便游戏开发者能够在脚本中使用组件,那么我们首先要在C#文件中定义一个Component类。
//脚本中的组件Component
public class Component
public int ID { get; }
private IntPtr native_
与此同时,在Unity3D游戏引擎(C/C++)中,则必然有和脚本中的Component相对应的结构。
//游戏引擎中的组件Component
struct Component
托管代码(C#)中的接口
可以看到此时组件类Component只有一个属性,即ID。我们再为组件类增加一个属性,Tag。
之后,为了使托管代码能够和非托管代码交互,我们需要在C#文件中引入命名空间pilerServices,同时需要提供一个IntPtr类型的句柄以便于托管代码和非托管代码之间引用数据。(IntPtr 类型被设计成整数,其大小适用于特定平台。 即是说,此类型的实例在 32 位硬件和操作系统中将是 32 位,在 64 位硬件和操作系统上将是 64 位。IntPtr 对象常可用于保持句柄。 例如,IntPtr 的实例广泛地用在 System.IO.FileStream 类中来保持文件句柄。)
最后,我们将Component对象的构建工作由托管代码C#移交给非托管代码C/C++,这样游戏开发者只需要专注于游戏脚本即可,无需去关注C/C++层面即游戏引擎层面的具体实现逻辑了,所以我在此提供两个方法即用来创建Component实例的方法:GetComponents,以及获取ID的get_id_Internal方法。
这样在C#端,我们定义了一个Component类,主要目的是为游戏脚本提供相应的接口,而非具体逻辑的实现。下面便是在C#代码中定义的Component类。
using pilerS
namespace ManagedLibrary
public class Component
private IntPtr native_handle = (IntPtr)0;
[MethodImpl(MethodImplOptions.InternalCall)]
public extern static Component[] GetComponents();
[MethodImpl(MethodImplOptions.InternalCall)]
public extern static int get_id_Internal(IntPtr native_handle);
public int ID
return get_id_Internal(this.native_handle);
public int Tag {
[MethodImpl(MethodImplOptions.InternalCall)]
之后,我们还需要创建这个类的实例并且访问它的两个属性,所以我们再定义另一个类Main,来完成这项工作。Main的实现如下:
// Main.cs
namespace ManagedLibrary
public static class Main
public static void TestComponent ()
Component[] components = Component.GetComponents();
foreach(Component com in components)
Console.WriteLine("component id is " + com.ID);
Console.WriteLine("component tag is " + com.Tag);
非托管代码(C/C++)的逻辑实现
完成了C#部分的代码之后,我们需要将具体的逻辑在非托管代码端实现。而我上文之所以要在Component类中定义两个属性:ID和Tag,是为了使用两种不同的方式访问这两个属性,其中之一就是直接将句柄作为参数传入到C/C++中,例如上文我提供的get_id_Internal这个方法,它的参数便是句柄。第二种方法则是在C/C++代码中通过Mono提供的mono_field_get_value方法直接获取对应的组件类型的实例。所以组件Component类中的属性获取有两种不同的方法:
//获取属性
int ManagedLibrary_Component_get_id_Internal(const Component* component)
return component-&
int ManagedLibrary_Component_get_tag(MonoObject* this_ptr)
Component*
mono_field_get_value(this_ptr, native_handle_field, reinterpret_cast&void*&(&Component));
return component-&
之后,由于我在C#代码中基本只提供接口,而不提供具体逻辑实现。所以我还需要在C/C++代码中实现获取Component组件的具体逻辑,之后再以在C/C++代码中创建的实例为样本,调用Mono提供的方法在托管环境中创建相同的类型实例并且初始化。由于C#中的GetComponents方法返回的是一个数组,所以对应的,我们需要使用MonoArray从C/C++中返回一个数组。所以C#代码中GetComponents方法在C/C++中对应的具体逻辑如下:
MonoArray* ManagedLibrary_Component_GetComponents()
MonoArray* array = mono_array_new(domain, Component_class, num_Components);
for(uint32_t i = 0; i & num_C ++i)
MonoObject* obj = mono_object_new(domain, Component_class);
mono_runtime_object_init(obj);
void* native_handle_value = &Components[i];
mono_field_set_value(obj, native_handle_field, &native_handle_value);
mono_array_set(array, MonoObject*, i, obj);
其中num_Components是uint32_t类型的字段,用来表示数组中组件的数量,下面我会为它赋值为5。之后通过Mono提供的mono_object_new方法来创建MonoObject的实例。而需要注意的是代码中的Components[i],Components便是在C/C++代码中创建的Component实例,这里用来给MonoObject的实例初始化赋值。创建Component实例的过程如下:
num_Components = 5;
Components = new Component[5];
for(uint32_t i = 0; i & num_C ++i)
Components[i].id =
Components[i].tag = i * 4;
C/C++代码中创建的Component的实例的id为i,tag为i * 4。最后我们还需要将C#中的接口和C/C++中的具体实现关联起来。即通过Mono的mono_add_internal_call方法来实现,也即在Mono的运行时中注册刚刚用C/C++实现的具体逻辑,以便将托管代码(C#)和非托管代码(C/C++)绑定。
// get_id_Internal
mono_add_internal_call("ponent::get_id_Internal", reinterpret_cast&void*&(ManagedLibrary_Component_get_id_Internal));
mono_add_internal_call("ponent::get_Tag", reinterpret_cast&void*&(ManagedLibrary_Component_get_tag));
//GetComponents
mono_add_internal_call("ponent::GetComponents", reinterpret_cast&void*&(ManagedLibrary_Component_GetComponents));
这样,我们便使用非托管代码(C/C++)实现了获取组件、创建和初始化组件的具体功能,完整的代码如下。
#include &mono/jit/jit.h&
#include &mono/metadata/assembly.h&
#include &mono/metadata/class.h&
#include &mono/metadata/debug-helpers.h&
#include &mono/metadata/mono-config.h&
struct Component
Component* C
uint32_t num_C
MonoClassField* native_handle_
MonoDomain*
MonoClass* Component_
//获取属性
int ManagedLibrary_Component_get_id_Internal(const Component* component)
return component-&
int ManagedLibrary_Component_get_tag(MonoObject* this_ptr)
Component*
mono_field_get_value(this_ptr, native_handle_field, reinterpret_cast&void*&(&component));
return component-&
//获取组件
MonoArray* ManagedLibrary_Component_GetComponents()
MonoArray* array = mono_array_new(domain, Component_class, num_Components);
for(uint32_t i = 0; i & num_C ++i)
MonoObject* obj = mono_object_new(domain, Component_class);
mono_runtime_object_init(obj);
void* native_handle_value = &Components[i];
mono_field_set_value(obj, native_handle_field, &native_handle_value);
mono_array_set(array, MonoObject*, i, obj);
int main(int argc, const char * argv[])
mono_set_dirs("/Library/Frameworks/Mono.framework/Versions/3.12.0/lib/", "/Library/Frameworks/Mono.framework/Home/etc");
mono_config_parse(NULL);
const char* managed_binary_path = "./ManagedLibrary.dll";
domain = mono_jit_init(managed_binary_path);
MonoAssembly* assembly = mono_domain_assembly_open(domain, managed_binary_path);
MonoImage* image = mono_assembly_get_image(assembly);
mono_add_internal_call("ponent::get_id_Internal", reinterpret_cast&void*&(ManagedLibrary_Component_get_id_Internal));
mono_add_internal_call("ponent::get_Tag", reinterpret_cast&void*&(ManagedLibrary_Component_get_tag));
mono_add_internal_call("ponent::GetComponents", reinterpret_cast&void*&(ManagedLibrary_Component_GetComponents));
Component_class = mono_class_from_name(image, "ManagedLibrary", "Component");
native_handle_field = mono_class_get_field_from_name(Component_class, "native_handle");
num_Components = 5;
Components = new Component[5];
for(uint32_t i = 0; i & num_C ++i)
Components[i].id =
Components[i].tag = i * 4;
MonoClass* main_class = mono_class_from_name(image, "ManagedLibrary", "Main");
const bool include_namespace = true;
MonoMethodDesc* managed_method_desc = mono_method_desc_new("ManagedLibrary.Main:TestComponent()", include_namespace);
MonoMethod* managed_method = mono_method_desc_search_in_class(managed_method_desc, main_class);
mono_method_desc_free(managed_method_desc);
mono_runtime_invoke(managed_method, NULL, NULL, NULL);
mono_jit_cleanup(domain);
delete[] C
接下来为了验证我们是否成功的模拟了将Mono运行时嵌入&Unity3D游戏引擎&中,我们需要将代码编译并且查看输出是否正确。首先将C#代码编译为DLL文件。我们在终端直接使用Mono的mcs编译器来完成这个工作。运行后生成了ManagedLibrary.dll文件。之后将unity.cpp和Mono运行时链接、编译,会生成一个a.out文件(在Mac上)。执行a.out,可以看到在终端上输出了创建出的组件的ID和Tag的信息。
通过本文,我们可以看到游戏脚本语言出现的必然性。同时也应该了解Unity3D的底层是C/C++实现的,但是它通过Mono提供了一套脚本机制,以方便游戏开发者快速的开发游戏同时也降低了游戏开发的门槛。
最新文章&& 热门推荐
版权所有 爱编程 (C) Copyright 2012. . All Rights Reserved.
闽ICP备号-3
第三方登录:&

我要回帖

更多关于 游戏脚本 的文章

 

随机推荐