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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM