1、主循环
![【从零开始学Skynet】实战篇《球球大作战》(十三):场景代码设计(下)](https://imgs.yssmx.com/Uploads/2023/04/417405-1.png)
service/scene/init.lua中新增的内容:
function update(frame)
food_update()
move_update()
eat_update()
--碰撞略
--分裂略
end
现在思考一个问题,怎样开启稳定的定时器?可以开启一个死循环协程,协程中调用update,最后用skynet.sleep让它等待一小段时间。定义服务初始化方法init:
s.init = function()
skynet.fork(function()
--保持帧率执行
local stime = skynet.now()
local frame = 0
while true do
frame = frame + 1
local isok, err = pcall(update, frame)
if not isok then
skynet.error(err)
end
local etime = skynet.now()
local waittime = frame*20 - (etime - stime)
if waittime <= 0 then
waittime = 2
end
skynet.sleep(waittime)
end
end)
end
- 它会调用skynet.fork开启一个协程,协程的代码位于匿名函数中。
- pcall是为安全调用update而引入的,它的功能可以参照前面的xpcall。
-
waittime 代表每次循环后需等待的时间。
-
由于程序有可能卡住,我们很难保证“ 每隔 0.2 秒调用一次 update” 是精确的。
-
update 方法也需要一定的执行时间,等待时间waittime 的实际值应为 0.2减去执行时间,如下左图所示,图中 update 前的竖直黑线代表 update 的执行时间。
-
若某次执行时间超过间隔(如下右图 第 0.2 秒执行的 update ),则程序需要加快执行,只能给很短的间隔时间。使得运行较长时间后,最终会在第N 秒执行 N×5 次 update 。
2、移动逻辑
function move_update()
for i, v in pairs(balls) do
v.x = v.x + v.speedx * 0.2
v.y = v.y + v.speedy * 0.2
if v.speedx ~= 0 or v.speedy ~= 0 then
local msg = {"move", v.playerid, v.x, v.y}
broadcast(msg)
end
end
end
由于主循环会每隔0.2秒调用一次move_update,因此它只需遍历场景中的所有球,根据“路程 = 速度 × 时间”计算出每个球的新位置,再广播move协议通知所有客户端即可。
3、生成食物
function food_update()
if food_count > 50 then
return
end
if math.random( 1,100) < 98 then
return
end
food_maxid = food_maxid + 1
food_count = food_count + 1
local f = food()
f.id = food_maxid
foods[f.id] = f
local msg = {"addfood", f.id, f.x, f.y}
broadcast(msg)
end
- 判断食物总量:场景中最多能有50个食物,多了就不再生成;
-
控制生成时间:计算一个 0 到 100 的随机数,只有大于等于 98 才往下执行,即往下执行的概率是1/50 。由于主循环每 0.2 秒调用一次 food_update,因此平均下来每 10 秒会生成一个食物。
-
生成食物:创建 food 类型对象 f ,把它添加到 foods 列表中,并广播addfood 协议。生成食物时,会更新食物总量 food_count 和食物最大标识food_maxid 。
4、吞下食物
function eat_update()
for pid, b in pairs(balls) do
for fid, f in pairs(foods) do
if (b.x-f.x)^2 + (b.y-f.y)^2 < b.size^2 then
b.size = b.size + 1
food_count = food_count - 1
local msg = {"eat", b.playerid, fid, b.size}
broadcast(msg)
foods[fid] = nil --warm
end
end
end
end
![【从零开始学Skynet】实战篇《球球大作战》(十三):场景代码设计(下)](https://imgs.yssmx.com/Uploads/2023/04/417405-3.png)
代码中变量名的含义如下:
- pid:即playerid,指遍历到的小球对应的玩家id。
- b:遍历到的ball对象。
- fid:遍历到的食物id。
-
f :遍历到的 food 对象。
说明:本篇的场景服务代码更多的是为了演示如何使用框架, 没有很多性能考究。比如在eat_update代码中,双重嵌套for循环的计算量较大。在实际项目中,往往会使用一些简化的计算方法(后面会有简单的描述)。
至此,完成了场景服务的所有代码。文章来源:https://www.toymoban.com/news/detail-417405.html
5、主服务修改
--scene (sid->sceneid)
for _, sid in pairs(runconfig.scene[mynode] or {}) do
local srv = skynet.newservice("scene", "scene", sid)
skynet.name("scene"..sid, srv)
end
说明: 为简单起见,演示程序会开启固定数量的场景服务。在 实际项目中,可以仿照agent 动态开启场景服务。
完整项目地址:https://gitee.com/frank-yangyu/ball-server文章来源地址https://www.toymoban.com/news/detail-417405.html
到了这里,关于【从零开始学Skynet】实战篇《球球大作战》(十三):场景代码设计(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!