在上一篇我們已經成功將資料庫建立起來,不過我們還需要將原本的 Player Model 轉換成使用資料庫的資料。並且加入玩家連上產生角色、記錄座標這些功能,才能算是初步的時做出保存玩家狀態的機制。
Sequel 除了可以直接對資料庫使用 SQL 操作之外,也提供了類似 ActiveRecord 的 ORM 機制,我們可以使用這個功能快速的將原本的 Player Model 改良成使用資料庫的版本。
# app/models/player.rb
# frozen_string_literal: true
class Player < Sequel::Model
end
原本我們需要透過 attr_accessor
以及 instance_variable_set
來輔助我們,在 Sequel 中跟 ActiveRecord 一樣提供了事前的封裝,因此我們可以再不做任何額外處理的情況下像這樣使用。
player = Player[id]
player.x = 1
player.y = 1
player.save
因為這次的範例中我們並沒有設計註冊的機制,因此採用玩家輸入暱稱後直接生成或者查詢已存在的玩家作為當下的玩家。因此我們需要先增加一個「登入」的步驟到我們伺服器上。我們在 BaseController 上增加一個通用指令 negotiate
來處理這件事情。
# app/controllers/base_controller.rb
# frozen_string_literal: true
class BaseController
# ...
def negotiate(name)
player =
Player.find_or_create(name: name)
@conn.player = player
response(:negotiate, true)
end
# ...
end
如此一來我們只需要在開始連線前先呼叫一次 negotiate
這個指令,伺服器就知道現在是哪個玩家正在操作。不過這個設計也有可能還可以再繼續改進,像是在玩家建立連線後就馬上進行驗證,如果失敗的話就將玩家從伺服器切斷連線來節省遊戲的連線數。
因為原本我們會在連線產生時設定玩家,這樣就會造成直接設定 Primary Key 因此要將 app/network/connection.rb
上的玩家設定清除。
# app/controllers/connection.rb
class Connection
# ...
attr_accessor :player
# ...
def open(_event)
Connection.pool.add(self)
end
end
要注意的是
attr_reader :player
要改為attr_accessor :player
讓我們可以在 Controller 上面設定玩家。
既然已經知道目前連線的玩家,我們就可以到 MapController 上面繼續處理,針對玩家移動的指令調整加入儲存到資料庫的設計。
# app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
# ...
def move(x, y)
current_player.move_to(x, y)
broadcast(:move, current_player.id, x, y)
end
end
這邊我們選擇用 #move_to
來實現,假設每次都手動的寫 current_player.x = x
這樣的程式碼,除了會變得難以閱讀之外也很難重複利用。因此我們再回到 Model 上面增加對應的處理行為。
# app/models/player.rb
# frozen_string_literal: true
class Player < Sequel::Model
def move_to(x, y)
self.x = x
self.y = y
save
end
end
柵站之下似乎都完成了,不過我們還需要對 join
指令處理,之前是寫死遊戲地圖的「預設位置」但是因為我們已經能持久保存玩家的座標資訊,因此需要改成從資料庫抓取玩家的位置。
# app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
def join
position_ready = !current_player.x.nil? && !current_player.y.nil?
current_player.move_to(8, 6) unless position_ready
broadcast(:join, current_player.id, current_player.x, current_player.y)
end
# ...
end
這邊我們先對玩家的 x
和 y
座標檢查,確保已經初始化過以免無法正確的放置玩家。然後再將玩家的初始座標廣播出去,如此一來就能夠將連上線的玩家放到我們最後一次紀錄的位置上。
在這邊我們需要用
!current_player.x.nil?
去確保「不是 nil 但可以是 0」的情況,在這種時候 Rails 的#present?
擴充就非常方便,可以自己擴充或者引入 ActiveSupport 的core_ext
來支援這類功能。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。