2Dゲームの基礎
ここまでは、サーバーとクライアントの間の通信は1バイトですんでいました。 しかし、ゲーム性を高めるためには、ネットワーク越しにゲームの状態を管理するためにより多くの情報をやり取りする必要があります。
複雑なゲームを作る上での第一歩として、2次元の画面上を複数のプレイヤーが動き回れるような構成を考えてみましょう。 プレイヤーのキャラクターは自動的に前に進み、プレイヤーは左右の方向転換だけが可能なようにします。 各プレイヤーに対応するクライアントは、プレイヤーのキー入力の結果をサーバーに送信し、サーバーは随時全てのプレイヤーの座標を更新します。 この場合、座標がサーバー側で一括管理されているため、複数のプレイヤーで座標がずれるなどのデータの不整合は起きません。
なお、オンラインゲームでは、汎用的なプロトコルは存在しないため、それぞれのゲームに応じてフォーマット等を独自に決めなければなりません。 それらを適切に設計することもオンラインゲーム開発では必要なスキルです。 ここでは処理を単純化するために、ゲームの状態の送受信にはJSONフォーマットを使用することにしましょう。 通信内容は以下のようになります。
通信方向 | 送信内容 | 例 |
---|---|---|
クライアント→サーバー | プレイヤーの操作(左右への方向転換) | {"direction": "right"} |
クライアント←サーバー | 現在のゲームの状態(各プレイヤーの座標と向きなど) | {"players": [{"x": 100, "y": 200, "angle": 1.2}]} |
サーバープログラム
import processing.net.*;
class Player {
int id;
float cx, cy, angle;
Player(int id) {
this.id = id;
this.cx = random(width);
this.cy = random(height);
this.angle = random(TWO_PI);
}
void forward(float step) {
float x = step * cos(this.angle) + this.cx;
float y = step * sin(this.angle) + this.cy;
this.cx = x;
if (this.cx > width) {
this.cx -= width;
}
if (this.cx < 0) {
this.cx += width;
}
this.cy = y;
if (this.cy > height) {
this.cy -= height;
}
if (this.cy < 0) {
this.cy += height;
}
}
}
Server server;
int idOffset = 0;
HashMap<Client, Player> players = new HashMap();
void setup() {
size(400, 400);
server = new Server(this, 5204);
}
void draw() {
float step = 1;
for (Player player : players.values()) {
player.forward(step);
}
JSONObject message = playerInfo();
server.write(message.toString());
server.write('\0');
}
void clientEvent(Client client) {
int dAngle = 20;
String payload = client.readStringUntil('\0');
if (payload != null) {
JSONObject message = parseJSONObject(payload.substring(0, payload.length() - 1));
String direction = message.getString("direction");
if (direction.equals("left")) {
players.get(client).angle += radians(-dAngle);
} else {
players.get(client).angle += radians(dAngle);
}
}
}
void serverEvent(Server server, Client client) {
Player player = new Player(idOffset++);
players.put(client, player);
}
void disconnectEvent(Client client) {
players.remove(client);
}
JSONObject playerInfo() {
JSONArray playerArray = new JSONArray();
for (Player player : players.values()) {
JSONObject item = new JSONObject();
item.setInt("id", player.id);
item.setFloat("x", player.cx);
item.setFloat("y", player.cy);
item.setFloat("angle", player.angle);
playerArray.append(item);
}
JSONObject message = new JSONObject();
message.setJSONArray("players", playerArray);
return message;
}
クライアントプログラム
import processing.net.*;
class Player {
int id;
float cx, cy, angle;
Player(int id, float cx, float cy, float angle) {
this.id = id;
this.cx = cx;
this.cy = cy;
this.angle = angle;
}
void draw() {
int size = 20;
noStroke();
fill(0);
float x1 = size * cos(this.angle) + cx;
float y1 = size * sin(this.angle) + cy;
float x2 = size * cos(this.angle + radians(150)) + cx;
float y2 = size * sin(this.angle + radians(150)) + cy;
float x3 = size * cos(this.angle - radians(150)) + cx;
float y3 = size * sin(this.angle - radians(150)) + cy;
triangle(x1, y1, x2, y2, x3, y3);
}
}
Client client;
ArrayList<Player> players = new ArrayList();
void setup() {
size(400, 400);
client = new Client(this, "127.0.0.1", 5204);
}
void draw() {
background(255);
synchronized(players) {
for (Player player : players) {
player.draw();
}
}
}
void keyPressed() {
if (key == CODED) {
JSONObject message = null;
if (keyCode == RIGHT) {
message = new JSONObject();
message.setString("direction", "right");
} else if (keyCode == LEFT) {
message = new JSONObject();
message.setString("direction", "left");
}
if (message != null) {
client.write(message.toString());
client.write('\0');
}
}
}
void clientEvent(Client client) {
String payload = client.readStringUntil('\0');
if (payload != null) {
JSONObject message = parseJSONObject(payload.substring(0, payload.length() - 1));
JSONArray playerArray = message.getJSONArray("players");
synchronized(players) {
players.clear();
for (int i = 0; i < playerArray.size(); ++i) {
JSONObject item = playerArray.getJSONObject(i);
int id = item.getInt("id");
float x = item.getFloat("x");
float y = item.getFloat("y");
float angle = item.getFloat("angle");
Player player = new Player(id, x, y, angle);
players.add(player);
}
}
}
}