浏览器里的多人 ARPG
搭好的世界、会记住的数据库、会应答的服务器、会推送的 websocket——还有同一个标签页里的两个玩家。
42 秒 · 含中文字幕 · 8.5 Mo
你会学会的 7 件事
第 12 课:浏览器里的多人 ARPG
Regarde la leçon (42 s) : un monde bâti, une base qui se souvient, un serveur qui répond, un websocket qui pousse — quatre étapes, et ton ARPG tourne dans un onglet.
一格一格搭世界
Écris `cellule(x, y, sol)` : une case du monde — un Hash avec `"x"`, `"y"` et `"sol"` (ex: `"herbe"`). Le monde 8-bit commence par UNE case.
会记住的数据库
Écris `charge(rows, id)` : lire la base — `rows` est un tableau de lignes Hash (avec `"id"`), renvoie LA ligne dont l'id vaut `id`, sinon `nil`. Sans ce lire, la partie recommence à zéro.
服务器应答路由
Écris `route(chemin)` : le petit routeur — `"/joueurs"` → `:joueurs` · `"/monde"` → `:monde` · `"/parties"` → `:parties` · tout autre chemin → `nil`. Le serveur ne fait QUE ça : un chemin, un symbole.
websocket 推送动作
Écris `evenement(msg)` : le serveur reçoit `msg` (un Hash avec `"type"`) — `"tire"` → `:inflige` · `"parle"` → `:diffuse` · tout autre type → `:ignore`. Le websocket ne fait que ça : type → action.
送到【对的】对象手里
Écris `remets?(element, acteur)` : un élément venu de la base porte `"pour"` (un id) et `"role"` (`"pnj"` ou `"joueur"`) ; l'acteur porte `"id"` et `"role"`. true SEULEMENT si l'id correspond ET le rôle correspond — au bon joueur, au bon PNJ.
第 12 课:OK
Sans aide : `transfert(rows, id, acteur)` — 1) cherche la ligne `id` dans la base `rows` (absente → `false`) · 2) livre seulement si `"pour"` == `"id"` de l'acteur ET `"role"` == `"role"` de l'acteur. Le reste, c'est `false`.
📄 备忘单
打印出来贴显示器上,就当是你自己的主意📄 备忘单 — 浏览器里的多人 ARPG(世界 · 数据库 · 流 · websocket)
浏览器里的 8-bit 多人 ARPG 就四步:搭世界、记进数据库、在路由上应答、用 websocket 推送——然后把每个元素送到【对的】对象手里。
旅程的四步
格子 → 数据库 → 路由 → websocket (然后:投递给对的对象)
cellule() charge() route() evenement()
1. 世界:格子,不是引擎
def cellule(x, y, sol)
{ "x" => x, "y" => y, "sol" => sol }
end
cellule(2, 5, "herbe") # => {"x"=>2, "y"=>5, "sol"=>"herbe"}
整个世界 = 这些 hash 的数组(第 6 + 8 课,复习)。贴图在后面——数据先行。
2. 记忆:会记住的数据库
def charge(rows, id)
rows.find { |r| r["id"] == id } # 那一行……或者 nil
end
两次会话之间,只有数据库能活过标签页。加载、玩、存——三次调用,世界就有了记忆。
3. 流:一个路径,一个符号
case chemin
when "/joueurs" then :joueurs
when "/monde" then :monde
when "/parties" then :parties
else nil # 不认识:服务器说不
end
一次请求不过如此:客户端喊一个路径,服务器回一个符号
(或者 nil——一个诚实的 404)。
4. websocket:类型 → 动作,推送出去
case msg["type"]
when "tire" then :inflige # 服务器执行……
when "parle" then :diffuse # ……并且【广播】给其他人
else :ignore # 不是玩法:扔掉
end
和路由的区别:服务器先开口。 连接一直开着,动作实时推给所有人。
5. 投递:给对的对象,绝不「差不多」
def remets?(element, acteur)
element["pour"] == acteur["id"] && element["role"] == acteur["role"]
end
来自数据库的元素自带目的地(pour)和受众(role:"pnj" 或 "joueur")。
两个条件——只看 id 会送错 NPC,只看角色会送给任何人。这就是多人分流的 &&。
完整的一趟(服务器每条消息在做的)
def transfert(rows, id, acteur)
el = rows.find { |r| r["id"] == id } # 1. 数据库
return false unless el # 2. 没找到 → 什么都不发
el["pour"] == acteur["id"] && el["role"] == acteur["role"] # 3. 对的收件人
end
数据库 → 查找 → 双重条件 → 送达。五行 Ruby 顶一个 MMO 后端。 (真的还要加持久化、隐藏 id、延迟——但形状就是这样。)
以后别再说
「做多人得有游戏引擎」→ 一个数据库、一条路由、一个 socket「服务器决定一切」→ 它决定【什么为真】:谁有什么,数据库说了算「websocket = 实时魔法」→ 对消息的case/when,在循环里推送
SlopCourse — 从 slop 到干净的代码。