COLYSEUS服务器框架实践Demo
前言
游戏服务器框架colyseus,使用起来十分简单,只需要一丢丢的代码就可以实现一个状态同步的服务器
安装&项目设置
- 使用npm初始化项目
npm i -g typescript npm init -y tsc --init npm i colyseus npm i express @types/express @types/node @types/redis
colyseus@0.14.23 版本
- 入口文件 在主目录下新建一个index.ts文件
/** * 官方文档 * https://docs.colyseus.io/zh_cn/colyseus/server/api/ */ import { GameRoom } from './room/GameRoom'; import { Server } from 'colyseus'; import http from 'http'; import express from 'express'; const port = Number(process.env.port) || 3000; const app = express(); app.use(express.json()); // init game server const gameServer = new Server({ server: http.createServer(app) }); // Define 'game' room gameServer.define('game', GameRoom); // listen server port gameServer.listen(port); console.log('server is on');
- 新建一个文件夹room 新建一个GameRoom.ts
import { Room, Client } from 'colyseus'; import { State } from '../entity/State'; export class GameRoom extends Room<State> { // max clients maxClients: number = 3; // Colyseus will invoke when creating the room instance onCreate(options: any) { // initialize empty room state this.setState(new State()); // set patch rate this.setPatchRate(50); // Called every time this room receives a "move" message this.onMessage("move", (client, data) => { this.state.movePlayer(client, data.x, data.y); // test log let player = this.state.players.get(client.sessionId); if (player != undefined) { console.log(client.sessionId + " at, x: " + player.x, "y: " + player.y); } }); // Triggers when any other type of message is sent, // excluding "move", which has its own specific handler defined above. this.onMessage("*", (client, type, message) => { console.log(client.sessionId, "sent", type, message); }); } // Called every time a client joins onJoin(client: Client, options?: any, auth?: any) { this.state.addPlayer(client); } // Called every time a client leaves onLeave(client: Client, consented?: boolean) { this.state.removePlayer(client); } }
-
新建一个文件夹entity 新建文件State.ts和Player.ts
-
State.ts
import { Client } from 'colyseus'; import { Schema, MapSchema, type } from '@colyseus/schema' import { Player } from './Player'; export class State extends Schema { // MapSchema是colyseus的对象实体模板 @type({ map: Player }) players = new MapSchema<Player>(); /** * 添加新用户的方法 * * @param {Client} client * @memberof PlayerState */ addPlayer(client: Client) { let player = new Player(0, 0); this.players.set(client.sessionId, player); } /** * 删除一个用户的方法 * * @param {Client} client * @memberof PlayerState */ removePlayer(client: Client) { this.players.delete(client.sessionId); } /** * 移动用户的方法 * * @param {Client} client * @param {number} [x=0] * @param {number} [y=0] * @memberof PlayerState */ movePlayer(client: Client, x: number = 0, y: number = 0) { let player = this.players.get(client.sessionId); if (player != undefined) { (<Player>player).x += x; (<Player>player).y += y; if (x > 0) { (<Player>player).dir = true; } else { (<Player>player).dir = false; } } else { // 当前用户不存在 console.log('client sessionId not exist!'); } } }
Player.ts
import { Schema,type } from '@colyseus/schema' import { randomChineseName } from '../Utils' export class Player extends Schema { @type("string") name: string; // 名称 @type("number") x: number; // x轴的位置 @type("number") y: number; // y轴的位置 @type("boolean") dir: boolean; // 玩家的方向(左 false 右 true) 简单定义 constructor(x: number, y: number, name?: string) { super(); this.x = x; this.y = y; this.name = name || randomChineseName(); this.dir = true; } }
- 根目录新建一个Utils.ts的文件
- 一些基础工具方法写在这里
- 现在又一个随机返回一个中文名称的方法
const NAMES: Array<string> = [ '断笔画墨', '默然相爱', '旅人不扰', '多余温情', '云中谁忆', '残雪冰心', '末世岛屿', '桑榆非晚', '扉匣与桔', '木槿暖夏', '空城旧梦', ]; /** * 返回随机的中文名 * * @export * @returns {string} */ export function randomChineseName(): string { return NAMES[~~(NAMES.length * Math.random())]; }
不正确的图像架构简单分析
- 一个游戏服务器下面可以开N个房间Room
- Room中存在一个state的对象,发生变化时候同步到Room下的客户端
- 使得客户端的状态保持一致
- 这个就是colyseus实现的状态同步服务器
说明
1、使用npm初始化项目 npm i -g typescript npm init -y tsc --init npm i colyseus npm i express @types/express @types/node @types/redis 2、tsconfig.json 配置文件修改 添加"outDir": "./dist" 取消注释"experimentalDecorators": true 3、package.json 配置文件修改 在scripts下添加start的配置 "start": "tsc && cd dist && port=3001 node index.js" 4、启动服务器 npm start
启动服务器
npm start
简单的客户端效果
colyseus框架文档:https://docs.colyseus.io/zh_cn/colyseus/
本文参考出处:https://allknowboy.com/posts/a8be8288/
客户端
colyseus.js@0.14.13
ccc客户端文档:https://docs.colyseus.io/zh_cn/colyseus/getting-started/cocos-creator/
colyseus.js下载地址:https://github.com/colyseus/colyseus.js/releases
客户端代码
this.client = new Colyseus.Client('ws://10.250.8.127:3000'); this.connect();
connect 方法:
async connect() { let _self = this; try { this.room = await this.client.joinOrCreate("game"); // console.log("joined successfully!", this.room); // console.log("user's sessionId:", this.room.sessionId); this.room.state.players.onAdd = function (player, sessionId) { let playerNode: cc.Node = cc.instantiate(_self.p_prefab); playerNode.getChildByName('name').getComponent(cc.Label).string = player.name; _self.m_gameMap.addChild(playerNode); _self.players[sessionId] = playerNode; if (sessionId === _self.room.sessionId) { playerNode.getChildByName('name').color = cc.Color.RED; } player.onChange = function (changes) { _self.players[sessionId].position = cc.v2(player.x, player.y); } // console.log('add', player) } this.room.state.players.onRemove = function (player, sessionId) { if (this.players[sessionId]) { this.players[sessionId].removeFromParent(true); delete this.players[sessionId]; } } this.room.onStateChange((state) => { console.log("onStateChange: ", state); //console.log("players: ", state.players); }); this.room.onLeave((code) => { console.log("onLeave:", code); }); this.room.onMessage("*", (type, message) => { console.log("received message:", type, "=>", message); }); } catch (e) { console.error(e); } }
onKeyDown方法:
onKeyDown(event: cc.Event.EventKeyboard) { switch (event.keyCode) { case cc.macro.KEY.up: case cc.macro.KEY.w: { this.room.send("move", { y: 5 }); break; } case cc.macro.KEY.down: case cc.macro.KEY.s: { this.room.send("move", { y: -5 }); break; } case cc.macro.KEY.left: case cc.macro.KEY.a: { this.room.send("move", { x: -5 }); break; } case cc.macro.KEY.right: case cc.macro.KEY.d: { this.room.send("move", { x: 5 }); break; } } }