iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 30
0
Software Development

從讀遊戲原始碼學做連線遊戲系列 第 30

Day. 30 - 實作練習 - 保存狀態(一)

現在我們已經可以完整的同步不同玩家的資訊,不過依舊無法將玩家的狀態保存在伺服器。因此我們需要改寫伺服器來保存狀態,受限於內容長度以及 RPG Maker MV 的限制,我們會直接使用輸入使用者 ID 的方式來做使用者認證,在實際的應用中建議考慮像是 Unlight 選用的 SRP 這類機制來加強安全性。

RPG Maker 因為沒有實作輸入框(要在 Canvas 上實作也很困難)因此不太能在很短的篇幅內快速搭建一個跟遊戲畫面相融合的輸入介面,作為替代我們使用簡單的 prompt 透過瀏覽器內建的方式快速跳出選項,就會相對容易許多。

設定 Sequel

管理資料庫的變動是一件複雜的事情,我們雖然可以透過 SQL 維護。但是沒有專業 DBA(資料庫管理者)的情況下並不容易,以及開發專案時還是需要能建構開發環境,因此採用 Migration 的方式對中小型的專案會是一個不錯的選擇。

首先,我們先對 config/application.rb 擴充,加上資料庫的設定。

# config/application.rb

# frozen_string_literal: true
# ...

class SimpleRPG
  include Singleton

  class << self
    # ...

    def database_url
      ENV['DATABASE_URL'] ||
        "sqlite://#{root}/db/development.sqlite"
    end

    def database
      @database ||=
        Sequel.connect(database_url)
    end
  end

  # ...
  
  def start(server, options = {})
    SimpleRPG.database
    Thread.new { @fsevent.run }
    # TODO: Select Server
    Rack::Handler.default.run(WebSocketServer, options)
  end
end

Sequel 在設定上算是很容易的,只需要在要使用資料庫的時候先呼叫一次 Sequel 的 #connect 方法,就可以連上資料庫。這次因為是開發與示範使用,所以就直接讓他在專案目錄下的 db/ 產生一個 development.sqlite 檔案。

實際專案使用的時候可以參考 Rails 製作一個 SimpleRPG.env 的設定值,透過判斷目前的環境變數來決定產生的檔案,或者讀取專用的設定檔來設定要連上的資料庫。

Migrate 機制

在 Unlight 使用的舊版 Sequel 中是在 Model 直接定義 Schema 的,也因此會讓 Model 增加很多程式碼。現在比較新版本的 Sequel 5 已經出現「棄用警告」通知我們應該改成使用單獨的 Migration 檔案來處理,不過預設不會載入這個功能而且我們應該只在需要更新時運行這些更新指令。

因此先新增一個 bin/migrate 執行檔,用 Ruby 來啟動這個資料庫更新的腳本。

# bin/migrate

#!/usr/bin/env ruby

# frozen_string_literal: true

$LOAD_PATH.unshift("#{File.absolute_path('../..', __FILE__)}")

require 'logger'

require 'config/application'

Sequel.extension :migration
Sequel.connect(SimpleRPG.database_url, logger: Logger.new($stderr)) do |db|
  Sequel::Migrator.run(
    db,
    "#{SimpleRPG.root}/db/migrations",
    use_transactions: true
  )
end

實作上跟啟動伺服器的方式差不多,這邊我們需要將 Logger 設定為輸出到 $stderr 上這樣才能正確地看到錯誤訊息,如果是 $stdout 的話還是會無法看到(可能是 Sequel 的設計關係,有點微妙)為了確保更新失敗後能正確的 Rollback 因此將 use_transactions 模式啟用,這部分可以自行評估設計的系統是否需要這樣的機制。

完成後一樣需要對 bin/migrate 這個檔案做 chmod +x bin/migrate 的設定,確保能夠執行。

Migration 撰寫

在前面的步驟我們設定了 db/migrations 作為讀取 Migration 的目錄,接下來要進行撰寫的設定。在 Sequel 中檔案有兩種命名方式,一種是用序號命名另一種則是用日期跟時間。以經驗上來看用日期命名會比用序號好的多,因此我們編輯一個 201910141700_create_players.rb 的檔案來產生 players 這張表儲存玩家資訊。

# db/migrations/201910141700_create_players.rb

# frozen_string_literal: true

Sequel.migration do
  change do
    create_table :players do
      primary_key :id
      String :name, null: false, index: true, unique: true
      Integer :x, default: 0
      Integer :y, default: 0
    end
  end
end

關於如何定義 Scheme 的部分需要參考 Sequel 的 文件,因為跟大多數 Ruby 開發者熟悉的 ActiveRecord 有所差異,因此建議先參考文件了解要如何設定後再開始撰寫會比較好。

當我們產生好對應的檔案後,就可以執行 bin/migrate 來確認是否能正常執行。

[elct9620] server % ./bin/migrate
I, [2019-10-13T23:47:10.887144 #20938]  INFO -- : (0.001378s) PRAGMA foreign_keys = 1
I, [2019-10-13T23:47:10.887241 #20938]  INFO -- : (0.000018s) PRAGMA case_sensitive_like = 1
I, [2019-10-13T23:47:10.887598 #20938]  INFO -- : (0.000077s) SELECT sqlite_version()
E, [2019-10-13T23:47:10.887768 #20938] ERROR -- : SQLite3::SQLException: no such table: schema_migrations: SELECT NULL AS 'nil' FROM `schema_migrations` LIMIT 1
I, [2019-10-13T23:47:10.891970 #20938]  INFO -- : (0.001127s) CREATE TABLE `schema_migrations` (`filename` varchar(255) NOT NULL PRIMARY KEY)
E, [2019-10-13T23:47:10.892312 #20938] ERROR -- : SQLite3::SQLException: no such table: schema_info: SELECT NULL AS 'nil' FROM `schema_info` LIMIT 1
I, [2019-10-13T23:47:10.892590 #20938]  INFO -- : (0.000077s) SELECT `filename` FROM `schema_migrations` ORDER BY `filename`
I, [2019-10-13T23:47:10.892903 #20938]  INFO -- : Begin applying migration 201910141700_create_players.rb, direction: up
I, [2019-10-13T23:47:10.892986 #20938]  INFO -- : (0.000019s) BEGIN
I, [2019-10-13T23:47:10.893436 #20938]  INFO -- : (0.000270s) CREATE TABLE `players` (`id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, `name` varchar(255) NOT NULL UNIQUE, `x` integer DEFAULT (0), `y` integer DEFAULT (0))
I, [2019-10-13T23:47:10.893597 #20938]  INFO -- : (0.000071s) CREATE INDEX `players_name_index` ON `players` (`name`)
I, [2019-10-13T23:47:10.893769 #20938]  INFO -- : (0.000081s) INSERT INTO `schema_migrations` (`filename`) VALUES ('201910141700_create_players.rb')
I, [2019-10-13T23:47:10.894314 #20938]  INFO -- : (0.000485s) COMMIT
I, [2019-10-13T23:47:10.894376 #20938]  INFO -- : Finished applying migration 201910141700_create_players.rb, direction: up, took 0.001465 seconds

如果看到類似上述的訊息,基本上就算是執行成功。並且要確定再次執行 bin/migrate 時不會看到相同的結果,因為已經執行過同樣的 Migration 了!

建議在 db/ 目錄下放置 .gitkeep 檔案才不會因為找不到目錄而無法產生 SQLite 檔案,也可以使用 sqlite3 指令檢查產生的資料庫檔案是否有正確的生成我們所需要的 players 表。

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


上一篇
Day 29 - 實作練習 - 玩家列表
下一篇
Day 31 - 實作練習 - 保存狀態(二)
系列文
從讀遊戲原始碼學做連線遊戲33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言