UE4SS环境下Lua脚本ToggleVisibility函数重复执行问题求助
问题分析与解决方法
你遇到的attempt to index a nil value错误,核心原因是变量未初始化就被访问,或者全局Hook/定时器逻辑未做足够空值校验,具体分两种场景修复:
一、TimerManager方式失效的原因与修复
UE4SS中TimerManager并非全局可直接调用的对象,需从玩家控制器或游戏实例中获取有效实例。另外原逻辑未判断LightComponent是否初始化就重复调用,导致空指针错误。
修改后的定时器实现:
local IDA, IDB local LightComponent local TimerHandle -- 存储定时器句柄,方便后续销毁 function HookNewLight(ContextParam) local Context = ContextParam:get() LightComponent = Context:GetPropertyValue("PointLight") if LightComponent and LightComponent:IsValid() then LightComponent:SetHiddenInGame(true, true) -- 仅当LightComponent有效时启动定时器 StartVisibilityCheckTimer() end end function StartVisibilityCheckTimer() -- 获取玩家控制器实例 local PlayerController = GetPlayerController() if not PlayerController then return end local TimerManager = PlayerController:GetTimerManager() if not TimerManager then return end -- 使用循环定时器替代递归调用,避免栈溢出 TimerHandle = TimerManager:SetTimer(function() CheckVisibility() end, 0.1, true) end function CheckVisibility() if (LightComponent and LightComponent:IsValid()) then -- 逻辑调整:如果当前可见则隐藏 if LightComponent:IsVisible() then LightComponent:SetHiddenInGame(true, true) end else -- LightComponent失效时停止定时器 local PlayerController = GetPlayerController() if PlayerController then local TimerManager = PlayerController:GetTimerManager() if TimerManager and TimerHandle then TimerManager:ClearTimer(TimerHandle) TimerHandle = nil end end end end RegisterHook("/Script/Engine.PlayerController:ClientRestart", function(Context, NewPawn) if IDA == nil then IDA, IDB = RegisterHook("/Game/Core/Characters/Player/AnathemaPlayerCharacter_BP.AnathemaPlayerCharacter_BP_C:ReceiveBeginPlay", HookNewLight) end end) function ToggleVisibility() if(LightComponent and LightComponent:IsValid()) then -- 原逻辑写反:SetHiddenInGame参数为是否隐藏,需取反当前可见状态 local NewState = not LightComponent:IsVisible() LightComponent:SetHiddenInGame(NewState, true) end end RegisterKeyBind(Key.F3, ToggleVisibility)
二、Actor Tick Hook失效的原因与修复
全局Hook/Script/Engine.Actor:Tick会对所有Actor触发回调,原逻辑未处理回调参数、未判断当前Actor是否为玩家角色,导致多数场景下LightComponent为nil。正确做法是仅Hook玩家角色的Tick:
local IDA, IDB, TickHookID local LightComponent function HookNewLight(ContextParam) local Context = ContextParam:get() LightComponent = Context:GetPropertyValue("PointLight") if LightComponent and LightComponent:IsValid() then LightComponent:SetHiddenInGame(true, true) -- Hook当前玩家角色的Tick local PlayerCharacter = Context:GetOwner() if PlayerCharacter then TickHookID = RegisterHook(PlayerCharacter:GetFullName() .. ":Tick", CheckVisibility) end end end function CheckVisibility(ContextParam) -- 先校验LightComponent有效性 if not (LightComponent and LightComponent:IsValid()) then -- 失效时取消Tick Hook if TickHookID then UnregisterHook(TickHookID) TickHookID = nil end return end if LightComponent:IsVisible() then LightComponent:SetHiddenInGame(true, true) end end RegisterHook("/Script/Engine.PlayerController:ClientRestart", function(Context, NewPawn) if IDA == nil then IDA, IDB = RegisterHook("/Game/Core/Characters/Player/AnathemaPlayerCharacter_BP.AnathemaPlayerCharacter_BP_C:ReceiveBeginPlay", HookNewLight) end end) function ToggleVisibility() if(LightComponent and LightComponent:IsValid()) then local NewState = not LightComponent:IsVisible() LightComponent:SetHiddenInGame(NewState, true) end end RegisterKeyBind(Key.F3, ToggleVisibility)
通用注意事项
- 所有访问
LightComponent的地方必须先做LightComponent and LightComponent:IsValid()校验,避免对象失效引发错误。 - 避免全局Hook所有Actor的通用函数(如Tick),尽量只Hook目标对象的函数,减少性能开销和错误概率。
- UE4SS中获取全局对象(如PlayerController)可使用
GetPlayerController()或GetGameInstance(),确保获取有效实例后再操作。
内容的提问来源于stack exchange,提问作者Account Kis
相关产品推荐
相关产品推荐

