饥荒联机版Mod开发——常用inst方法(八)

饥荒联机版Mod开发——常用inst方法(八)

  • 前言
  • 生成实体
  • 监听/推送事件
  • 使用标签
  • 增删组件
    • 网络组件
  • 定时/阶段任务
  • 回调函数
  • 父子实体
  • 平台
  • 删除
  • 位置,角度
  • 显示/隐藏
  • 判断
  • 大脑
  • 状态图
  • 皮肤
  • 地图
  • 物理
  • 传送门

前言

在饥荒联机版Mod开发——Class, Prefab, component,debug(四)
中我们简单介绍了游戏实体(Entity)和组件的使用方法,在这一章里我们将学习Entity的常用方法,想了解更多也可以去看文中提到的源代码

生成实体

生成实体可分为两种情况,一种是控制台生成,另一种是代码里生成。前者是我们进游戏后调试用,后者是日常写代码时用。

控制台生成预设物实体

--consolecommands.lua
--在鼠标位置生成预设物实体,如猪人 "pigman"
function c_spawn(prefab, count=1) ... end
--给予玩家可放进物品栏的预设物实体,如树枝 "twigs"
function c_give(prefab, count=1) ... end

代码里生成预设物实体、实体

--mainfunctions.lua
--生成并返回预设物实体,一般只需要个name。如果creator没有对应皮肤,则显示默认皮肤
function SpawnPrefab(name, skin, skin_id, creator) ... end
--在原本的实体位置生成预设物实体,并删除原本的实体,一般只需要前两个参数
function ReplacePrefab(original_inst, name, skin, skin_id, creator) ... end

--生成并返回实体,name可省,常见于各prefab的fn中
function CreateEntity(name)
    local ent = TheSim:CreateEntity()	--C层的Entity数据,存放在inst.entity
    local guid = ent:GetGUID()	--GUID独一无二的标志数字
    local scr = EntityScript(ent)	--entityscript.lua,这就是inst
    if name ~= nil then
        scr.name = name
    end
    Ents[guid] = scr	--全部Entity都放到Ents这个全局表里(声明在main.lua)
    NumEnts = NumEnts + 1	--全部Entity的数量(声明在main.lua)
    return scr	--返回inst
end

CreateEntity中我们可以看成,常见的 inst:XXX() 实际调用的是entityscript.lua中的方法

此外mainfunctions.lua中还定义了其他的全局函数,如
获取时间的GetTick()、
游戏开始时的Start()、保存游戏SaveGame(…),关闭游戏Shutdown()、SaveAndShutdown()
游戏回调OnFocusLost()、OnFocusGained()、
Entity回调OnEntitySleep(),OnEntityWake,OnPhysicsWake,OnPhysicsSleep、
GUID处理函数GetEntityString等; inst.GUID

监听/推送事件

事件作为脚本中传递信息的重要途径,在降低代码复杂度,提供可扩展型上发挥着至关重要的作用。本质是把函数(fn)先存起来(ListenForEvent),在特定的时候(PushEvent)再传入参数(source, data)来调用函数(fn)。

--entityscript.lua

--监听/取消监听事件
--event:string,独一无二的事件名,对应inst:PushEvent中的event
--fn:监听的函数,参数是 (source, data),source对应调用PushEvent的Entity,data对应PushEvent时的data,也可以print出来看看data是什么
--source:被监听的实体,默认监听自身
inst:ListenForEvent(event, fn, source=inst)
inst:RemoveEventCallback(event, fn, source=inst)
inst:RemoveAllEventCallbacks()

--推送事件,运行fn
--event:string,独一无二的事件名,mod的event推荐加个前缀		
--data:any,一般是个table
--如果inst有sg(stategraph状态图)且sg在监听这个事件,也会调用 inst.sg:PushEvent(event, data)
--如果inst有brain(大脑/AI)也会调用 inst.brain:PushEvent(event, data)
inst:PushEvent(event, data)

--table的key可以是任意非nil值,通过table[key]来访问,key是字符串时可以写成table.key
--当前source被监听的事件列表 source.event_listeners[event][inst] = {fn1, fn2, ...} 
--当前inst在监听的事件列表 inst.event_listening[event][source] = {fn1, fn2, ...}

在监听世界(TheWorld)时,可以把source赋值为TheWorld。不过如果要监听世界的状态(worldstate)(时间,洞穴时间,月圆,天气,季节,温度,湿度,下雪,下雨等)
更推荐使用下面的函数

--entityscript.lua

--TheWorld的预设物在prefabs/world.lua
--监听/取消监听世界TheWorld.state中的状态 (components/worldstate)
--var:components/worldstate.lua中 SetVariable(var, val, togglename)
--fn:一般参数为(inst, val),不过start或stop开头的var,只有inst。
inst:WatchWorldState(var, fn)
inst:StopWatchingWorldState(var, fn)
inst:StopAllWatchingWorldStates()

--inst.worldstatewatching[var] = {fn1, fn2, ...}
--_watchers[var][inst] = { {fn, inst}, ... }

--举个例子components/worldstate.lua中
--SetVariable(var, val, togglename)
--有第三个参数时,有start和stop开头var  (val and "start" or "stop")..togglename
SetVariable("cavephase", phase)	
-->	var="cavephase",  fn的val=phase,洞穴白天到黄昏等时期变化时推送
SetVariable("iscaveday", phase == "day", "caveday")		
--> var="iscaveday"	 fn第二个参数val=(phase == "day"),一个boolean
--> var="startcaveday",val为true时推送,fn参数只有inst
--> var="stopcavaday",val为false时推送,fn参数只有inst

注:

  • TheWorld是Entity,赋值在prefabs/world.lua,负责管理世界、地图、时钟、玩家生成等。
  • 判断是否是地下世界,TheWorld:HasTag(“cave”)
  • The开头及Ents等全局变量(声明在main.lua),可以在游戏中直接访问到
  • 在modmain中也可以访问到main中的全局变量,不过直接访问时可能还没赋值,所以一般写在
    AddGamePostInit(fn()) --先
    AddSimPostInit(fn()) --后,和单机不同,fn没有参数,ThePlayer也没赋值
    如果需要访问ThePlayer,用下面函数
    AddPlayerPostInit(fn(player)) --在fn中用ThePlayer==player来判断是否当前玩家

使用标签

标签常用于Component判断Entity,也影响着生物的AI等。
_开头的标签比较特殊,在下面的组件部分介绍。
常见的tags如下:

  • NOCLICK:不可点击物体
  • FX:特效
  • lightningtarget:闪电目标(移除就不不会被雷劈)
  • electricdamageimmune:电伤害免疫(机器人免疫电伤害)
  • irreplaceable:无法替代,玩家离开世界自动掉落(眼骨等)
  • player、playerghost:玩家状态(也可以用inst.components.health:IsDead()来判断)
  • scarytoprey:吓跑小动物(移除就不会吓跑小动物)
--tag: string
inst:AddTag(tag)
inst:RemoveTag(tag)
inst:HasTag(tag)
--tags = {"tag1", "tag2", ...}
inst:HasTags(tags)	--拥有全部 
inst:HasOneOfTags(tags)	--拥有其中一个标签

增删组件

--name:string, 对应components中文件夹中的文件名,组件的使用看第四章
inst:AddComponent(name)	--有_replica的同名组件会自动添加(服务器)
inst:RemoveComponent(name)

--inst.components.name
--inst.repica.name

网络组件

以_replica的结尾同名组件 ,用网络变量(netvars.lua)进行服务器和客户端之间的通信,需要服务器和客户端都添加以_replica的结尾同名组件。
这里先讲下添加组件流程,具体使用以后出个网络通信相关的教程,感兴趣的也可以先去看
官方的netvars教程
注:动作的网络变量了entityscript.lua的AddNetwork(),其他的大部分在player_classified.lua

以玩家的health组件为例子(服务器修改血量,通知)

local inst = CreateEntity()
inst.entity:AddNetwork()	--网络通信当然要添加网络了
inst:AddTag("_health")	--添加网络组件标签
inst.entity:SetPristine()	--客户端根据添加的_开头的tag,添加xxx_replica组件
if not TheWorld.ismastersim then return inst end	--客户端返回,下面是仅服务器代码
inst:RemoveTag("_health")	--移除网络组件标签,等AddComponent会再次加上。官方书写习惯,不删问题应该也不大
inst:AddComponent("health")	--服务器添加health和health_replica组件,和_health标签。
--注:客户端只需要知道玩家的血量,其他生物的血量并不需要知道,所以其他生物的预设物fn中
--并不会有inst:AddTag("_health") 和 inst:RemoveTag("_health") 这两句

定时/阶段任务

--entityscript.lua	scheduler.lua

--下面4个函数的返回值都是Periodic  可通过 :Cancel() 取消任务	
--定时任务,time秒后调用 fn(inst, ...)。仅调用一次,不需要手动取消
--注:inst:DoTaskInTime(0,...) 时会在下一帧调用; Sprite中是毫秒
inst:DoTaskInTime(time, fn, ...)	

--阶段任务,initialdelay秒后调用fn(inst, ...),之后每隔time秒调用一次fn,需要手动取消
--一般都是 inst.task = inst:DoPeriodicTask(...),某个时候 inst.task:Cancel()
inst:DoPeriodicTask(time, fn, initialdelay=time, ...)	

--和上面的类似,不过定时不受世界暂停时影响
inst:DoStaticTaskInTime(time, fn, ...)	
inst:DoStaticPeriodicTask(time, fn, initialdelay, ...)	

inst:CancelAllPendingTasks()	--取消上面所以的定时任务

回调函数

-------- entityscript.lua---------
Fn:对应函数 XXXCb:回调函数,非nil时调用
一般都是先调用ComponentCb,之后再调用EntityCb,除非是Pre开头的
EntityCb常规写法: inst.OnXXX = function(inst, …) … end
ComponentCb常规写法:function Cmpname:OnXXX(…) … end

  • refs:引用的Entity列表或nil,{Entity1, Entity2, …}
  • cmpdata:一般是个table,仅保存数值,不保存引用
  • newents:重新开服时生成的Entitys,一般用不上
  • builder:一般是玩家的Entity
  • dt:deltatime,距离上次更新的时间
Fn EntityCb ComponentCb
inst:GetPersistData() inst:OnSave(data) -> refs cmp:OnSave() -> cmpdata, refs
inst:SetPersistData(data, newents) inst:OnPreLoad(data, newents) inst:OnLoad(data, newents) cmp:OnLoad(cmpdata, newents)
inst:LoadPostPass(newents, savedata) inst:OnLoadPostPass(newents, savedata) cmp:LoadPostPass(newents, cmpdata)
inst:OnBuilt(builder) inst:OnBuiltFn(builder) cmp:OnBuilt(builder)
inst:Remove() inst:OnRemoveEntity() cmp:OnRemoveEntity()
inst:LongUpdate(dt) inst:OnLongUpdate(dt) cmp:LongUpdate(dt)

--------Update:update.lua--------
dt: deltatime,距离上次更新的时间

  • WallUpdate:墙壁更新
  • Update:日常更新
  • StaticUpdate:世界暂停时开始更新
  • LongUpdate:长时间间隔的更新,如进出洞穴,跳天数等
Fn EntityCb ComponentCb
WallUpdate(dt) - cmp:OnWallUpdate(dt)
Update(dt) - cmp:OnUpdate(dt)
StaticUpdate(dt) - cmp:OnStaticUpdate(0)
LongUpdate(dt, ignore_player) inst:OnLongUpdate(dt) cmp:LongUpdate(dt)

--------Player:player_common.lua-----------.
玩家特有回调
inst.OnNewSpawn = function() … end
inst.OnDespawn = function() … end

父子实体

--entityscript.lua
--child:Entity
inst:AddChild(child)
inst:RemoveChild(child)
--inst.children[child] = true
--child.parent = inst	child.platform = nil

平台

当前只用在船上,让玩家随船移动

--entityscript.lua
--child: Entity
inst:AddPlatformFollower(child)
inst:RemovePlatformFollower(child)
inst:GetPlatformFollowers()
inst:GetCurrentPlatform()

--inst.platformfollowers[child] = true
--child.platform = inst		child.parent = nil

删除

--移除自身和子Entity,从父Entity的孩子中移除
--触发事件onremove,OnRemoveEntity回调
--移除所以监听、组件、定时和阶段任务
inst:Remove()	
inst:IsValid()	--正常返true,被Remove返false

位置,角度

inst:GetPosition()	--> Point(inst.Transform:GetWorldPosition())  一个table
inst:GetRotation() --> inst.Transform:GetRotation()

inst:FacePoint(x, y, z)	--> 面向某点
inst:ForceFacePoint(x, y, z)  --> 强制面向某点
inst:FaceAwayFromPoint(dest, force)

--DistanceSq: 距离的平方,少了sqrt,减少计算量
inst:GetDistanceSqToInst(inst) --> 距离的平方
inst:IsNear(otherinst, dist) --> inst:GetDistanceSqToInst(otherinst) < dist*dist
inst:GetDistanceSqToPoint(x, y, z)	--> 距离的平方
inst:IsNearPlayer(range, isalive)	--> range半径内是否有玩家
inst:GetNearestPlayer(isalive)	--> 最近的玩家
inst:GetDistanceSqToClosestPlayer(isalive)	--> 距离最近的玩家的距离的平方

显示/隐藏

--显示/隐藏,仅改变显示
inst:Show()
inst:Hide()

--显示到屏幕,常用于物品被移出物品栏
--IsInLimbo()->false	inst:Show()
--开始brain,sg,Physics,Light,AnimState,DynamicShadow,MiniMapEntity
--inst:PushEvent("exitlimbo")
inst:ReturnToScene()

--从屏幕隐藏中,常用物品被放进物品栏
--IsInLimbo()->true		inst:Hide()
--停止brain,sg,Physics,Light,AnimState,DynamicShadow,MiniMapEntity
--inst:PushEvent("enterlimbo")
inst::RemoveFromScene()

判断

inst:IsOnWater()	--是否在水上,即没船且脚下是海
inst:IsInLimbo()	--是否在物品栏,仅在mastersim(服务器)有效,客户端用INLIMBO标签判断?
inst:GetIsWet()		--是否是湿的,服务器客户端都可以使用
inst:IsInLight()	--是否在光照内
inst:IsValid()		--正常返true,被Remove返false

大脑

--brainfn: require("brains/xxxbrain")
inst:SetBrain(brainfn)
inst:StopBrain()
inst:RestartBrain()

状态图

--name: "SGXXX"		stategraphs文件夹下的文件名
inst:SetStateGraph(name)
inst:ClearStateGraph()

皮肤

--entityscript.lua
inst:GetSkinBuild()
inst:GetSkinName()
--inst.AnimState:SetSkin(build_name, def_build)

地图

--entityscript.lua
--设置参数,具体应用在components/map.lua
inst:SetDeployExtraSpacing(spacing)
inst:SetTerraformExtraSpacing(spacing)
inst:SetGroundTargetBlockerRadius(radius)

inst:IsOnValidGround()	--是否在地面上
inst:IsOnOcean(allow_boats)	--是否在海上
inst:IsOnPassablePoint(include_water, floating_platforms_are_not_passable)  --是否可通过某点

inst:GetCurrentTileType()	--获得当前地皮,如果要获得有效地皮先判断inst:IsOnValidGround()

物理

设置物理的时候,一般用的是 standardcomponents.lua 里的 MakeXXXPhysics,不过偶尔有需要重新设置物理碰撞半径的时候

--entityscript.lua
inst:SetPhysicsRadiusOverride(radius)	--设置覆盖的物理碰撞半径
inst:GetPhysicsRadius(default)	--返回物理碰撞半径或default

传送门

→饥荒联机版Mod开发——制作栏(九)
←饥荒联机版Mod开发——制作可入锅,烹饪,凉干的食物(七)

你可能感兴趣的:(饥荒Mod开发,lua,开发语言)