iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

0

在上一篇我們已經成功將資料庫建立起來,不過我們還需要將原本的 Player Model 轉換成使用資料庫的資料。並且加入玩家連上產生角色、記錄座標這些功能,才能算是初步的時做出保存玩家狀態的機制。

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

這邊我們先對玩家的 xy 座標檢查,確保已經初始化過以免無法正確的放置玩家。然後再將玩家的初始座標廣播出去,如此一來就能夠將連上線的玩家放到我們最後一次紀錄的位置上。

在這邊我們需要用 !current_player.x.nil? 去確保「不是 nil 但可以是 0」的情況,在這種時候 Rails 的 #present? 擴充就非常方便,可以自己擴充或者引入 ActiveSupport 的 core_ext 來支援這類功能。

我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。


上一篇
Day. 30 - 實作練習 - 保存狀態(一)
下一篇
Day. 32 - 實作練習 - 登入遊戲
系列文
從讀遊戲原始碼學做連線遊戲33

尚未有邦友留言

立即登入留言