GitHub | Documentation | Discord (chat) | Become a Patron

Stencyl (HaXe) Extension



  • This week I've started using Colyseus. I'm going to try to create a HaXe Extension for Stencyl so that I can publish multiplayer games with Colyseus.
    Previously I've created a couple of multiplayer extensions for Stencyl but failed to get anything near realtime. Maybe Colyseus can help?!?
    From first glance the Colyseus stuff looks great. I love the fact that I can run the server on my own infrastructure.

    It will be a long road ahead and I've decided to start this Showcase thread to let you all follow my progress. Maybe you can help me along the way?!?

    First I started by porting the example nyancat to a Stencyl game (Using a bitmap for the cat)

    Flash publication failed! I've tried to put the flash (swf) on the same server and experimented with crossdomain.xml. But apparently websockets do not care about that.

    Working clients:

    • HTML5 (Tested only Chrome)
    • Windows native
    • Mac OSX native
    • iOS (iPad mini)
    • iOS Simulator (iPhone 5s)
    • Android (Samsung S3 Note)
    • Linux


  • Since I like developers journals myself, I'm writing one for the Stencyl Colyseus Extension journey.

    The main idea of the Stencyl Extension is a general purpose Server with accommodating Stencyl blocks.
    Not sure if that is the right approach, but that is the path I take now.

    Goals:

    • Application ID system
    • Lobby system
    • Turn Based system
    • System that one client acts as a server for the other clients and itself
    • Chat system

    First, I want something persistent. Currently the thought process is that there is a LobbyRoom.
    That can be used for MatchMaking but I hope to use it for public storage of the application IDs.

    Persistence

    The journey leads to investigations on how to store application ids.
    This can be using a simple text file since I'm aiming for a single server (at this time) and I
    hopefully don't need a (shared) database server.

    Test LocalPresence : adjusted rooms/01-chat-room.ts

    import { LocalPresence } from "colyseus";
    

    after maxClients = 4;

    private presence:LocalPresence=new LocalPresence();
    

    Added to onMessage:

    var msg = this.presence.hget("MdotTest","Field");
    console.log("Last Message: ", msg);
    this.presence.hset("MdotTest", "Field", data.message);
    

    This works really well. When leaving room and joining again it still knew the last message.
    However, when I kill the server and start it up the data is lost.

    I couldn't find any writing to files in https://github.com/colyseus/colyseus/tree/master/src/presence

    In RedisPresence there is use of promisfy but no where is something with files (at least so far I can see)

    How do we do server reboot persistence?!

    This is what I hacked together and I'm sure there is something else that I should be doing so please point m.e. to the better thing!!!

    import { readFileSync,writeFileSync } from "fs";
    

    Added to onJoin:

    var jsonfile=JSON.parse(readFileSync('temp.txt','utf8'));
    var item=jsonfile.hash;
    //console.log('item', item.MdotTest.Field);
    this.presence.hset("MdotTest", "Field", item);
    

    Added to onDispose:

    writeFileSync("temp.txt", JSON.stringify(this.presence));
    

    Now at least the data is persistent even when there is a server boot/crash/shutdown (not when it crashes when writing ...)


  • administrators

    hey @mdotedot, not exactly sure what you're planning to build, but to persist the data across server reboots you can use the RedisPresence alternative. The LocalPresence really doesn't persist any data. Cheers!



  • Thanks Endel! It appears that RedisPresence needs a Redis Server!
    When using RedisPresence and you don't have a running Redis Server you get this:

    Could not connect to Redis at 127.0.0.1:6379: Connection refused
    

    For running a server this quick start is available : https://redis.io/topics/quickstart

    I hate to run different things on the server since I want to have deployment by the Extension users as easy as it can be.

    Maybe I will revisit this approach when I need persistence.
    I've changed the approach to ApplicationIDs and 'just' generate some IDs for the Extension Users.

    In the chat I've been told that one of my room-mechanism approach will fail. We will see where this thing is going to crash ...
    I'm not promising that I can create a general purpose server for all kinds of clients.
    If an approach don't work, extension users still have the ability to change the server themselves.
    Although they probably lack the skills, they can hire you guys to make the server for them :D
    There will be overhead on the proxy mechanism, but we will see what can be done.
    Rather than saying that running logic on client is impossible I still want to try it.
    Using the Colyseus server as a proxy to the active client.

    For now I've created a turn based mechanism on the server that I need to write some clients for.

    The example : 02-state-handler.ts appears to be working correctly with below additions, but I guess that I need to investigate
    different types of turn based games and their server-side logic.

    These are the steps taken to rotate player activity: (Modify 02-state-handler.ts)

    After : something = "This ...

    
            nextPlayer(){
                    var doNext=false;
                    for(var m in this.players){
                            if(doNext){
                                    this.players[m].ActivePlayer=m;
                                    doNext=false;
                            }else{
                                    if(this.players[m].ActivePlayer != ""){
                                            this.players[m].ActivePlayer="";
                                            doNext=true;
                                    }
                            }
                    } // check Next available player
                    if(doNext){ // if we need to shift but there are no players after : first player
                            for(var m in this.players){
                                    if(doNext){
                                            this.players[m].ActivePlayer=m;
                                            doNext=false;
                                    }
                            }// go through all players
                    } // check first available player
            }// ActivePlayer shift
    // Changed createPlayer
        createPlayer (id: string, room:Room) {
            this.players[ id ] = new Player();
            if(room.clients.length == 1){ // first player in room
                    this.players[id].ActivePlayer=id;
            }
        } // createPlayer
    
    

    In removePlayer we need to check if the leaving player is the active player and rotate when necessary:

        if(this.players[id].ActivePlayer!="")this.nextPlayer(); 
    

    movePlayer includes a check on activity: After movePlayer(id: string, movement: any){

       
            if(this.players[id].ActivePlayer != id)	return; // ignore move when not active
    		
    

    And before the end of the function:

    	this.nextPlayer(); // after move 
    

    Adjusted class Player with the ActivePlayer :

    
    export class Player {
        x = Math.floor(Math.random() * 400);
        y = Math.floor(Math.random() * 400);
            ActivePlayer = "";
    }
    
    

    Adjusted onJoin :

       this.state.createPlayer(client.sessionId, this);
    

    When running this example the players can only move when it is their turn.
    It is a basic example with no validation on move or if time expired etc..



  • This week I've tried to make a few Stencyl Extension blocks:

    • get Player (ID / Name)
    • get Room (ID / Name)
    • assign Name [..] to [room/player] with id []
    • List of Rooms
    • List of Players

    It took me a while to find out that client.id != player.id. It appears that player is instantiated with room.SessionId.

    Then it took me a lot longer to get a list of rooms (of the same type).
    getAvailableRooms seems to show a list of rooms where you can join to, not the ones that are full or something like that.
    I (also) want rooms with large number of possible clients, so the client is now always joining the same room where I want the client to be able to create a new room.

    Apparently we need to use the MatchMaker to get a list of rooms. But I fail to get it to use.
    It is a Server component, but how do I access this from the client when the client isn't in a room?

    I've also looked at the example on MatchMaker but it registered the rooms differently then I was seeing in the 02-state-change example
    Source: https://github.com/colyseus/colyseus/blob/master/src/MatchMaker.ts

    At last I've hacked something together that appears to be working for now.

    globals.ts:

    //
    // Global data for the server
    //
    export abstract class Globals{
            private static theRooms =  [];
    
            public static set(index:string,value:string){
                    this.theRooms[index]=value;
            }
    // get, remove as well when room gets disposed
    }
    

    This static class can hold 'server-wide' globals as long as you import them in your room-code, like:

    import { Globals } from "../globals"; // global data from the server
    
    //  onInit :
      Globals.set(""+this.roomId, "state_handler");
    
    

    This method allows me to get the list of rooms from other client sessions and even for other rooms when I want...

    I'm sure that there is another way to get this done using the MatchMaker but I couldn't find example of this.