amxx插件开发新手教程1-开篇
条评论本篇文章只是作为理论说明,让大家对amxx插件开发有个初步的认识,并不具备实践效果。如果按照本篇文章来操作的话,可能会出现一些错误。
什么是AMXX插件
AMXX插件即Amx Mod X插件,是基于半条命Half-Life这款游戏开发的可扩展的模块,可安装到到反恐精英cs1.6上,可通过编写插件来拓展原本反恐精英cs1.6的功能。例如:
- 修改皮肤
- 会员系统
- 火箭筒、导弹、追踪类导弹
- 游戏数据修改(生命值、攻击力、防御力等)
- 新增武器
插件核心模块
什么是插件的核心模块?核心模块是插件开发的基础,我们可以利用这些基础代码来我们自己的插件。比如说建立大楼需要有地基,那么插件也是一样,如果没有核心模块就无法安装插件。
使用控制台进行测试
- 打开 反恐精英cs1.6
- 以任意地图创建房间,然后进入游戏中
- 按
~键 打开控制台,接着输入amxx
接下来你会看见控制台返回amxx的信息
前提是你的cs1.6客户端安装了amxx插件,纯净版的cs1.6是需要另外自己安装amxx插件的
返回数据
通常,控制台会返回的信息只有两种
- 无效的命令/无反应
- 有反应
控制台检查模块是否安装
显示无效的命令或无反应
这表示反恐精英cs1.6没有安装插件。这时,你就需要自己安装了。
- 首先到官方网站下载插件安装包
- 找到反恐精英cs1.6游戏所在文件夹(如D:/cs1.6)
- 将压缩包里面的文件替换到D:/cs1.6/cstrike,选择全部替换
至此,你就完成了amxx核心插件模块的安装了,当然,官方提供了很多种安装方法,可自行查阅选择合适的安装方式
显示有反应
这说明反恐精英cs1.6的amxx插件模块已经安装完成。接下来你可以安装单独的插件了,安装独立插件的这个过程分为两步:
第一步 -> 放置插件
打开cs1.6安装目录然后找到cstrike文件夹
在上述位置找到并打开addons/amxmodx/plugins文件夹
将插件(后缀名 .amxx)的文件放入上述文件夹中
第二步 -> 修改插件配置文件
打开cs1.6然后找到cstrike文件夹
在上述位置找到并打开addons/amxmodx/configs文件夹。然后找到plugins.ini配置文件
使用记事本打开配置文件,在该文件中最后一行通过回车新建一行写入插件的名字
例如:111.amxx这样的名字
注意:配置文件中,插件的名字不能写中文名,不然会无法读取插件。
我们知道插件可以安装进去,可是插件是怎么得到的?
这里我们接下来就需要先了解一下编译器了
插件与由来
我们知道AMXX是插件文件,可是他是怎么来的呢,你也许会说:“这么简单,网上下载就是”,那有没有想过网上的插件是怎么来的呢?
其实,任何插件都是通过编译器得到的。
什么是编译器?
编译器就是将我们写的程序,比如用C, JAVA, C++, 汇编等语言写的程序,经过编译器的转换,把这些语言转换成计算机或者微型处理器能够识别的机器代码,它是由0和1以组成的序列,说白了,就是相当于英语翻译成中文的工具一样。
如何获取编译器?
在addons/amxmodx/scripting中,有amxxpc.exe和compile.exe这两个文件,这个就是编译器了
编译器如何使用?
- 首先确定源代码和编译器处于同在目录的状态,反之请复制源代码文件到编译器目录中
- 将源代码文件(.sma)用鼠标拖拽到编译器目录下的 compile.exe 文件上,然后放开鼠标
- 若成功编译,你会在编译器目录看见 compiled 文件夹。
- 在这个文件夹中就可以找到你编译后的插件
代码组成与结构
主结构
每个源代码中由不同的代码块组成,大致包含以下内容:
- 模块定义
- 变量
- 函数
- 事件
- 判断
- 表达式以及运算符
模块定义
Include头文件
头文件定义通常在源代码中的最顶部,关键字为include<模块名>的内容,这就是模块定义。
他至关重要,是整个源码的心脏。插件的代码由头文件所定义的代码来编写,不在头文件包含的代码,是无法被编译器编译的。
通常常见为:
1 |
插件使用什么模块中的代码进行工作,就在这里定义
常用模块:
- amxmodx
- fun
- engine
- fakemeta
- hamsandwich
- cstrike
常用模块解释
AmxModx
基础模块,必须定义。
他内置了大量代码来让开发者无比强大
代码:
1 |
Fun
这个模块比较适合新手,可以用简单的代码实现常用效果
代码:
1 |
Engine
不太常用的模块,他提供的代码非常复杂,不推荐使用这个
代码:
1 |
FakeMeta
这个模块我们简称为FM(取自FakeMeta的大写首字母)
可实现绝大部分的功能,是一个非常常见的模块
代码:
1 |
HamSanwich
此模块我们简称为Ham(取Hamsandwich的前三个字母)
可实现绝大部分的功能,是一个常用的模块
代码:
1 |
cstrike
可实现大部分功能,是一个比较常用的模块
1 |
变量
变量是对“值”的引用,使用变量等同于引用一个值。每一个变量都有一个变量名。
1 | new a |
上面的代码声明新建变量a
变量类型
为了方便理解,我们可以把变量想象成一款水杯。
当然并不是所有水杯都一样,比如 “玻璃杯”,“不锈钢杯”,等等。
所以变量也分为几种可定义的类型,分别是:
- 临时变量 (temporary)
- 全局变量 (global)
- 布尔变量 (bool)
- 浮点变量 (Float)
临时变量
这是一种在函数内部的变量,通常其他函数无法读取
临时变量看起来像这样:
1 | public test( ) |
上述例子中的abc即为临时变量,如果不特殊处理。将无法被其他函数获取值
全局变量
全局变量不同于临时变量,他可以被其他函数所获取,通常用于变量判断,如:是否这局已经使用过了、当前累计的造成的伤害是多少、保存以及存储的坐标等等。
这种变量通常写在注册部分的上面,并且不处于函数内部
一般看起来像这样:
1 |
|
常见位置处于#include和plugin_init()的中间
上面的LaveSpace就是全局变量
而除了上面两种变量类型,还有其他一种变量类型可以来组合搭配
浮点变量
除了临时变量和全局变量,这两种外。
还有一种叫浮点变量,他可以配合临时变量和全局变量来使用,是一种组合型的变量。
浮点是很常用的一种变量类型,和普通变量的区别是:这种变量他只能存储浮点数(带小数的数,比如 12.5)
代码看起来像这样:
1 | new Float:变量名 |
技巧:浮点变量标记Float的大小写不要写错了,不然会出现编译错误。
布尔
这种变量又不同于浮点变量,但他可以以临时变量或全局变量的形式存在。
此变量仅有两个标记方式:
- true
- false
1 | new bool:变量名 |
bool变量的标记方式
true代表是的、真
false代表否定、假
我们可以通过下面的代码进行标准型判断
1 | new bool:atp |
注意:
布尔变量和浮点变量是独立的两个变量类型。
要么用布尔变量,要么用浮点,仅仅只能选择一种
函数
什么是函数
函数的英文名为 function,也就是功能。
打一个简单的比方:
在超市买散装食品的时候,你需要一个口袋,将口袋把散装食品装起来,然后去称称。然后就可以得到一个标签,贴在这个口袋上,收银员会扫描这个标签上的条形码,来获取称称时的价格。
理解方法:散装食品我们就可以理解为许多许多的单个代码,而口袋就是将单个代码封装了起来,变成了一个方便拿走的东西(即功能)。
在源代码中,函数一般以public开头,例如:
1 | public 函数名() |
同理,函数名也可以像变量名那样由你自己去定义。
函数类型
函数可简单分为三种类型
- 公用函数(public)
- 非公用函数
- 待调用函数(stock)
公用函数
这是很常见的函数类型。
比如说:
1 | public 函数名() |
函数名可以由你自己去写,但需要你自己记得住
非公用函数
这种变量是一种比较特殊的变量
他不同于公用函数,这种变量没有public定义部分
他的代码仅仅是:
1 | 函数名() |
有疑惑?不用担心,这种方式比较少见,一般不这样写
待调用函数
这是一种特殊的函数,他不同于非公用函数和公用函数
他只在被其他代码调用的时候才被读取,除此之外他都以注释代码段的方式存在。
1 | stock 函数名() |
注意:
- stock函数的内容,只在有其他函数调用的时才会被读取并触发。
- stock函数在没有被触发的时候,即使里面有错误也不会影响编译。
索引(index)
什么是索引
就如一个人的名字一样,可以让你知道“他”是谁
索引是用于函数传递信息用的一种标识
一般由“触发者”所引用,他的位置常见处于函数名字后面的括弧内
1 | public test(索引) |
对了,有时索引不止一个
因为可能函数会同时传递多个参数,所以不止一个。
比如说:
1 | public addhealth(id, health) |
提示:索引又可以称为参数,但参数不一定是索引。
字符串
字符串或串(String)是由数字、字母、下划线组成的一串字符
在代码中,含有””(双引号)的内容就是字符串
比如说:
1 | "你好" |
提示:对于字符串没什么太多限制,不过要注意的是,字符串必须两边都有”” (双引号),不然会造成编译错误。
数组
什么是数组
数组是一种存放一组值的集合,这组值可以是数字、字符串或者其他东西
之前我们说了变量,可以用水杯来代替,那数组怎么通过这种方式理解呢
那就是水杯里面装的水。
水装的多少可以理解为数组内的大小
数组中可以存储值,而数组又是在变量基础上来存储的,所以必须要有变量才能存储数组。
数组的作用是来存储每个玩家或每个实体的单独数据。
为什么要单独存储?因为如果仅仅用一个变量来存储所有玩家的数据,那么就会导致,上一个玩家的数据被下一个玩家的数据所覆盖,这是无论如何也不想发生的事情。
这时候就要用到数组来解决
一个变量可以存储一名玩家的数据,如果想存储多个玩家的数据,并且同时使用这个变量,就会为这个变量添加数组来存储。
数组是什么样子
在代码里,在变量命中后面写上[],视为数组。
数组一般和变量共同使用,他的作用是让“同一个变量存储更多的东西”
例如:
1 | new plane[] |
上例代码中[]就是数组的容器,
这个容器可以设定一个大小,比如:[3]
数组的大小
数组是一个从0开始的容器,在[]里面设定的值叫数组最大值
例如:
1 | ArrayTest[3] |
这个变量中的数组,最大值是3
所以数组从0开始,包含了[0],[1],[2],[3]
函数传递及触发
什么是传递
在学校运动会中,有一种比赛叫:接力跑,由一名同学跑完规定一圈后,将手中的棒子传递给下一个待跑的同学,这位同学接到之前同学拿过来的棒子后,就会开始奔跑,为胜利而战。
就如接力棒一样,函数之间可以传递一些数据,让其他函数跟着执行未完成的任务。
比如说:
1 | public aaa() |
即aaa函数触发了bbb函数
什么是触发
像我们家里电灯开关一样,当我们打开开关的时候,电灯会发亮,反之会熄灭。这就是电灯开关触发了电灯泡,让其开启或关闭。
那在反恐精英cs1.6这款游戏中,有什么可以明显看出的触发吗?
有的,比如说:在 cs_assault(72街仓库)这张地图中,作为CT的你去解救人质,进入人质房后,对着人质按“E”键,他们便会跟随你。
除此之外,有一些地图中,会有一种开关按“E”才能开门或开灯。
也比如:一些道具插件,输入某些命令才能打开菜单或者得到道具。
一些相同,一些不同
触发的方式和传递无比接近,但传递是由前一个函数传递给另一个函数,而触发时由一个命令或者一个行为来传递
判断以及表达式
在代码中可以做各种判断,来实现你需要的表达
判断是以if开头然后接上括号,在括号内的内为表达式
1 | if(表达式) |
基础表达式
| 表达式 | 释义 |
|---|---|
| && | 并且 |
| > | 大于 |
| < | 小于 |
| >= | 大于或等于 |
| <= | 小于或等于 |
| != | 不等于 |
| == | 绝对等于 |
例如:
若要判断World变量大于4并且小于10
代码为:
1 | if( World > 4 && World < 10 ) |
若要判断Earth变量大于6或者小于10
代码为:
1 | if( Earth > 6 || Earth < 10 ) |
运算符
代码中的运算符就像计算器中的加减乘除一样
常用运算符
| 运算符 | 释义 |
|---|---|
| + | 加上 |
| - | 减去 |
| * | 乘以 |
| / | 除以 |
| += | 加上指定值 |
| -= | 减去指定值 |
| *= | 乘以指定值 |
| /= | 除以指定值 |
比如要表达5 + 4 > 6这种判断,可配合判断写出如下代码
1 | if( 5 + 4 > 6 ) |
大括号组合
在代码中,当函数里的内容超过一行,这时候我们需要用到大括号{}把内容包围起来。
比如说:
1 | public test (id) |
但如果在一个函数中出现了多个条件,这种情况下括号就需要出入的括号数量统一
出入括号统一
1 | public test(id) |
在上述示例中,有两个{号,所以结尾也要两个},也就是说需要将代码内容完全包围起来
代码注释
以//开头直到行末的字符被视为行注释,被注释的代码编译器会自动忽略
1 | //这是一行注释 |
除了行注释,还有一种是用/*...*/将多行代码包裹起来,把一大块视为一个注释
1 | /* 从这里开始是块注释 |
插件初始化
每个插件在游戏载入地图的时候,会初始化。
所触发的函数就是plugin_init,我们可以在这个初始化函数中写上需要为这个插件所注册的信息
通常我们需要注册的基本信息就是,“插件的名字”,“版本号”,”作者名字”
这里我们要用到下面这个代码:
1 | register_plugin(注册的信息以及参数 ) //基本信息注册 |
这个代码拥有三个可供填写的参数,他们以字符串存在,并且每个参数会以,逗号来隔开。
这三个参数分别是:
- 插件名字
- 插件版本号
- 插件作者
如果以字符串配合逗号来分隔,就是下面这样
1 | register_plugin("插件名字", "版本号", ”作者名") |
配合基础代码组合后
1 |
|
提示:
- 上面的这个就是代码的基础,任何代码都会以这种形式展开。
- 双引号是必须是切换至英文输入法才可以,不能写中文输入法的双引号,不然会无法编译插件(报错)。
赋值
变量是能赋值的,例如将Test变量赋值为4
1 | new Test = 4 |
当赋值后,做判断的时候就可以判断这个变量中的值,例如
1 | if( Test > 0) |
除此之外,还有另一种赋值方式,和上面的代码基本一样,区别只是不在建立变量的时候直接赋值,看上去像这样
1 | new Test |
通常来说第二种非立即赋值,大部分情况下会先做一个判断,然后再来赋值
提示:
- 任何变量在创建后,他的值都为0。如果在创建变量时直接赋值,那么这个变量在创建后值就会发生变化,而不是默认的0
- 注意,不可以给Bool(布尔)变量赋于其他的值(因为他只拥有false以及true)
多重判断
超过两个(包含两个)的判断视为多重判断。
多重判断可以让我们写出的代码更精确的执行我们需要的效果。
常见写法有两种,一种是堆叠多重
将两个或两个以上的判断堆在一起,这种情况下就不用写 大括号{}来包括起来
1 | if( iTestB > 2) //这里不需要写 { |
上面代码中,由于从第二个判断后没有更多判断表达,所以需要加上{}大括号
单行多重判断
除了堆叠,还有另一种是单行多重,也就是将所有的表达式都写在一个判断中
1 | //表达式为:iTest > 2 并且 TestA < 3 或者 iTestC 刚好等于 5 |
判断的时机
一般会在一些特殊的时候进行判断,例如说:
- 玩家持有手雷的时候身上是否拥有AK47
- 玩家生命值是否小于60,并且该玩家是否拿着M4A1
- 玩家在地上的时候是否是蹲着的
- 玩家在空中的时候是否按住了鼠标左键没有放开
- 玩家在蹲着的时候是否还拿着AWP狙击枪
- 玩家的当前持有SG550武器,且子弹小于10发的时候是否他是站着的
- 更多就不列举了…
提示:多利用多重判断可以让判断更精确、更有效、减少BUG错误
循环
要计算1+2+3,我们可以直接写表达式:1 + 2 + 3; // 6
要计算1+2+3+…+10,勉强也能写出来。
但是,要计算1+2+3+…+10000,直接写表达式就不可能了。
为了让计算机能计算成千上万次的重复运算,我们就需要循环语句。
for循环
AMXX的循环有两种,一种是for循环,通过初始条件、结束条件和递增条件来循环执行语句块。
1 | new i |
让我们来分析一下for循环的控制条件:
- i=1 这是初始条件,将变量i置为1;
- i<33 这是判断条件,满足时就继续循环,不满足就退出循环;
- i++ 这是每次循环后的递增条件,由于每次循环后变量i都会加1,因此它终将在循环后不满足判断条件(i>=33)而退出循环;
- n 为定义的变量,默认是1000
当这段循环开始执行后,会执行32次,每次会触发n = n - 17,也就是32 x 17 = 544,而默认变量n创建时赋值为1000,也就是说这段代码将会变成下面这样计算
1 | 1000 - (32 x 17) = 456 |
while循环
for循环在已知循环的初始和结束条件时非常有用。
而上述忽略了条件的for循环容易让人看不清循环的逻辑,此时用while循环更佳。
while循环只有一个判断条件,条件满足,就不断循环,条件不满足时则退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:
1 | new x = 0 |
循环是让计算机做重复任务的有效的方法,有些时候,如果代码写得有问题,会让程序陷入“死循环”,也就是永远循环下去。AMXX插件的死循环会让游戏无法执行当前代码的逻辑,某些游戏会直接挂掉或闪退(卡死未响应等),因此,要特别注意死循环的问题。
在编写循环代码时,务必小心编写初始条件和判断条件。