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;
                }
        }
    }

源码下载地址

https://gitee.com/opendavid/colyseus_demo

posted @ 2021-11-26 10:37  柳轩涤俗  阅读(883)  评论(0编辑  收藏  举报