經過重構之後我們終於可以回到「後加入的玩家看不到之前玩家」的問題,這個問題源自於我們在玩家加入時並沒有發送任何資訊給新加入的玩家「線上有多少人」
處理的方式我們可以選擇「加入後馬上發送列表」或者「加入後另外要求」列表兩種方式,不過不管是哪種形式都能夠達到我們的需要。
考量到每個指令應該都只負責一件事情,因此我們將「玩家列表」獨立成一個 player_list
指令讓我們在需要的時候可以單獨要求過在線上的玩家列表。
修改伺服器的 app/controllers/map_controller.rb
來增加指令
# app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
# ...
def player_list
items = Connection.players.map(&:id)
response(:player_list, items)
end
# ...
end
然後再客戶端的部分,在 js/plugins/SimpleRPG_Map.js
增加發送 player_list
指令,讓我們在加入地圖後要求在這個地圖上的玩家。
// js/plugins/SimpleRPG_Map.js
(function() {
// ...
// On Map Loaded
var _Scene_Map_onMapLoaded = Scene_Map.prototype.onMapLoaded;
Scene_Map.prototype.onMapLoaded = function() {
_Scene_Map_onMapLoaded.call(this);
SimpleRPG.Servers.Map.setCurrentMap(this);
SimpleRPG.Servers.Map.send('join', []);
SimpleRPG.Servers.Map.send('player_list', []);
// Hide Default Player
$gamePlayer.setOpacity(0);
}
// ...
}());
我們在原本的 join
指令後面加入了 player_list
指令要求完整的玩家列表,接下來再修改 js/plugins/SimpleRPG_Controllers_Map.js
來針對玩家列表處理。
// js/plugins/SimpleRPG_Controllers_Map.js
(function() {
// ...
MapController.prototype.player_list = function(players) {
players.forEach(function(id) {
var player = this.map.addPlayer(id);
player.setImage('Actor2', 1);
player.locate(8, 6);
}.bind(this));
};
}());
如此一來我們再次開啟兩個視窗測試,就會發現能夠正確的呈現兩個不同的玩家。
當其他玩家正常出現後,卻發現所有玩家都是從起點出現的。如果是新加入的玩家可能還沒有什麼問題,但是已經存在的玩家走動過的位置無法被區分出來。
因為我們還沒有資料庫,因此先將資料記錄在記憶體中。將 Player Model 修改增加 x
和 y
兩個屬性。
// app/models/player.rb
# frozen_string_literal: true
class Player
attr_accessor :id
attr_accessor :x, :y
# ...
end
接下來在 Controller 中針對移動指令增加更新座標的機制。
// app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
# ...
def move(x, y)
current_player.x = x
current_player.y = y
broadcast(:move, current_player.id, x, y)
end
end
然後再繼續修改 player_list
指令,從原本回傳玩家識別編號修改為增加回傳座標資訊。
// app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
# ...
def player_list
items = Connection.players.map do |player|
[player.id, player.x, player.y]
end
response(:player_list, items)
end
# ...
end
因為指令回傳的資訊改變了,因此我們也要根據改變後的指令調整 Client 端的 Controller 來處理這些新的資訊。
// js/plugins/SimpleRPG_Controllers_Map.js
(function() {
// ...
MapController.prototype.player_list = function(players) {
players.forEach(function(item) {
var id = item[0];
var x = item[1];
var y = item[2];
var player = this.map.addPlayer(id);
player.setImage('Actor2', 1);
player.locate(x, y);
}.bind(this));
};
// ...
}());
不過測試之後卻發現「自己的角色不見了!」的情況,這是因為在我們的實作中是不會排除玩家當下操作的角色。而每個角色預設的 x
和 y
座標是不明的,反而讓自己的角色因為操作更新位置而消失在某處。
因此我們還需要稍微調整伺服器的 join
指令記錄玩家初始的座標位置。
// app/controllers/map_controller.rb
# frozen_string_literal: true
class MapController < BaseController
def join
current_player.x = 8
current_player.y = 6
broadcast(:join, current_player.id)
end
# ...
end
如此一來就能正常顯示其他玩家,也能夠正確操作和看到其他玩家的反應。
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。