iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 29
0
Software Development

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

Day 29 - 實作練習 - 玩家列表

經過重構之後我們終於可以回到「後加入的玩家看不到之前玩家」的問題,這個問題源自於我們在玩家加入時並沒有發送任何資訊給新加入的玩家「線上有多少人」

處理的方式我們可以選擇「加入後馬上發送列表」或者「加入後另外要求」列表兩種方式,不過不管是哪種形式都能夠達到我們的需要。

玩家列表指令

考量到每個指令應該都只負責一件事情,因此我們將「玩家列表」獨立成一個 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 修改增加 xy 兩個屬性。

// 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));
  };
  
  // ...
}());

不過測試之後卻發現「自己的角色不見了!」的情況,這是因為在我們的實作中是不會排除玩家當下操作的角色。而每個角色預設的 xy 座標是不明的,反而讓自己的角色因為操作更新位置而消失在某處。

因此我們還需要稍微調整伺服器的 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

如此一來就能正常顯示其他玩家,也能夠正確操作和看到其他玩家的反應。

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


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

尚未有邦友留言

立即登入留言