有那么一种需求, 就是玩家在开房间玩游戏的同时, 允许观众进入围观, 但是不能对观众开放玩家数据, 观众也不允许影响玩家的功能.
这篇文章就谈论一下这种需求的简单实现, Colyseus开发非常灵活, 所以方法不只一个, 本例不作为官方推荐方案, 只是抛砖引玉.
首先, 建立服务器项目, 最便捷的方法参考这里 https://discuss.colyseus.io/topic/633/右键菜单-在此创建-colyseus .
用喜欢的IDE打开, 我喜欢用Intellij Idea 或者 webstorm.
用 Git 备份到 Github 上面.
养成好习惯O(∩_∩)O
我们的思路很简单:
服务器里有两个不自动销毁的房间: 一个游戏室, 一个观众室.
- 玩家进入游戏室, 两种房间显示玩家形象.
- 玩家控制其在游戏室的坐标位置, 两种房间都可以看到其最新位置.
- 观众室成员只能观看, 不能控制任何玩家形象.
- 玩家退出游戏, 两种房间销毁其形象.
首先设计Schema, 我们习惯称其为 Room 的 state.
state
export class Player extends Schema {
@type("number") x: number;
@type("number") y: number;
}
export class MyRoomState extends Schema {
@type({ map: Player }) players = new MapSchema<Player>();
}
需要同步给玩家的数据很简单, 只有 x, y 两个坐标点.
下面我们分别创建玩家房间和观众房间两个Room.
player room
onCreated
let s = new MyRoomState();
this.setState(s);
this.autoDispose = false;
创建房间时, 设置好 state, 本例中不允许房间自动销毁.
onJoined
let player = new Player()
player.x = Math.random()*700+100;
player.y = Math.random()*400+100;
this.state.players.set(client.id, player);
this.presence.publish("update", this.state);
玩家进入游戏房间时, 分配其一个随机位置, 存入 state, 然后把 state 广播到 "update" 频道.
如果不了解presence, 请参考 https://discuss.colyseus.io/topic/589/再谈-redis-presence 和 https://docs.colyseus.io/colyseus/server/presence/ .
onMessage
let player = this.state.players.get(client.id);
player.x = message.x;
player.y = message.y;
this.state.players.set(client.id, player);
this.presence.publish("update", this.state);
收到玩家移动数据时, 更新 state, 然后广播出去.
onLeave
this.state.players.delete(client.id);
this.presence.publish("update", this.state);
玩家离开游戏房间时, 删掉相应的 state 内容, 然后广播出去.
Audience Room
观众房间里并没有 state, 我们使用 "订阅 - 广播" 的方式把游戏房间的数据发送给观众客户端, 而且并不处理观众的任何消息.
data:any = {};
onCreate (options: any) {
this.autoDispose = false;
this.presence.subscribe("update", (d:any)=>{
console.log(d);
this.data = d;
this.broadcast("update", this.data);
});
}
onJoin (client: Client, options: any) {
console.log(client.sessionId, "joined!");
this.broadcast("update", this.data);
}
到这里观众围观游戏的简单功能就已经实现了. 当然在生产环境代码应该更加复杂和完善, 但思路大体一致.
Arena
export default Arena({
getId: () => "Your Colyseus App",
initializeGameServer: (gameServer) => {
/**
* Define your room handlers:
*/
gameServer.define('player_room', PlayerRoom);
gameServer.define('audience_room', AudienceRoom);
},
options:{presence: new RedisPresence()},
initializeExpress: (app) => {
/**
* Bind your custom express routes here:
*/
app.get("/", (req, res) => {
res.send("It's time to kick ass and chew bubblegum!");
});
/**
* Bind @colyseus/monitor
* It is recommended to protect this route with a password.
* Read more: https://docs.colyseus.io/tools/monitor/
*/
app.use("/colyseus", monitor());
},
beforeListen: () => {
/**
* Before before gameServer.listen() is called.
*/
matchMaker.create("player_room").then((res)=>{
matchMaker.create("audience_room").then((res2)=>{
console.log(res.room.roomId, res2.room.roomId);
})
});
}
});
脚本入口主文件中, 定义并建立两个房间, 然后关键一点是明确使用了 RedisPresence.
https://docs.colyseus.io/colyseus/server/presence/#redispresence-clientopts
Colyseus Arena
Colyseus Arena 是部署 Colyseus 服务器的最简单解决方案, 让您可以 Get To Fun Faster™
Arena Cloud 是一站式托管解决方案, 可让您专注于多人游戏开发. 它在服务器托管的基础上, 为开发人员提供企业级服务器管理, 基础设施建设, 开发运营和规模扩展等服务. 使用 Arena, 只需在直观的管理仪表板上点击几下, 即可完成 Colyseus 服务器的配置, 管理和更新.
点击这里注册 Arena Cloud 账户.
初级客户免费. 技术支持完善.
Arena Cloud 可以在全球多地使用, 并可应要求提供客户指定区域部署.
https://github.com/CocosGames/ColyseusAudience
客户端使用 Defold. 当然 Unity, Phaser, OpenCanvas, cocos creator 只要Colyseus支持的都行.
加入客户端 SDK
https://github.com/colyseus/colyseus-defold/archive/refs/tags/0.14.1.zip
https://github.com/defold/extension-websocket/archive/refs/tags/3.0.0.zip
Defold需要两个库, 一个是 Colyseus 的 Defold 客户端, 一个是 Defold 的原生 Websocket 扩展库.
新建一个GUI, 包括两个按钮, 方便用户选择作为玩家进入还是作为观众进入.
每个玩家用 Defold 的logo 表示, 头上显示其 Id 信息.
把它们整合到一起.
gui 脚本
function init(self)
msg.post(".", "acquire_input_focus")
end
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
local playerNode = gui.get_node("playerBtn")
local audienceNode = gui.get_node("audienceBtn")
if gui.pick_node(playerNode, action.x, action.y) then
msg.post("/players#example", "selected", {role = "player"})
msg.post("/gui", "disable")
elseif gui.pick_node(audienceNode, action.x, action.y) then
msg.post("/players#example", "selected", {role = "audience"})
msg.post("/gui", "disable")
end
end
end
客户端脚本
local Colyseus = require "colyseus.client"
local client
local xroom
local players = {}
function init(self)
client = Colyseus.new("ws://localhost:2567")
--while (not client) do end
end
function final(self)
-- Add finalization code here
-- Remove this function if not needed
msg.post(".", "release_input_focus")
end
function update(self, dt)
-- Add update code here
-- Remove this function if not needed
end
function on_message(self, message_id, message, sender)
print(message_id)
pprint(message)
if message_id == hash("selected") then
if message.role == "player" then
joinRoom("player")
else
joinRoom("audience")
end
end
end
function joinRoom(whichOne)
if not client then return end
client:join_or_create(whichOne.."_room", function(err, room)
if (err) then
print("JOIN ERROR: " .. err)
return
end
xroom = room;
room:on("statechange", function(state)
updatePlayers(state.players.items)
end
)
room:on_message("update", function(message)
if message and message.players then
updatePlayers(message.players)
end
end)
print("successfully joined '" .. room.name .. "'")
msg.post(".", "acquire_input_focus")
end)
end
function updatePlayers(data)
go.delete_all(players)
for k, v in pairs(data) do
if not players[key] then
local p = vmath.vector3()
p.x = v.x;
p.y = v.y
players[k] = factory.create("#factory", p)
local id = tostring(players[k])
id = string.sub(id,8,string.len(id)-1)
label.set_text(id.."#playerName", k)
end
end
end
function on_input(self, action_id, action)
if action_id==hash("touch") and client and xroom then
xroom:send("move", { x=action.x, y=action.y })
end
end
function on_reload(self)
-- Add reload-handling code here
-- Remove this function if not needed
end
好了, 启动 Redis server, Colyseus 和 Defold 看效果吧!
https://github.com/CocosGames/ColyseusAudience