Gmod lua插件(Dishonored_Jump)解析

该文章详细介绍了如何在Gmod中开发插件,包括利用hook机制编写事件处理,如二段跳的实现,通过定时器和按键状态判断;视角晃动功能利用带阻尼的弹簧模型模拟;以及攀爬功能的实现,通过TraceLine检测障碍并进行二分法寻找落脚点。此外,文章还提到了控制台变量的创建和客户端与服务器端的通信机制。
摘要由CSDN通过智能技术生成

版本1.1

这个插件比较简单,主要是如何利用gmod事件编写。

首先是Gmod wiki连接:https://wiki.facepunch.com/gmod/

搜索hook就能找到这些事件。

第二,可以用vscode编辑gmod lua文件,vscode 有gmod lua插件,比较方便

vscode中的gmod插件

二段跳:在地面上按住空格键0.3s触发二段跳

玩家按键按下和弹起事件

boolean Player:KeyPressed( number key )
boolean Player:KeyReleased( number key )

利用hook.add(事件名,功能名,函数)添加事件的功能,函数可以像是下面这样的匿名函数

hook.Add("KeyPress", "DoubleJump_KeyValue",function( p, k)
    if ( k == IN_JUMP )  then
        KeyJump_Value = 1//空格键值		
    end
end)

hook.Add("KeyRelease", "Dishonored_DoubleJump_KeyValue",function( p, k)
    if ( k == IN_JUMP ) then
		KeyJump_Value=0
    end
end)

hook.Remove(事件名,功能名)可以移除添加的功能。

以上代码就能获取玩家的跳跃键状态,如果说起跳时在地上,那么创建一个0.3s的定时器,定时器到期后判断跳跃键是否按下,如果满足条件就给玩家加个向上的速度。

timer. Create(名字,时长 , 执行次数, 函数)

hook.Add("KeyPress", "DoubleJump_KeyValue",function( p, k)
    if ( k == IN_JUMP )  then
        KeyJump_Value = 1//空格键值		
		if p:OnGround() then
			timer. Create("DoubleJump_Timer",0.3 , 1 , function ()
				if KeyJump_Value==1 and !p:OnGround() then
					p:DoCustomAnimEvent(PLAYERANIMEVENT_JUMP , -1)//播放动作					 
					p:SetVelocity(Vector(0,0,500))//二段跳					
					p:EmitSound(Sound("dishonoreddoublejump"..math.random(1, 3)..".mp3"), 100, 100)//播放音效
				end
			end)		
		end
    end
end)

hook.Add("KeyRelease", "Dishonored_DoubleJump_KeyValue",function( p, k)
    if ( k == IN_JUMP ) then
		KeyJump_Value=0
    end
end)

这样就实现了二段跳。

有些需要注意的:

1、某些函数只能在服务器端实现(也就是在lua/autorun/server/下或者用lua_openscript打开),比如设置速度。

2、对玩家来说,SetVelocity貌似是起到加速的作用,SetLocalVelocity才是设置速度

视角晃动功能:落地时视角晃动

在耻辱中,玩家掉到地上的话,视角会像果冻或者说弹簧那样晃动。这里用带阻尼的弹簧去模拟,视角的晃动量可以用数值法或符号法解

撞击地面事件和设置视角偏移函数:

boolean GM:OnPlayerHitGround( Entity player, boolean inWater, boolean onFloater, number speed )
Player:SetViewOffset( Vector viewOffset )
Player:SetCurrentViewOffset( Vector viewOffset )

这两个比较奇怪,站立时,第一个似乎只对Z方向偏移启作用,第二个只对XY方向启作用,

蹲下时,第二个对XYZ都起作用,第一个可能对XY方向其作用,两个可能需要结合着用。

再对晃动程度做个限制,直接限制初始条件的速度就行。

hook.Add("OnPlayerHitGround", "Dishonored_ShakeView", function( p,iW,oF,speed)//下坠视角震荡:弹簧加阻尼,数值求解
	if !sv_dj_a_sv:GetBool() then return end
	local damping=10//阻尼系数
	local k=100//弹性
	//PrintMessage(HUD_PRINTTALK,tostring(p:GetVelocity()) )
	local Origin=Vector(0,0,0)//原点
	local Vel=p:GetVelocity()*sv_dj_svg:GetFloat()//下坠速度
	local Acc=Vector(0,0,0)//加速度(弹簧)

	local ViewOffset=Origin
	local Dt=0.015
	local SpeedLimit = sv_dj_svms:GetFloat()
	
	Vel[1]=math.Clamp(Vel[1], -SpeedLimit, SpeedLimit)
	Vel[2]=math.Clamp(Vel[2], -SpeedLimit, SpeedLimit)
	Vel[3]=math.Clamp(Vel[3], -SpeedLimit, SpeedLimit)
	timer.Create('Dishonored_view'..tostring(p['UserID']), Dt, 50, function()			
		ViewOffset=ViewOffset+(Vel + Acc*0.5*Dt)*Dt//二阶泰勒
		Acc=(Origin-ViewOffset)*k -damping*Vel
		Vel=Vel+Acc*Dt			
		p:SetViewOffset(ViewOffset+Vector(0,0,StandZOffset))//站立时,Z轴偏移立马生效,XY?	
		p:SetCurrentViewOffset(ViewOffset+Vector(0,0,DuckZOffset))//站立时,XY轴偏移立马生效,Z不生效。下蹲时对XYZ轴偏移立马生效
		//PrintMessage(HUD_PRINTTALK,tostring(ViewOffset) )
	end)
end)

这样就实现了视角晃动。

一些常数:

 1、默认站立视角偏移(0,0,64)

 2、默认蹲下视角偏移(0,0,28)

攀爬功能:

这里用TraceLine来判断,相当于射出一条线,然后返回线碰到的位置和法向量。

   Ck_L[i]=util.TraceLine({
				start = Vector(0,0,0),//起始位置
				endpos = Vector(0,0,100),//终止位置
				filter = LocalPlayer() //忽略实体:玩家
			})

那这任务相当于做一个避障判断,我不擅长这个,所以这插件BUG比较多。

这个事件每tick一次:

GM:Think()

这里8个tick检测一次。

检测

首先是水平扫描,在玩家位置创建一组间隔均匀、长度50左右的探测器

蹲着能通过的最大高度为36,为了不错过这个窗口,所以间隔为36的一半,当有探测器碰到了并且法向量Z值小于cos(45°),45°是能站稳的平面最小角度,这说明可能碰到障碍物,延长其他探测器以获得更多信息。

根据探测器的结果来判断哪个高度可能有可通行的路径,这里这样判断,上个与下个水平距离至少拉开10且不能再同一平面,防止以下情况。

通过点积与水平距离差来大致判断

通过后,再检测一下上面,由于没有准确的障碍物高度,所以大概判断一下,缝隙别太小就行

通过后利用二分法来搜索落脚点,实际上游戏环境比较复杂,而寥寥几次的Traceline信息量有限,所以必须做一些假设。

比较常见的障碍物形状差不多这样:

假设在障碍物上能找到个点将梯度符号给分割开来,然后类似找极值,对落脚点要求是能站住,并且高度也有一定要求,这样就比较简单了。

							if Ck_div.HitPos[3] >= Z and Ck_div.HitNormal[3]>Cos_BanAngle then
								climb_Data['Pos'] = Ck_div.HitPos
								climb_Data['Norm'] = Ck_div.HitNormal
								climb_over = true
								//print('收敛')
								break	
                            end

收敛条件:对高度有点要求,大致判断一下能否站稳

nxor(eyeDir:Dot(Ck_div.HitNormal)>0 , Ck_div.HitNormal[3]>0)

变化率符号:这两非异或一下可以得到

再列举一些情况,减少BUG

找到落脚点后再大致判断一下这个点会不会卡住。

	//是否会卡住
	for i=0,1,1 do
		for j=0,3,1 do
			Ck = util.TraceLine({
				start = climb_Data['Pos'] + Vector(0,0,18)*i,
				endpos = climb_Data['Pos'] + Vector(0,0,18)*i + Temp[j],
				filter = p})
			if Ck.Hit and (Ck.HitNormal[3] < climb_Data['Cos_BanAngle'] or Ck.Normal:Dot(Ck.HitPos-Ck.StartPos)<12) then
				if Debug then PrintMessage(HUD_PRINTTALK, '无效落脚点,距离:'..tostring(Ck.Normal:Dot(Ck.HitPos-Ck.StartPos))) end
				return
			end
		end
	end

落脚点判断完成了,做个抛物线插值到落脚点就可以了。

改变实体位置,只能在服务器端运行

Entity:SetPos( Vector position )

晃动玩家视角角度

Player:ViewPunch( Angle PunchAngle )

如果说检测是在客户端跑的,那就需要与服务器通信,比如说客户端发个定位,让服务器将玩家移动到那:

客户端:

net.Start("climb_over")
    net.WriteTable(climb_Data)
net.SendToServer()

服务器端:

util.AddNetworkString("climb_over")
net.Receive("climb_over", function(len,p)
	local climb_Data = net.ReadTable()//读取数据
	p:SetPos(climb_Data.Pos)
end)

类似用hook.add添加事件

创建菜单和控制台变量:

CreateConVar:创建控制台变量,如果已有就返回那个变量

第一个参数是名字,第二是默认值

FCVAR那些是变量的特性,wiki中可以查到

//添加控制台变量
CreateConVar( "sv_dj_a_dj", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//允许二段跳
CreateConVar( "sv_dj_a_a1", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//允许初级加速
CreateConVar( "sv_dj_a1", "100", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//初级弹速
CreateConVar( "sv_dj_a2", "300", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//二级弹速
CreateConVar( "sv_dj_g", "0.1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//增益
CreateConVar( "sv_dj_dt", "0.3", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//延时

CreateConVar( "sv_dj_a_sv", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//允许摇晃视角
CreateConVar( "sv_dj_svg", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//摇晃速度增益
CreateConVar( "sv_dj_svms", "1000", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//摇晃限制

CreateConVar( "sv_dj_a_cl", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//允许攀爬
CreateConVar( "sv_dj_sd", "50", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//水平扫描距离
CreateConVar( "sv_dj_sh", "72", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//不可跨越的高度
CreateConVar( "sv_dj_sg", "0.25", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//跨越距离增益
CreateConVar( "sv_dj_lt", "200", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//跨越判断-水平速度阈值
CreateConVar( "sv_dj_vt", "-200", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//跨越判断-垂直速度阈值
CreateConVar( "sv_dj_chs", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//启动高攀音效
CreateConVar( "sv_dj_cls", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//启动低攀音效
CreateConVar( "sv_dj_chvp", "30", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//高攀镜头晃动
CreateConVar( "sv_dj_clvp", "0", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//低攀镜头晃动

CreateConVar( "sv_dj_a_rb", "1", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//允许反弹
CreateConVar( "sv_dj_rbg", "2", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//反弹增益
CreateConVar( "sv_dj_rbl", "84", { FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE } )//头顶距离

添加控制台变量

Utilities是指添加到“设置”里

hook.Add("PopulateToolMenu", "djmenu", function()//添加菜单
    spawnmenu.AddToolMenuOption("Utilities", "耻辱跑跳(Dishonored_Jump)", "Dishonored_Jump", "配置(Config)", "", "", function(panel)
        panel:ClearControls()
		panel:Help("------------------二段跳(Double Jump)------------------")
		panel:CheckBox( "启用二段跳(Activate)", 'sv_dj_a_dj' )//复选框
		panel:CheckBox( "允许初级加速(Allow First Jump Gain)", 'sv_dj_a_a1' )
		panel:NumSlider( "初级弹速(Acc1)", "sv_dj_a1", -1000, 1000,0)//进度条
		panel:NumSlider( "二级弹速(Acc2)", "sv_dj_a2", -1000, 1000,0)
		panel:NumSlider( "延时(Delay)", "sv_dj_dt", 0.1, 1)
		panel:NumSlider( "增益(Gain)", "sv_dj_g", 0, 1)
        panel:Help("跳跃速度 = 自身速度 x 增益 + (0 ,0 , 弹速)\nJumpVel = Vel * Gain + ( 0, 0, Acc)")
    end)
end)

添加菜单

进度条、复选框之类得指定调节刚才创建的控制台变量

效果

补充:SetViewOffset只能在服务器跑,所以在多人模式下,上述方法实现的摇晃视角存在延迟,可以用客户端的CalcView事件来做,但这种方法只会晃动视角,手臂位置没变,可能需要重写当前武器的GetViewModelPosition方法,但这样的话可能会使一些武器的机瞄失效。

//本地
local svUpdate_not = true
local damping = 10 //阻尼系数
local k = 100 //弹性
local Vel = Vector(0,0,0)//速度
local Acc = Vector(0,0,0)//加速度(弹簧)
local ViewOffset = Vector(0,0,0)//偏移
local Dt = 0.015
net.Receive('shakeview', function(len,p)
	if First then return end
	Acc = Vector(0,0,0)
	ViewOffset = Vector(0,0,0)
	Vel = net.ReadVector()
	WEP = ply:GetActiveWeapon()
	function WEP:GetViewModelPosition(EyePos, EyeAng)
		if svUpdate_not then return end
		EyePos = EyePos + ViewOffset
		return EyePos, EyeAng
	end
	svUpdate_not = false
	timer.Create('Dishonored_view', 0.75, 1, function()
		svUpdate_not = true
	end)
end)
hook.Add('CalcView', 'Dishonored_ShakeView', function(ply, pos, angles, fov)
	if svUpdate_not then return end
	Dt = FrameTime()
	ViewOffset = ViewOffset + (Vel + Acc*0.5*Dt)*Dt//二阶泰勒
	Acc = (-ViewOffset)*k - damping*Vel
	Vel = Vel + Acc*Dt	
	local view = GAMEMODE:CalcView(ply, pos, angles, fov) 
	view.origin  = view.origin  + ViewOffset
	return view
end)


//服务器
hook.Add("OnPlayerHitGround", "Dishonored_ShakeView", function( p,iW,oF,speed)
	if !sv_dj_a_sv:GetBool() then return end
	local Vel = p:GetVelocity()*sv_dj_svg:GetFloat()
	local SpeedLimit = sv_dj_svms:GetFloat()
		Vel[1]=math.Clamp(Vel[1], -SpeedLimit, SpeedLimit)
		Vel[2]=math.Clamp(Vel[2], -SpeedLimit, SpeedLimit)
		Vel[3]=math.Clamp(Vel[3], -SpeedLimit, SpeedLimit)

    net.Start('shakeview')
		net.WriteVector(Vel)
	net.Send(p)

end)

后续版本的攀爬过程不再使用设置位置而是使用设置速度,抛物线的数值微分作为速度,不再需要判断是否会卡住,彻底解决了穿模问题。

补充:在第二个版本中使用了TraceHull来检测,就像探测线那样,只不过这个是盒子,

 

输入和Traceline一样,返回的结构体也是一样的,这可比Traceline好用多了,如果早用这个就不用搞那么多花里胡哨的。

第二就是,可以在SetupMove这个钩子中使用SetOrigin函数来改变位置相比SetPos,这个更平滑一些。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值