在饥荒联机版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
注:
标签常用于Component判断Entity,也影响着生物的AI等。
_开头的标签比较特殊,在下面的组件部分介绍。
常见的tags如下:
--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
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,距离上次更新的时间
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开发——制作可入锅,烹饪,凉干的食物(七)