World`s first Active-Active Geo-Distributed* Multiplayer top-down arcade shooter. App is made to showcase Redis and Redis modules and their capabilities. This game is literally built on Redis!**
*Fully supporting Active-Active Geo-Distributed redis infrastructure once deployed using Redis deployments.
**Most of game event interactions are handled by calling RedisGears functions.
Join the arena, avoid projectiles by moving around and dominate others by landing hits!
Video Explanation of redis and Redis Gears functions
Application stack consists of three main components:
- JavaScript client:
- uses phaser 3 game engine for rendering and physics simulations;
- captures user inputs and sends inputs to the backend;
- Node.js backend:
- Serves as a WebServer;
- Provides a WebSocket server;
- Enables Redis API;
- Ensures User -> Redis -> User event communication;
- Redis:
- Uses RedisGears to define game functions (functionality);
- UsesRedis Searchto enable robust querying experience;
- Stores game state (enables data decoupling from node.js backend);
- Validates user inputs (using RedisGears functions);
- Provides Game API (again, RedisGears).
Redis Gears functions are registered on container startup in the redis/start_redis.sh
create_new_game(CommandReader, args: [user_id], optional: [private,secret]):- Creates a hash (
HSET); - Creates an expiry for the hash (
EXPIRE); - Triggered by calling
RG.TRIGGER create_new_game USER:p1_uid 1 secret123; - returns
game_id;
- Creates a hash (
create_new_user(CommandReader, args: [uid], optional: [settings,secret]):- Creates a hash (
HSET) ; - Creates an expiry for the hash (
EXPIRE); - Triggered by calling
RG.TRIGGER create_new_user p1_uid playername '' secret123; - returns
user_id;
- Creates a hash (
find_game(CommandReader, optional: [game_id]):- If game_id provided then executes
FT.SEARCH(seeRedis Searchbellow); - If game not found then trigger
create_new_game; - Triggered by calling
RG.TRIGGER find_game p1_uid; - returns
game_id;
- If game_id provided then executes
join_game(CommandReader, args: [user_id,game_id]):- Assigns the user to the game_instance (
HSET) - Increments player count of the game_instance (
HINCRBY); - Triggered by calling
RG.TRIGGER join_game p1_uid g1_gid secret123; - returns
game_id;
- Assigns the user to the game_instance (
leave_game(CommandReader, args: [user_ud,game_id]):- Deletes hash field (
HDEL); - Decrements player count of the game instance (
HINCRBY); - Triggered by calling
RG.TRIGGER leave_game p1_uid g1_gid; - returns
game_id;
- Deletes hash field (
user_authorized(CommandReader, args: [user_ud,game_id]):- Executes three (
HGET) calls to determine if user is a part of the game instance; - returns
game_id;
- Executes three (
player_actions(StreamReader, args: [action,action_args]):- Parses an event received from the user;
- Adds a state to
game_statesstream (XADD); - Publishes state change to subscribers (
PUBLISH); - Triggered by calling
XADD player_actions:g1_gid action p action_args "10,100,0".
player_actions Stream Reader explained:
- Client connects to node.js websocket server;
- With the active connection context the user is being
SUBSCRIBEd to thegame_idPubSub channel; - From now on all channel messages the user is subscribed to (on the backend) are also forwarded to the websocket (to frontend);
MESSAGE_EVENT_HANDLERSobject stores event -> function mapping, and on an incomming message one of the message event functions is being called (see function list below.)
client message event handler (MESSAGE_EVENT_HANDLERS):
p(pose) args: [user_id,x,y,orientation]; explanation: client receives auser_idposition update;c(click) args: [user_id,x(where it was clicked at),y(where it was clicked at), angle (from the player position to click position)]; explanation: client receivesuser_idclick eventr(respawn) args: [user_id,x,y]; explanation: client receivesuser_idhas respawned;l(leave) args: [user_id]; explanation: client receivesuser_idhas left the game;j(join) args: [user_id,x,y]; explanation: client receivesuser_idhas joined the game, anduser_idhas spawned in the (x,y) positionuid(user id) args: [is_valid]; explanation: client receives the response weather it is possible to find 'log the user in';gid(game id) args: [is_valid]; explanation: client receives if the user is part of the game (is user authorized)hitargs: [user_id]; explanation: client receives a message thatuser_idhas been hit / client can removeuser_idfrom rendering it;scoreargs: ['score']; explanation client receives current score of the game.
websocket server event handler (MESSAGE_EVENT_HANDLERS):
p(pose) args: [socket,x,y,orientation]; explanation: server receivesuser_idposition update;c(click) args: [socket,x(where it was clicked at),y(where it was clicked at), angle (from the player position to click position)]; explanation: server receivesuser_idclick event;r(respawn) args: [socket,x,y] ; explanation: server receives respawn request;l(leave) args: [socket]; explanation: server receives leave event;j(join) args: [socket,user_id,secret]; explanation: server receives join request, and authorizes the useruid(user id) args: [socket]; explanation: server receivesuser id;hitargs: [socket,enemy_id]; explanation: server receives hit event, and validates if it's possilbe;
Redis Search indexes are registered on container startup in the redis/start_redis.sh
Created Redis Search indexes:
FT.CREATE GAME ON HASH PREFIX 1 GAME: SCHEMA owner TEXT secret TEXT private NUMERIC SORTABLE playercount NUMERIC SORTABLE
FT.CREATE USER ON HASH PREFIX 1 USER: SCHEMA name TEXT settings TEXT secret TEXT
Query to find a game:
FT.SEARCH "GAME" "(@playercount:[0 1000])" SORTBY playercount DESC LIMIT 0 1
- docker
- docker-compose
Run following commands from the online_game directory
docker-compose up
access online WebServer via http://127.0.0.1:8080
If you are connected to the game, but player charecter is not showing then you most likeley don't have a connection to the WebSocket server ws://127.0.0.1:8002.
If you are being kicked out of the game, then congratulations, you most likely have found a bug,
General steps
To fully reset the states:
Try removing the redis dump.rdb located in ./redis/redis_data/dump.rdb.
rm redis/redis_data/dump.rdb
or
sudo rm redis/redis_data/dump.rdb
then run start up containers
docker-compose up --build
Other troubleshooting
Perhaps ports 8080 or 8082 are already registered. You can change port mapping in docker-compose.yaml to, for example, 8081:8080 and 8083:8002.
backend:
build:
dockerfile: ./dockerfiles/Dockerfile_node_backend
context: .
environment:
- NODE_ENV=development
volumes:
- ./game_conf.json:/game_config/game_conf.json
ports:
- 8080:8080
- 8082:8082
restart: always
WebSocket might be working inconsistently when minimized. For best experience do not run multiple minimized instances.
Run following commands to clean up your running environment.
docker-compose down
rm redis/redis_data/dump.rdb
or
docker-compose down
sudo rm redis/redis_data/dump.rdb



