[关闭]
@sogouwap 2017-06-06T04:32:25.000000Z 字数 6337 阅读 4541

Lv.4 轻难度 - AMXX插件编写教程


前言

本教程由小灰编写,这是对于 难度:进阶 更上一层的教程
建议在对 难度:进阶 学习之后再阅读
从现在,将会开始逐渐增加教程内容以及难度,请做好心理准备
我们的QQ群:139659650


快速切换

点击上述列表中 未打钩 的链接,以切换学习等级。

常见问题

这个教程学习下来需要多少时间?
你需要花一些时间直到你掌握它,并且每天都要坚持编写代码。
根据理解力的不同,需要的时间也不一样(三个月,六个月,或一个星期)

我需要准备什么样的系统?

你需要 Windows 7/8/10 或者 Windows XP 系统。



一、修改目标生命值

我们可以通过 pev 来修改玩家生命值

示例:

  1. set_pev(id, pev_health, 120.0)
我们可以看出来,上面这个代码可以将目标的血量改成指定的值

仅仅是这样还不够,我们大部分需要的效果是 加血 (在当前生命值的情况下添加指定值

思路:
首先我们需要获取玩家生命值,然后再通过获取到的值来进行添加生命值

获取生命值:

  1. new Float:hp
  2. pev(id, pev_health, hp) //获取生命值后存储到 hp 浮点变量中

上例中存储到 hp 变量之后,那么此时要做的就是设定生命值 = ( 当前生命值 + 添加的值 )

即:

  1. new Float:hp
  2. pev(id, pev_health, hp)
  3. set_pev(id, pev_health, hp + 40.0) //依靠当前生命值添加 40生命值

注意:
1. 索引(index)必须是有效的,不能是非玩家实体并且还没用(比如地图本身)
2. 设定的生命值 或者 添加值 必须是浮点数。不然你会发现本来设定140的生命值变成 49239239932 巨大数值了
3. 除了 hp + 40.0 你还可以 hp - 40.0,发挥你的想象力吧(不过除法就算了,因为大概会变成0血)

二、死亡之后重生

虽然说是死亡后重生,但是实际上我们还可以做到 即使活着也能重新复活

当然这样就不具有实际价值,只是少加一个 判断 而已,我们还是做我们需要的目标吧

我们需要用到 hamsanwich 模块中的 ExecuteHamB 来触发Ham函数

触发HAM是什么意思?

Ham事件不止可以用来勾住,也可以直接用来触发(触发的时候也可以被勾住)。
比如爆炸的时候造成的伤害我们就可以触发出来,这样即使原有不存在的效果,我们也可以自己做出来

对于Ham事件来说,重新复活的事件代码是 Ham_CS_RoundRespawn

我们通过 ExecuteHamB 来触发这个事件

示例:

  1. ExecuteHamB(Ham_CS_RoundRespawn, id) //通过触发Ham事件中的 Ham_CS_RoundRespawn 来让 id 复活

那应该怎么写才能实现死亡后复活?

很简单,我们需要先勾住玩家的死亡事件,然后触发函数,通过这个被触发的函数来延迟复活玩家
不太理解吗?

示例:

  1. #include <amxmodx>
  2. #include <hamsandwich>
  3. public plugin_init()
  4. {
  5. RegisterHam(Ham_Killed, "player", "PlayerKilled", 1)
  6. }
  7. public PlayerKilled(vic, attacker)
  8. {
  9. if(!is_user_connnected(vic)) return //如果被击杀的玩家不是服务器中的 返回
  10. set_task(3.0, "Task_Spawn", vic) //设定延迟事件(延迟3.0秒,触发 Task_Spawn 函数,被触发的索引是 vic(即:受伤者) )
  11. }
  12. //被触发的复活函数
  13. public Task_Spawn(id)
  14. {
  15. if(is_user_alive(id)) return //如果在函数触发的时候 玩家已经复活 返回
  16. ExecuteHamB(Ham_CS_RoundRespawn, id)
  17. client_print(id, print_chat, "你已经复活。")
  18. }

注意:
1. 写代码的时候需要考虑全面,比如玩家在复活函数被延迟触发的时候,此时玩家已经处于存活状态,此时就不应该继续执行复活代码,所以我们应该添加一个 返回 。不然玩家会在延迟时间达到的时候重新又复活一次,这样的效果是我们不希望见到的。
2. 延迟函数的时候,触发的函数名一定要和被触发的函数名一致,并且要注意是否大小写一致(不一致的大小写会出现无法触发函数的问题)
3. 这个代码只用到了 hamsandwichamxmodx 模块中的代码,所以这里不需要写上 fakemeta

三、目标移动向量

在游戏中,玩家的每次移动或跳跃都是会改变 pev_velocity 移动向量(速率)的

其移动的向量也是一个数组 和 坐标系 几乎一样,也是三个轴 X, Y ,Z

其中 Z轴 表示了目标上升还是在下降

获取示例:

  1. new Float:vel[3]
  2. pev(id, pev_velocity, vel)

注意是需要新建的是 浮点数的数组变量 然后通过 pev 来获取并存储到 新建变量

其中 vel[2] 代表 Z轴,其前面 vel[0] (X轴)vel[1] (Y轴) 代表了 左右 前后 不同的移动方向

比如让目标向上飞起:

  1. new Float:vel[3]
  2. vel[2] = 600.0
  3. set_pev(ent, pev_velocity, vel)

注意:
1. 新建变量用来存储的时候,必须是浮点变量
2. 如果 vel[2] (Z轴) 中的浮点数值是正数那么代表 正在上升或者原地不动,反之如果是负数 代表 正在坠落或下降中

四、手雷的爆炸时间

本次要讲的是修改手雷爆炸时间。似乎这个方法对于 烟雾弹 没有效果,具体我没有去测试

代码 pev_dmgtime 用于获取或改变游戏中手雷的爆炸时间

可是应该怎么修改呢?

这里我们要用到一个函数 FM_SetModel ,这个函数会在设定模型的瞬间触发(本例中代表 投掷手雷的瞬间就会执行)

示例:

  1. #include <amxmodx>
  2. #include <fakemeta>
  3. public plugin_init()
  4. {
  5. register_forward(FM_SetModel, "Fw_SetModel")
  6. }
  7. public Fw_SetModel(ent, model[])
  8. {
  9. }

上例中,通过触发 FM_SetModel 事件勾住了我们的 Fw_SetModel 函数,
该函数中有两个参数,分别是:

  1. 实体
  2. 模型路径

什么是实体?

游戏中会见到各种形形色色的实体,比如 爆炸点的箱子,安装之后在 地上的C4炸弹,丢弃在地上的武器,投掷出去的手雷 等等,这些都是实体,实体还分为点实体固体实体 这里暂不细说。

刚刚我们稍稍了解了一下 什么是实体,那么回到本例 即投掷出去的手雷(在空中飞的)就是一个实体

那么回到代码中: public Fw_SetModel(ent, model[]) 也就是说其中那个 ent 代表了飞行中的手雷

那么我们需要在这个函数中判断什么?首先需要添加

if(!pev_valid(ent)) return //无效实体即返回

示例:

  1. public Fw_SetModel(ent, model[])
  2. {
  3. if(!pev_valid(ent)) return //无效实体即返回
  4. }

为什么要判断 pev_valid( ) 无效的实体?

无效的实体即 实体不存在了实体被删除了实体获取不到
pev_valid( ) 在我们后期制作插件的时候尤其重要,因为无效的实体会导致游戏崩溃或者卡住,甚至控制台报错
为了避免这些不必要的情况,为了代码的稳定性是必须添加这个的

那么接下来我们需要做的也不会太难,我们需要再添加一个判断,判断这个实体的 实体名 是不是 手雷

什么是实体名?

实体名 (pev_classname) 是用来区分不同实体的最佳途径,对于CS来说几乎每个武器都有自己的实体名,手雷的实体名是 grenade,玩家自己的实体名是 player ,爆炸点的箱子实体名是 func_breakable 等等。通过判断实体名我们可以清晰的分辨哪些 需要修改,哪些是不必要 的。

既然我们知道手雷的实体名是 grenade 接下来要做的也就很简单了:判断实体名是不是我们需要的那个

这里我们会用到一个 equal 字符串判断代码,稍后会讲到

首先获取实体名,注意这里的 索引要改成 ent 了,因为我们获取的是别的东西

  1. new class[33]
  2. pev(ent, pev_classname, class, 32)

现在实体名已经存储到了 class 变量中,接下来我们要做的就是判断这个 class 中是否是 grenade 字符串。简单说就是 判断这个实体的名字是不是 grenade

示例(判断是否是那个字符串):

  1. if(!equal(class, "grenade")) return //如果class变量中 不是 grenade 字符串 那么 返回

然后我们还需要判断一下模型路径,因为 grenade实体名 还可能包含 C4炸弹 (安装后)烟雾弹

和上面的写法一样 只是要改变一下 变量名

  1. if(!equal(model, "models/w_hegrenade.mdl")) return //如果字符串中的模型路径不是获取的那个 返回

我们可能需要同时使两个手雷类型 (高爆手雷,闪光弹) 的时间都发生变化,此时我们要用到 && 或 || 来添加多重判断

示例:

  1. if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return

不太懂?那就用代码翻译一下 即:如果不是手雷的路径 并且也不是 烟雾弹的路径 那么返回,这样就可以避免别的相同实体名使用这个函数了

我们组合一下代码就会变成下面这样:

  1. #include <amxmodx>
  2. #include <fakemeta>
  3. public plugin_init()
  4. {
  5. register_forward(FM_SetModel, "Fw_SetModel")
  6. }
  7. public Fw_SetModel(ent, model[])
  8. {
  9. if(!pev_valid(ent)) return //无效实体即返回
  10. new class[33]
  11. pev(ent, pev_classname, class, 32) //获取实体的名字存储到 class变量中
  12. if(!equal(class, "grenade")) return //如果获取到的实体名不是 grenade 字符串 那么返回
  13. //如果获取到的模型路径既不是手雷也不是闪光 返回
  14. if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return
  15. }

写到这里,几乎就快完成目的了。接下来要做的就是修改手雷的爆炸时间,这里的手雷指的就是实体,所以我们写上 ent

示例:

  1. set_pev(ent, pev_dmgtime, 爆炸时间)

上面这段看上去没什么不对,但是实际上如果在游戏中测试的话,手雷扔出去后会 瞬间爆炸 所以我们还需要通过游戏时间来决定,配合 get_gametime() 完成最终效果吧!

  1. set_pev(ent, pev_dmgtime, get_gametime() + 12.0) //爆炸时间设定为 当前游戏时间 + 12秒

我们将所有代码组合一下,就会变成这样

  1. #include <amxmodx>
  2. #include <fakemeta>
  3. public plugin_init()
  4. {
  5. register_forward(FM_SetModel, "Fw_SetModel")
  6. }
  7. public Fw_SetModel(ent, model[])
  8. {
  9. if(!pev_valid(ent)) return //无效实体即返回
  10. new class[33]
  11. pev(ent, pev_classname, class, 32) //获取实体的名字存储到 class变量中
  12. if(!equal(class, "grenade")) return //如果获取到的实体名不是 grenade 字符串 那么返回
  13. //如果获取到的模型路径既不是手雷也不是闪光 返回
  14. if(!equal(model, "models/w_hegrenade.mdl") && !equal(model, "models/w_flashbang.mdl")) return
  15. set_pev(ent, pev_dmgtime, get_gametime() + 12.0) //爆炸时间设定为 当前游戏时间 + 12秒
  16. }

如此,你就成功了

注意:
1. 本次我们初次涉及到了实体,以后我们还会学习建立一个新的实体
2. 如果你需要修改模型则需要将函数触发改为 post ,对于当前代码来说即 register_forward(FM_SetModel, "Fw_SetModel", 1)
3. 若将爆炸时间改成 set_pev(ent, pev_dmgtime, get_gametime()) 手雷将瞬间爆炸
4. equal 的判断需要注意 括号 ( ) 的相符度,具体可参看我的写法

五、判断按键

判断玩家按的什么键,这是我们在后期经常会遇到的问题

如何解决呢?使用代码:pev_button 即可

通过这个代码,可以获取到内置的一些键位,比如 基础移动(A,S,D,W) ,鼠标左键,右键,E键(使用键),R键(装子弹) 等等…

有哪些键位可以被获取到?

引用于 >> hlsdk_const.inc (你可以查看这个文件)

  1. IN_ATTACK //鼠标左键
  2. IN_JUMP //跳跃键
  3. IN_DUCK //蹲下键
  4. IN_FORWARD //前进键(默认是W)
  5. IN_BACK //后退键(默认是S)
  6. IN_USE //使用键(默认是E)
  7. IN_CANCEL //取消键(默认是ESC)
  8. IN_LEFT //左旋键(默认是 ← 箭头)
  9. IN_RIGHT //右旋键(默认是 → 箭头)
  10. IN_MOVELEFT //左平移键(默认是A)
  11. IN_MOVERIGHT //右平移键(默认是D)
  12. IN_ATTACK2 //鼠标右键
  13. IN_RUN //暂时不明
  14. IN_RELOAD //弹药装填键(默认是R)
  15. IN_ALT1 //摇杆控制键(默认是ALT)
  16. IN_SCORE //比分查看键(默认是TAB)

那应该怎么写出判断代码?

比如说我们要判断:按下了鼠标左键 ,可以这样写:

  1. if(pev(id, pev_button) & IN_ATTACK) //如果玩家按了鼠标左键
  2. {
  3. }

其中 & 相当于代表一个代码链接,让其获取到的结果对 定义的键位 进行对比

那如果要表达玩家 没有按这个键 呢?
我们以鼠标左键为例,需要添加一个 ! 反义判断符

  1. if( ! ( pev(id, pev_button) & IN_ATTACK) )
  2. {
  3. client_print(id, print_chat, "没有按下鼠标左键")
  4. }

单次键位判断

如果需要判断一个键位,是否处于按下但是没有持续按住的情况(比如手枪USP,按一下射一次) 应该怎么做?

这里我们还需要用到一个 pev_oldbuttons ,配合 &&(并且) 来判断上一次键位是否依然是那个

示例:

  1. //如果玩家按下了鼠标左键并且没有一直按住,即继续执行函数括号中的内容
  2. if(pev(id, pev_button) & IN_ATTACK && !(pev(id, pev_oldbuttons) & IN_ATTACK))
  3. {
  4. client_print(id, print_chat, '触发成功")
  5. }

Tutorial End - 轻难度 教程结束

你可以查看 上一章(进阶) / 下一章(中难度)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注