├── favicon.ico ├── apple-touch-icon.png ├── assets ├── bubbledots │ ├── ground.png │ ├── blueParticle.png │ ├── greenParticle.png │ ├── redParticle.png │ └── yellowParticle.png └── github_pages │ ├── screenshot_01.png │ ├── screenshot_01_thumb.png │ └── Realtime_HTML5_Multiplayer_Games_with_Node_js.pdf ├── .gitignore ├── js ├── BubbleDots │ ├── README.md │ ├── script.js │ ├── BubbleDotsApp.js │ ├── BubbleDotsConstants.js │ ├── server.js │ ├── traits │ │ ├── GravityTrait.js │ │ ├── PerlinNoiseTrait.js │ │ ├── PoisonTrait.js │ │ ├── BoundaryTrait.js │ │ ├── ChaseTrait.js │ │ └── FoodTrait.js │ ├── entities │ │ ├── PlayerEntity.js │ │ └── CircleEntity.js │ ├── lib │ │ └── Tween.js │ ├── BubbleDotsClientGame.js │ └── BubbleDotsView.js ├── DemoHelloWorld │ ├── script.js │ ├── DemoHelloWorldConstants.js │ ├── server.js │ ├── DemoHelloWorld.js │ ├── CircleEntity.js │ ├── DemoView.js │ ├── DemoServerGame.js │ └── DemoClientGame.js ├── DemoCircles │ ├── script.js │ ├── DemoAppConstants.js │ ├── server.js │ ├── DemoApp.js │ ├── PlayerEntity.js │ ├── DemoView.js │ ├── CircleEntity.js │ ├── DemoClientGame.js │ └── DemoServerGame.js ├── DemoBox2D │ ├── script.js │ ├── DemoBox2DConstants.js │ ├── server.js │ ├── DemoBox2DApp.js │ ├── DemoBox2DEntity.js │ ├── DemoBox2DView.js │ └── DemoBox2DClientGame.js ├── lib │ ├── RequestAnimationFrame.js │ ├── Stats.js │ ├── circlecollision │ │ └── Circle.js │ ├── SortedLookupTable.js │ └── dd_belatedpng.js ├── core │ ├── AbstractGameView.js │ ├── RealtimeMutliplayerGame.js │ ├── AbstractServerGame.js │ └── AbstractGame.js ├── model │ ├── WorldEntityDescription.js │ ├── ImprovedNoise.js │ ├── Constants.js │ ├── NetChannelMessage.js │ └── GameEntity.js ├── controller │ ├── traits │ │ ├── KeyboardInputTrait.js │ │ └── BaseTrait.js │ └── FieldController.js └── input │ └── Keyboard.js ├── RealtimeMultiplayerNodeJs.iml ├── package.json ├── README.md ├── css ├── bubbledots.css ├── application.css └── style.css ├── DemoBubbleDots.html ├── DemoHelloWorld.html ├── DemoBox2DApp.html └── DemoCircles.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/favicon.ico -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/apple-touch-icon.png -------------------------------------------------------------------------------- /assets/bubbledots/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/bubbledots/ground.png -------------------------------------------------------------------------------- /assets/bubbledots/blueParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/bubbledots/blueParticle.png -------------------------------------------------------------------------------- /assets/bubbledots/greenParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/bubbledots/greenParticle.png -------------------------------------------------------------------------------- /assets/bubbledots/redParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/bubbledots/redParticle.png -------------------------------------------------------------------------------- /assets/bubbledots/yellowParticle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/bubbledots/yellowParticle.png -------------------------------------------------------------------------------- /assets/github_pages/screenshot_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/github_pages/screenshot_01.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://github.com/github/gitignore/blob/master/Global/IntelliJ.gitignore 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .idea/ 6 | .DS_Store 7 | 8 | /node_modules -------------------------------------------------------------------------------- /assets/github_pages/screenshot_01_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/github_pages/screenshot_01_thumb.png -------------------------------------------------------------------------------- /assets/github_pages/Realtime_HTML5_Multiplayer_Games_with_Node_js.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onedayitwillmake/RealtimeMultiplayerNodeJs/HEAD/assets/github_pages/Realtime_HTML5_Multiplayer_Games_with_Node_js.pdf -------------------------------------------------------------------------------- /js/BubbleDots/README.md: -------------------------------------------------------------------------------- 1 | __BubbleDots is full game created with RealtimeMultiplayerNode.js framework (in progress)__ 2 | 3 | ## Development Screenshots 4 | [![BubbleDots](http://farm3.static.flickr.com/2188/5706069624_996b00cdae.jpg)](http://farm3.static.flickr.com/2188/5706069624_996b00cdae.jpg) 5 | -------------------------------------------------------------------------------- /js/DemoHelloWorld/script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Callback for when browse is ready 3 | var onDocumentReady = function () { 4 | var clientGame = new DemoHelloWorld.DemoClientGame(); 5 | DemoHelloWorld.DemoClientGame.prototype.log("DemoClientGame: Ready..."); 6 | }; 7 | 8 | // Listen for ready 9 | window.addEventListener('load', onDocumentReady, false); 10 | })(); 11 | -------------------------------------------------------------------------------- /js/DemoCircles/script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | WEB_SOCKET_SWF_LOCATION = "ABC" 3 | // Callback for when browse is ready 4 | var onDocumentReady = function () { 5 | var clientGame = new DemoApp.DemoClientGame(); 6 | DemoApp.DemoClientGame.prototype.log("DemoClientGame: Ready..."); 7 | }; 8 | 9 | // Listen for ready 10 | window.addEventListener('load', onDocumentReady, false); 11 | })(); 12 | -------------------------------------------------------------------------------- /js/DemoBox2D/script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | WEB_SOCKET_SWF_LOCATION = "js/lib/Socket.IO-node/support/socket.io-client/lib/vendor/web-socket-js/WebSocketMain.swf"; 3 | 4 | // Callback for when browse is ready 5 | var onDocumentReady = function () { 6 | var clientGame = new DemoBox2D.DemoClientGame(); 7 | DemoBox2D.DemoClientGame.prototype.log("DemoBox2DClient: Ready..."); 8 | }; 9 | 10 | // Listen for ready 11 | window.addEventListener('load', onDocumentReady, false); 12 | })(); 13 | -------------------------------------------------------------------------------- /js/lib/RequestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides requestAnimationFrame in a cross browser way. 3 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 4 | */ 5 | 6 | if ( !window.requestAnimationFrame ) { 7 | 8 | window.requestAnimationFrame = ( function() { 9 | 10 | return window.webkitRequestAnimationFrame || 11 | window.mozRequestAnimationFrame || 12 | window.oRequestAnimationFrame || 13 | window.msRequestAnimationFrame || 14 | function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) { 15 | 16 | window.setTimeout( callback, 1000 / 60 ); 17 | 18 | }; 19 | 20 | } )(); 21 | 22 | } -------------------------------------------------------------------------------- /RealtimeMultiplayerNodeJs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /js/DemoHelloWorld/DemoHelloWorldConstants.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoHelloWorldConstants.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS - Demo 8 | Abstract: 9 | This class contains Constants used by the DemoHelloWorld in RealtimeMultiplayerGame 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | var clientDropWait = RealtimeMultiplayerGame.Constants.CL_DEFAULT_MAXRATE 13 | 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | DemoHelloWorld.Constants = { 19 | ENTITY_DEFAULT_RADIUS: 8, 20 | GAME_WIDTH: 700, 21 | GAME_HEIGHT: 450, 22 | MAX_CIRCLES: 300, 23 | GAME_DURATION: 1000 * 300, 24 | 25 | ENTITY_TYPES: { 26 | CIRCLE: 1 << 1 27 | } 28 | } 29 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RealtimeMultiplayerNodeJs", 3 | "version": "1.1.1", 4 | "description": "RealtimeMultiplayerNodeJS is a framework specifically for building HTML5 multiplayer games with the Client / Server model", 5 | "main": "js/BubbleDots/server.js", 6 | "dependencies": { 7 | "socket.io": "~0.9.16" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs.git" 16 | }, 17 | "author": "Mario Gonzalez", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs/issues" 21 | }, 22 | "homepage": "https://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs" 23 | } 24 | -------------------------------------------------------------------------------- /js/DemoCircles/DemoAppConstants.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoAppConstants.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS - Demo 8 | Abstract: 9 | This class contains Constants used by the DemoApp in RealtimeMultiplayerGame 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | var clientDropWait = RealtimeMultiplayerGame.Constants.CL_DEFAULT_MAXRATE 13 | 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | DemoApp.Constants = { 19 | ENTITY_DEFAULT_RADIUS: 8, 20 | GAME_WIDTH: 700, 21 | GAME_HEIGHT: 450, 22 | MAX_CIRCLES: 100, 23 | GAME_DURATION: 1000 * 300, 24 | 25 | ENTITY_TYPES: { 26 | UNKNOWN: 1 << 0, 27 | GENERIC_CIRCLE: 1 << 1, 28 | PLAYER_ENTITY: 1 << 2 29 | } 30 | } 31 | })(); -------------------------------------------------------------------------------- /js/core/AbstractGameView.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractGameView.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class contains an interface for a GameView within RealtimeMultiplayerNodeJS 10 | Basic Usage: 11 | [This class only contains an interface] 12 | Your specific game's implementation of a GameView should implement at least these methods. 13 | */ 14 | (function () { 15 | RealtimeMultiplayerGame.AbstractGameView = function () { 16 | }; 17 | 18 | RealtimeMultiplayerGame.AbstractGameView.prototype = { 19 | setup: function () { 20 | }, 21 | update: function (gameClockReal) { 22 | }, 23 | addEntity: function (anEntityView) { 24 | }, 25 | removeEntity: function (anEntityView) { 26 | }, 27 | dealloc: function () { 28 | } 29 | }; 30 | })(); -------------------------------------------------------------------------------- /js/DemoBox2D/DemoBox2DConstants.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoBox2DConstants.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS - Demo 8 | Abstract: 9 | This class contains Constants used by the DemoBox2D in RealtimeMultiplayerGame 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | var clientDropWait = RealtimeMultiplayerGame.Constants.CL_DEFAULT_MAXRATE 13 | 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | DemoBox2D.Constants = { 19 | ENTITY_DEFAULT_RADIUS: 8, 20 | ENTITY_BOX_SIZE: 16, 21 | PHYSICS_SCALE: 32, 22 | GAME_WIDTH: 700, 23 | GAME_HEIGHT: 450, 24 | MAX_OBJECTS: 100, 25 | GAME_DURATION: 1000 * 300, 26 | 27 | ENTITY_TYPES: { 28 | CIRCLE: 1 << 1, 29 | BOX: 1 << 2 30 | } 31 | } 32 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Callback for when browse is ready 3 | var onDocumentReady = function () { 4 | var that = this; 5 | 6 | // Preload images 7 | var imagePreloader = new CAAT.ImagePreloader(); 8 | 9 | // Callback for image preloader 10 | imagePreloader.loadImages(BubbleDots.Constants.IMAGE_ASSETS, function (counter, images) { 11 | 12 | BubbleDots.DemoClientGame.prototype.log("Preloading image..."); 13 | 14 | if (counter != images.length) return; 15 | 16 | BubbleDots.IMAGE_CACHE = images; 17 | 18 | // Image preload complete - START THE GAME! 19 | var clientGame = new BubbleDots.DemoClientGame(images); 20 | }); 21 | }; 22 | 23 | 24 | // Adjust aside to match game dimensions 25 | var newHeight = BubbleDots.Constants.GAME_HEIGHT; 26 | newHeight -= parseFloat($("aside").css("padding-top")) * 2; 27 | $("aside").height(newHeight + "px"); 28 | 29 | // Listen for ready 30 | window.addEventListener('load', onDocumentReady, false); 31 | })(); 32 | -------------------------------------------------------------------------------- /js/DemoCircles/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | server.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the base server module for starting RealtimeMultiplayerGame 10 | Basic Usage: 11 | node server.js 12 | Version: 13 | 1.0 14 | */ 15 | require("../lib/SortedLookupTable.js"); 16 | require("../core/RealtimeMutliplayerGame.js"); 17 | require("../model/Point.js"); 18 | require("../lib/circlecollision/Circle.js"); 19 | require("../lib/circlecollision/CircleManager.js"); 20 | require("../model/Constants.js"); 21 | require("../model/NetChannelMessage.js"); 22 | require("../controller/FieldController.js"); 23 | require("../core/AbstractGame.js"); 24 | require("../network/Client.js"); 25 | require("../network/ServerNetChannel.js"); 26 | require("../core/AbstractServerGame.js"); 27 | require("../model/GameEntity.js"); 28 | require("../model/WorldEntityDescription.js"); 29 | require("../input/Keyboard.js"); 30 | 31 | 32 | require("./DemoApp.js"); 33 | require("./DemoAppConstants.js"); 34 | require("./CircleEntity.js"); 35 | require("./PlayerEntity.js"); 36 | require("./DemoServerGame.js"); 37 | 38 | var game = new DemoApp.DemoServerGame(); 39 | game.startGameClock(); 40 | -------------------------------------------------------------------------------- /js/DemoBox2D/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | server.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the base server module for starting RealtimeMultiplayerGame 10 | Basic Usage: 11 | node server.js 12 | Version: 13 | 1.0 14 | */ 15 | 16 | require("../lib/SortedLookupTable.js"); 17 | require("../core/RealtimeMutliplayerGame.js"); 18 | require("../model/Point.js"); 19 | require("../lib/circlecollision/Circle.js"); 20 | require("../lib/circlecollision/CircleManager.js"); 21 | require("../model/Constants.js"); 22 | require("../model/NetChannelMessage.js"); 23 | require("../controller/FieldController.js"); 24 | require("../core/AbstractGame.js"); 25 | require("../network/Client.js"); 26 | require("../network/ServerNetChannel.js"); 27 | require("../core/AbstractServerGame.js"); 28 | require("../model/GameEntity.js"); 29 | require("../model/WorldEntityDescription.js"); 30 | require("../input/Keyboard.js"); 31 | 32 | //require("v8-profiler"); 33 | require("./DemoBox2DApp.js"); 34 | require("./DemoBox2DConstants.js"); 35 | require("./DemoBox2DEntity.js"); 36 | require("./DemoBox2DServerGame.js"); 37 | 38 | var game = new DemoBox2D.DemoServerGame(); 39 | game.startGameClock(); 40 | -------------------------------------------------------------------------------- /js/DemoHelloWorld/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | server.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the base server module for starting RealtimeMultiplayerGame 10 | Basic Usage: 11 | node server.js 12 | Version: 13 | 1.0 14 | */ 15 | require("../lib/SortedLookupTable.js"); 16 | require("../core/RealtimeMutliplayerGame.js"); 17 | require("../model/Point.js"); 18 | require("../lib/circlecollision/Circle.js"); 19 | require("../lib/circlecollision/CircleManager.js"); 20 | require("../model/Constants.js"); 21 | require("../model/NetChannelMessage.js"); 22 | require("../controller/FieldController.js"); 23 | require("../core/AbstractGame.js"); 24 | require("../network/Client.js"); 25 | require("../network/ServerNetChannel.js"); 26 | require("../core/AbstractServerGame.js"); 27 | require("../model/GameEntity.js"); 28 | require("../model/WorldEntityDescription.js"); 29 | require("../input/Keyboard.js"); 30 | 31 | 32 | //require("v8-profiler"); 33 | require("./DemoHelloWorld.js"); 34 | require("./DemoHelloWorldConstants.js"); 35 | require("./CircleEntity.js"); 36 | require("./DemoServerGame.js"); 37 | 38 | var game = new DemoHelloWorld.DemoServerGame(); 39 | game.startGameClock(); -------------------------------------------------------------------------------- /js/DemoCircles/DemoApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | RealtimeMultiplayerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the core module for RealtimeMultiplayerGame contains the namespace, and extend method 10 | 11 | Basic Usage: 12 | This class is not instantiated 13 | Version: 14 | 1.0 15 | */ 16 | DemoApp = (typeof DemoApp === 'undefined') ? {} : DemoApp; 17 | /** 18 | * Allows a package to create a namespace within RealtimeMultiplayerGame 19 | * From Javascript Patterns book 20 | * @param ns_string 21 | */ 22 | DemoApp.namespace = function (ns_string) { 23 | var parts = ns_string.split('.'), 24 | parent = DemoApp, 25 | i = 0; 26 | 27 | // strip redundant leading global 28 | if (parts[0] === "DemoApp") { 29 | parts = parts.slice(1); 30 | } 31 | 32 | var len = parts.length, 33 | aPackage = null; 34 | for (i = 0; i < len; i += 1) { 35 | var singlePart = parts[i]; 36 | // create a property if it doesn't exist 37 | if (typeof parent[singlePart] === "undefined") { 38 | parent[singlePart] = {}; 39 | } 40 | parent = parent[singlePart]; 41 | 42 | } 43 | return parent; 44 | }; -------------------------------------------------------------------------------- /js/BubbleDots/BubbleDotsApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | RealtimeMultiplayerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the core module for RealtimeMultiplayerGame contains the namespace, and extend method 10 | 11 | Basic Usage: 12 | This class is not instantiated 13 | Version: 14 | 1.0 15 | */ 16 | BubbleDots = (typeof BubbleDots === 'undefined') ? {} : BubbleDots; 17 | /** 18 | * Allows a package to create a namespace within RealtimeMultiplayerGame 19 | * From Javascript Patterns book 20 | * @param ns_string 21 | */ 22 | BubbleDots.namespace = function (ns_string) { 23 | var parts = ns_string.split('.'), 24 | parent = BubbleDots, 25 | i = 0; 26 | 27 | // strip redundant leading global 28 | if (parts[0] === "BubbleDots") { 29 | parts = parts.slice(1); 30 | } 31 | 32 | var len = parts.length, 33 | aPackage = null; 34 | for (i = 0; i < len; i += 1) { 35 | var singlePart = parts[i]; 36 | // create a property if it doesn't exist 37 | if (typeof parent[singlePart] === "undefined") { 38 | parent[singlePart] = {}; 39 | } 40 | parent = parent[singlePart]; 41 | 42 | } 43 | return parent; 44 | }; -------------------------------------------------------------------------------- /js/DemoHelloWorld/DemoHelloWorld.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | RealtimeMultiplayerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the core module for RealtimeMultiplayerGame contains the namespace, and extend method 10 | 11 | Basic Usage: 12 | This class is not instantiated 13 | Version: 14 | 1.0 15 | */ 16 | DemoHelloWorld = (typeof DemoHelloWorld === 'undefined') ? {} : DemoHelloWorld; 17 | /** 18 | * Allows a package to create a namespace within RealtimeMultiplayerGame 19 | * From Javascript Patterns book 20 | * @param ns_string 21 | */ 22 | DemoHelloWorld.namespace = function (ns_string) { 23 | var parts = ns_string.split('.'), 24 | parent = DemoHelloWorld, 25 | i = 0; 26 | 27 | // strip redundant leading global 28 | if (parts[0] === "DemoHelloWorld") { 29 | parts = parts.slice(1); 30 | } 31 | 32 | var len = parts.length, 33 | aPackage = null; 34 | for (i = 0; i < len; i += 1) { 35 | var singlePart = parts[i]; 36 | // create a property if it doesn't exist 37 | if (typeof parent[singlePart] === "undefined") { 38 | parent[singlePart] = {}; 39 | } 40 | parent = parent[singlePart]; 41 | 42 | } 43 | return parent; 44 | }; -------------------------------------------------------------------------------- /js/BubbleDots/BubbleDotsConstants.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BubbleDotsConstants.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS - Demo 8 | Abstract: 9 | This class contains Constants used by the BubbleDots in RealtimeMultiplayerGame 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | var clientDropWait = RealtimeMultiplayerGame.Constants.CL_DEFAULT_MAXRATE 13 | 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | BubbleDots.Constants = { 19 | ENTITY_DEFAULT_RADIUS: 17, 20 | GAME_WIDTH: 700, 21 | GAME_HEIGHT: 450, 22 | MAX_CIRCLES: 200, 23 | GAME_DURATION: 1000 * 300, 24 | 25 | ENTITY_TYPES: { 26 | CANDY_ENTITY: 1 << 0, 27 | PLAYER_ENTITY: 1 << 1 28 | }, 29 | 30 | IMAGE_ASSETS: [ 31 | {id: "particle" + 1, url: "assets/bubbledots/blueParticle.png"}, 32 | {id: "particle" + 2, url: "assets/bubbledots/redParticle.png"}, 33 | {id: "particle" + 3, url: "assets/bubbledots/greenParticle.png"}, 34 | {id: "particle" + 4, url: "assets/bubbledots/yellowParticle.png"}, 35 | {id: "ground", url: "assets/bubbledots/ground.png"} 36 | ] 37 | } 38 | })(); -------------------------------------------------------------------------------- /js/DemoBox2D/DemoBox2DApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoBox2DApp.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the core module for the DemoBox2DApp contains the namespace 10 | This demo shows how to create a game that uses a Box2D javascript implementation (https://github.com/HBehrens/box2d.js) 11 | Basic Usage: 12 | This class is not instantiated 13 | Version: 14 | 1.0 15 | */ 16 | DemoBox2D = (typeof DemoBox2D === 'undefined') ? {} : DemoBox2D; 17 | /** 18 | * Allows a package to create a namespace within RealtimeMultiplayerGame 19 | * From Javascript Patterns book 20 | * @param ns_string 21 | */ 22 | DemoBox2D.namespace = function (ns_string) { 23 | var parts = ns_string.split('.'), 24 | parent = DemoBox2D, 25 | i = 0; 26 | 27 | // strip redundant leading global 28 | if (parts[0] === "DemoBox2D") { 29 | parts = parts.slice(1); 30 | } 31 | 32 | var len = parts.length, 33 | aPackage = null; 34 | for (i = 0; i < len; i += 1) { 35 | var singlePart = parts[i]; 36 | // create a property if it doesn't exist 37 | if (typeof parent[singlePart] === "undefined") { 38 | parent[singlePart] = {}; 39 | } 40 | parent = parent[singlePart]; 41 | 42 | } 43 | return parent; 44 | }; -------------------------------------------------------------------------------- /js/BubbleDots/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | server.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the base server module for starting RealtimeMultiplayerGame 10 | Basic Usage: 11 | node server.js 12 | Version: 13 | 1.0 14 | */ 15 | require("../lib/SortedLookupTable.js"); 16 | require("../core/RealtimeMutliplayerGame.js"); 17 | require("../model/Point.js"); 18 | require("../model/Constants.js"); 19 | require("../model/NetChannelMessage.js"); 20 | require("../model/GameEntity.js"); 21 | require("../model/WorldEntityDescription.js"); 22 | require("../network/ServerNetChannel.js"); 23 | require("../network/Client.js"); 24 | require("../lib/circlecollision/Circle.js"); 25 | require("../lib/circlecollision/CircleManager.js"); 26 | require("../controller/FieldController.js"); 27 | require("../core/AbstractGame.js"); 28 | require("../core/AbstractServerGame.js"); 29 | require("../input/Keyboard.js"); 30 | require("../controller/traits/BaseTrait.js"); 31 | 32 | 33 | //require("v8-profiler"); 34 | require("./BubbleDotsApp.js"); 35 | require("./BubbleDotsConstants.js"); 36 | require("./entities/CircleEntity.js"); 37 | require("./entities/PlayerEntity.js"); 38 | require("./traits/FoodTrait.js"); 39 | require("./traits/PoisonTrait.js"); 40 | require("./traits/PerlinNoiseTrait.js"); 41 | require("./traits/ChaseTrait.js"); 42 | require("./traits/GravityTrait.js"); 43 | require("./traits/BoundaryTrait.js"); 44 | require("./BubbleDotsServerGame.js"); 45 | 46 | var game = new BubbleDots.DemoServerGame(); 47 | game.startGameClock(); 48 | -------------------------------------------------------------------------------- /js/BubbleDots/traits/GravityTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | GravityTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This trait will cause an entity to chase a target 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | var RATE = 0.2; 16 | BubbleDots.traits.GravityTrait = function () { 17 | BubbleDots.traits.GravityTrait.superclass.constructor.call(this); 18 | this._force = BubbleDots.traits.GravityTrait.prototype.DEFAULT_FORCE; 19 | }; 20 | 21 | BubbleDots.traits.GravityTrait.prototype = { 22 | displayName: "GravityTrait", // Unique string name for this Trait 23 | 24 | _force: 0, 25 | 26 | DEFAULT_FORCE: 0.21, 27 | 28 | /** 29 | * @inheritDoc 30 | */ 31 | attach: function (anEntity) { 32 | BubbleDots.traits.GravityTrait.superclass.attach.call(this, anEntity); 33 | this.intercept(['updatePosition']); 34 | }, 35 | 36 | /** 37 | * Intercepted properties 38 | */ 39 | updatePosition: function (speedFactor, gameClock, gameTick) { 40 | var trait = this.getTraitWithName("GravityTrait"); 41 | 42 | this.acceleration.y += trait._force * speedFactor; 43 | // Call the original handleAcceleration 44 | trait.interceptedProperties._data.updatePosition.call(this, speedFactor, gameClock, gameTick); 45 | }, 46 | 47 | ///// ACCESSORS 48 | /** 49 | * Set the gravitational force 50 | * @param {Number} force Strength of gravity in Y axis 51 | */ 52 | setForce: function (force) { 53 | this._force = force 54 | } 55 | }; 56 | 57 | // Extend BaseTrait 58 | RealtimeMultiplayerGame.extend(BubbleDots.traits.GravityTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 59 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/traits/PerlinNoiseTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | PerlinNoiseTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | Applies perlin noise to an objects velocity 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | 16 | var NOISE_SEED = Math.random() * 1000; 17 | 18 | BubbleDots.traits.PerlinNoiseTrait = function () { 19 | BubbleDots.traits.PerlinNoiseTrait.superclass.constructor.call(this); 20 | }; 21 | 22 | BubbleDots.traits.PerlinNoiseTrait.prototype = { 23 | displayName: "PerlinNoiseTraitTrait", // Unique string name for this Trait 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | attach: function (anEntity) { 29 | BubbleDots.traits.PerlinNoiseTrait.superclass.attach.call(this, anEntity); 30 | this.intercept(['updatePosition']); 31 | }, 32 | 33 | /** 34 | * Intercepted properties 35 | */ 36 | updatePosition: function (speedFactor, gameClock, gameTick) { 37 | // Call the original handleAcceleration 38 | var trait = this.getTraitWithName("PerlinNoiseTraitTrait"); 39 | 40 | // Modify velocity using perlin noise 41 | var theta = 0.008; 42 | var noise = RealtimeMultiplayerGame.model.noise(this.position.x * theta, this.position.y * theta, NOISE_SEED + gameTick * 0.001); 43 | var angle = noise * Math.PI * 12.566370614359172; // PI * 4 44 | var speed = 0.05; 45 | this.acceleration.x += Math.cos(angle) * speed;//- 0.1; 46 | this.acceleration.y += Math.sin(angle) * speed; 47 | 48 | trait.interceptedProperties._data.updatePosition.call(this, speedFactor, gameClock, gameTick); 49 | } 50 | }; 51 | 52 | // Extend BaseTrait 53 | RealtimeMultiplayerGame.extend(BubbleDots.traits.PerlinNoiseTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 54 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/traits/PoisonTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | PoisonTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | 16 | BubbleDots.traits.PoisonTrait = function () { 17 | BubbleDots.traits.PoisonTrait.superclass.constructor.call(this); 18 | }; 19 | 20 | BubbleDots.traits.PoisonTrait.prototype = { 21 | displayName: "PoisonTrait", // Unique string name for this Trait 22 | color: "2", 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | attach: function (anEntity) { 28 | BubbleDots.traits.PoisonTrait.superclass.attach.call(this, anEntity); 29 | this.intercept(['onCollision', 'color']); 30 | }, 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | execute: function () { 36 | BubbleDots.traits.PoisonTrait.superclass.execute.call(this); 37 | }, 38 | 39 | /** 40 | * Intercepted properties 41 | */ 42 | /** 43 | * Called when this object has collided with another 44 | * @param a Object A in the collision pair, note this may be this object 45 | * @param b Object B in the collision pair, note this may be this object 46 | * @param collisionNormal A vector describing the collision 47 | */ 48 | onCollision: function (a, b, collisionNormal) { 49 | // We're either A or B, so perform a simple check against A to figure out which of the two objects we are 50 | var me = this === a ? a : b; 51 | var them = this === a ? b : a; 52 | 53 | // var newRadius = Math.max( 0.1, them.radius-0.5 ); 54 | // them.radius = newRadius; 55 | // them.collisionCircle.setRadius( newRadius ); 56 | 57 | them.acceleration.translatePoint(collisionNormal.multiply(them.velocityMax * 0.9)); 58 | } 59 | 60 | }; 61 | 62 | // Extend BaseTrait 63 | RealtimeMultiplayerGame.extend(BubbleDots.traits.PoisonTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 64 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/traits/BoundaryTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BoundaryTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This trait will cause an entity to react to boundary collisions 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | BubbleDots.traits.BoundaryTrait = function (aCollisionManager) { 16 | BubbleDots.traits.BoundaryTrait.superclass.constructor.call(this); 17 | this._collisionManager = aCollisionManager; 18 | }; 19 | 20 | BubbleDots.traits.BoundaryTrait.prototype = { 21 | displayName: "BoundaryTrait", // Unique string name for this Trait 22 | _boundaryRule: 0, 23 | _collisionManager: null, 24 | 25 | /** 26 | * @inheritDoc 27 | */ 28 | attach: function (anEntity) { 29 | BubbleDots.traits.BoundaryTrait.superclass.attach.call(this, anEntity); 30 | this.intercept(['updatePosition']); 31 | }, 32 | 33 | /** 34 | * Intercepted properties 35 | */ 36 | updatePosition: function (speedFactor, gameClock, gameTick) { 37 | var trait = this.getTraitWithName("BoundaryTrait"); 38 | 39 | // Call the original handleAcceleration 40 | trait._collisionManager.handleBoundaryForCircle(this.getCollisionCircle(), trait._boundaryRule); 41 | trait.interceptedProperties._data.updatePosition.call(this, speedFactor, gameClock, gameTick); 42 | }, 43 | 44 | detach: function (force) { 45 | this._collisionManager = null; 46 | BubbleDots.traits.BoundaryTrait.superclass.detach.call(this, force); 47 | }, 48 | 49 | ///// ACCESSORS 50 | /** 51 | * Set the gravitational force 52 | * @param {Number} aBoundaryRule Boundary rule to apply to collision circle, see CollisionManager 53 | */ 54 | setBoundaryRule: function (aBoundaryRule) { 55 | this._boundaryRule = aBoundaryRule; 56 | return this; 57 | } 58 | }; 59 | 60 | // Extend BaseTrait 61 | RealtimeMultiplayerGame.extend(BubbleDots.traits.BoundaryTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 62 | })(); -------------------------------------------------------------------------------- /js/model/WorldEntityDescription.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | WorldEntityDescription.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | A WorldEntityDescription is a full description of the current world state. 10 | 11 | AbstractServerGame creates this each 'tick' 12 | -> NetChannel passes it to each Client 13 | -> Each client performs 'delta compression' to remove unchanged entities from being sent 14 | -> If ready, each client sends the customized WorldEntityDescription to it's connection 15 | Basic Usage: 16 | // Create a new world-entity-description, could be some room for optimization here but it only happens once per game loop anyway 17 | var worldEntityDescription = new WorldEntityDescription( this ); 18 | this.netChannel.tick( this.gameClock, worldEntityDescription ); 19 | */ 20 | (function () { 21 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.model"); 22 | 23 | RealtimeMultiplayerGame.model.WorldEntityDescription = function (aGameInstance, allEntities) { 24 | this.gameClock = aGameInstance.getGameClock(); 25 | this.gameTick = aGameInstance.getGameTick(); 26 | this.allEntities = allEntities; 27 | 28 | 29 | // Ask each entity to create it's EntityDescriptionString 30 | this.entities = []; 31 | 32 | 33 | return this; 34 | }; 35 | 36 | RealtimeMultiplayerGame.model.WorldEntityDescription.prototype = { 37 | entities: null, 38 | gameClock: 0, 39 | gameTick: 0, 40 | 41 | /** 42 | * Ask each entity to create it's entity description 43 | * Returns a single snapshot of the worlds current state as a '|' delimited string 44 | * @return {String} A '|' delmited string of the current world state 45 | */ 46 | getEntityDescriptionAsString: function () { 47 | var len = this.allEntities.length; 48 | var fullDescriptionString = ''; 49 | 50 | this.allEntities.forEach(function (key, entity) { 51 | var entityDescriptionString = entity.constructEntityDescription(this.gameTick); 52 | fullDescriptionString += "|" + entityDescriptionString; 53 | }, this); 54 | 55 | return fullDescriptionString; 56 | } 57 | } 58 | })(); -------------------------------------------------------------------------------- /js/DemoCircles/PlayerEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoApp.CircleEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoApp 8 | Abstract: 9 | This is the base entity for the demo game 10 | Basic Usage: 11 | 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | 17 | DemoApp.PlayerEntity = function (anEntityid, aClientid) { 18 | DemoApp.PlayerEntity.superclass.constructor.call(this, anEntityid, aClientid); 19 | 20 | return this; 21 | }; 22 | 23 | DemoApp.PlayerEntity.prototype = { 24 | entityType: DemoApp.Constants.ENTITY_TYPES.PLAYER_ENTITY, 25 | input: null, 26 | radius: 40, 27 | 28 | updateView: function () { 29 | DemoApp.PlayerEntity.superclass.updateView.call(this); 30 | }, 31 | 32 | setInput: function (input) { 33 | this.input = input; 34 | }, 35 | 36 | updatePosition: function () { 37 | var moveSpeed = 1.5; 38 | // Horizontal accelertheation 39 | if (this.input.isLeft()) this.acceleration.x -= moveSpeed; 40 | if (this.input.isRight()) this.acceleration.x += moveSpeed; 41 | 42 | // Vertical movement 43 | if (this.input.isUp()) this.acceleration.y -= moveSpeed; 44 | if (this.input.isDown()) this.acceleration.y += moveSpeed; 45 | 46 | this.velocity.translatePoint(this.acceleration); 47 | this.velocity.limit(5); 48 | this.velocity.multiply(0.85); 49 | this.acceleration.set(0, 0); 50 | this.collisionCircle.position.translatePoint(this.velocity); 51 | this.position = this.collisionCircle.position.clone(); 52 | }, 53 | 54 | setCollisionCircle: function (aCollisionCircle) { 55 | DemoApp.PlayerEntity.superclass.setCollisionCircle.call(this, aCollisionCircle); 56 | // this.collisionCircle.setIsFixed( true ); 57 | }, 58 | 59 | /** 60 | * Deallocate memory 61 | */ 62 | dealloc: function () { 63 | if (this.input) { 64 | this.input.dealloc(); 65 | delete this.input; 66 | } 67 | DemoApp.CircleEntity.superclass.dealloc.call(this); 68 | } 69 | }; 70 | 71 | // extend RealtimeMultiplayerGame.model.GameEntity 72 | RealtimeMultiplayerGame.extend(DemoApp.PlayerEntity, DemoApp.CircleEntity, null); 73 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __RealtimeMultiplayerNodeJS is a framework specifically for building HTML5 multiplayer games with the Client / Server model__ 2 | 3 | # JSConf talk 4 | 5 | > **This repo was made to coincide with this talk** 6 | 7 | https://www.youtube.com/watch?v=z1_QpUkX2Gg 8 | 9 | ## Status: 10 | 11 | This repository is not in active development, unfortunately. 12 | However I do think there are still not enough good resources for learning how to do this on the client side so this respostory will remain preserved to perhaps give people something to poke around in and borrow concepts from. 13 | 14 | 15 | 16 | 17 | ##### ChangeLog: 18 | * Updated to work with socket.io 0.9.16 (thx @brendonjohn) 19 | 20 | # Video Demo 21 | https://vimeo.com/24149718 22 | 23 | # How to use 24 | 1. Download this repo 25 | 2. In the terminal, navigate to the root directory of the repo 26 | 3. Run "npm install" 27 | 4. Run "node js/DemoBox2D/server.js" 28 | 5. Within another terminal, navigate to the root directory and run "python -m SimpleHTTPServer" 29 | 6. From the browser, open "http://127.0.0.1:8000/DemoBox2DApp.html" 30 | 31 | [![DemoBox2D](http://farm6.static.flickr.com/5105/5694643562_fffce8b9cf_z.jpg)](http://farm6.static.flickr.com/5105/5694643562_53e54993dd_o.png) 32 | 33 | # RealtimeMultiplayerNodeJS comes with 3 demos 34 | ### DemoHelloWorld 35 | The most basic interesting working demo I could come up with. Objects move from left to right 36 | ![DemoHelloWorld](http://farm6.static.flickr.com/5309/5694599438_6fd56e21bd_b.jpg "DemoCircle") 37 | 38 | ### DemoCircle 39 | A demonstration of the engine's simple CircleCollision engine, which can provide you with simple collision information and fires an event when two objects collide with the two objects. 40 | This demo __also shows one implementation__ of having a special kind of entity which is controlled by the keyboard from a connected user. __A character controlled entity__ 41 | ![DemoCircle](http://farm4.static.flickr.com/3483/5694599612_1cdb1f935e_b.jpg "DemoCircle") 42 | 43 | ### DemoBox2D 44 | This demo uses a [Box2D.js](https://github.com/HBehrens/box2d.js) implementation to create a world, and show's off the idea of synchronized physics, and taking advantage that all the simulation happens on the server. 45 | It also shows __synchronized interaction__ between multiple users, and an example of sending a message to the server which it interprets back into the game 46 | ![DemoBox2D](http://farm6.static.flickr.com/5027/5694599478_7c9339c99c_b.jpg) 47 | -------------------------------------------------------------------------------- /css/bubbledots.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | // ========================================== \\ 4 | || || 5 | || Your styles ! || 6 | || || 7 | \\ ========================================== // 8 | */ 9 | 10 | body{ 11 | font-family:Helvetica, Helvetica Neue, Arial, sans-serif; 12 | } 13 | 14 | a, a:active, a:visited { color: #1c1c1c; } 15 | a:hover { color: #333333; } 16 | 17 | .wrapper{ 18 | margin:auto; 19 | width:950px; 20 | } 21 | 22 | #header-container{ 23 | background-color: #333333; 24 | height:130px; 25 | border-bottom:20px solid #9dfafa; 26 | margin-bottom:50px; 27 | -webkit-box-shadow:0 40px 40px -40px #AAA; 28 | -moz-box-shadow:0 40px 40px -40px #AAA; 29 | box-shadow:0 40px 40px -40px #AAA; 30 | } 31 | 32 | #title{ 33 | font-size: 40px; 34 | color:white; 35 | padding-top:80px; 36 | float:left; 37 | } 38 | 39 | h2{ 40 | font-size: 55px; 41 | } 42 | 43 | h3{ 44 | font-size: 20px; 45 | } 46 | 47 | nav{ 48 | float:right; 49 | margin-top:100px; 50 | } 51 | 52 | nav ul, nav ul li{ 53 | display:inline; 54 | } 55 | 56 | nav a{ 57 | padding:20px; 58 | text-decoration:none; 59 | background-color: #9dfafa; 60 | } 61 | 62 | aside{ 63 | color:white; 64 | padding:10px; 65 | width: 230px; 66 | float:right; 67 | background-color: #333333; 68 | } 69 | 70 | #main p{ 71 | font-family:Helvetica, Helvetica Neue, Arial; 72 | text-shadow:none; 73 | } 74 | 75 | aside p { 76 | font-size:11px; 77 | font-family:monospace; 78 | } 79 | 80 | #main header h2{ 81 | padding-bottom:30px; 82 | } 83 | 84 | /* 85 | article header{ 86 | margin-bottom:50px; 87 | padding-bottom:30px; 88 | width:700px; 89 | -webkit-box-shadow:0 45px 60px -60px #AAA; 90 | -moz-box-shadow:0 45px 60px -60px #AAA; 91 | box-shadow:0 45px 60px -60px #AAA; 92 | } 93 | */ 94 | 95 | #footer-container{ 96 | background-color: #333333; 97 | height:240px; 98 | border-top:20px solid #9dfafa; 99 | margin-top:50px; 100 | -webkit-box-shadow:0 -40px 40px -40px #AAA; 101 | -moz-box-shadow:0 -40px 40px -40px #AAA; 102 | box-shadow:0 -40px 40px -40px #AAA; 103 | } 104 | 105 | #footer-container footer{ 106 | color:white; 107 | margin-top: 10px; 108 | 109 | } 110 | 111 | .info { 112 | position:absolute; 113 | right: 0px; 114 | top:5px; 115 | background-color:white; 116 | padding:10px; 117 | margin-top:5px; 118 | } 119 | 120 | #jquery-test{ 121 | top:45px; 122 | } 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /js/core/RealtimeMutliplayerGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | RealtimeMultiplayerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This is the core module for RealtimeMultiplayerGame contains the namespace, and extend method 10 | 11 | Basic Usage: 12 | This class is not instantiated 13 | */ 14 | RealtimeMultiplayerGame = (typeof RealtimeMultiplayerGame === 'undefined') ? {} : RealtimeMultiplayerGame; 15 | /** 16 | * Allows a package to create a namespace within RealtimeMultiplayerGame 17 | * From Javascript Patterns book 18 | * @param ns_string 19 | */ 20 | RealtimeMultiplayerGame.namespace = function (ns_string) { 21 | var parts = ns_string.split('.'), 22 | parent = RealtimeMultiplayerGame, 23 | i = 0; 24 | 25 | // strip redundant leading global 26 | if (parts[0] === "RealtimeMultiplayerGame") { 27 | parts = parts.slice(1); 28 | } 29 | 30 | var len = parts.length, 31 | aPackage = null; 32 | for (i = 0; i < len; i += 1) { 33 | var singlePart = parts[i]; 34 | // create a property if it doesn't exist 35 | if (typeof parent[singlePart] === "undefined") { 36 | parent[singlePart] = {}; 37 | } 38 | parent = parent[singlePart]; 39 | 40 | } 41 | return parent; 42 | }; 43 | 44 | /** 45 | * Allows a simple inheritance model 46 | * based on http://www.kevs3d.co.uk/dev/canvask3d/scripts/mathlib.js 47 | */ 48 | RealtimeMultiplayerGame.extend = function (subc, superc, overrides) { 49 | /** 50 | * @constructor 51 | */ 52 | var F = function () { 53 | }; 54 | var i; 55 | 56 | if (overrides) { 57 | F.prototype = superc.prototype; 58 | subc.prototype = new F(); 59 | subc.prototype.constructor = subc; 60 | subc.superclass = superc.prototype; 61 | if (superc.prototype.constructor == Object.prototype.constructor) { 62 | superc.prototype.constructor = superc; 63 | } 64 | for (i in overrides) { 65 | if (overrides.hasOwnProperty(i)) { 66 | subc.prototype[i] = overrides[i]; 67 | } 68 | } 69 | } else { 70 | 71 | subc.prototype.constructor = subc; 72 | subc.superclass = superc.prototype; 73 | if (superc.prototype.constructor == Object.prototype.constructor) { 74 | superc.prototype.constructor = superc; 75 | } 76 | for (i in superc.prototype) { 77 | if (false == subc.prototype.hasOwnProperty(i)) { 78 | subc.prototype[i] = superc.prototype[i]; 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /js/DemoBox2D/DemoBox2DEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoBox2D.Box2DEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoBox2D 8 | Abstract: 9 | This is the base entity for the demo game 10 | Basic Usage: 11 | 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | var DAG2RAD = 0.0174532925; 17 | var RAD2DEG = 57.2957795; 18 | 19 | DemoBox2D.Box2DEntity = function (anEntityid, aClientid) { 20 | DemoBox2D.Box2DEntity.superclass.constructor.call(this, anEntityid, aClientid); 21 | }; 22 | 23 | DemoBox2D.Box2DEntity.prototype = { 24 | b2Body: null, // Reference to Box2D body 25 | radius: 1, 26 | entityType: DemoBox2D.Constants.ENTITY_TYPES.BOX, 27 | 28 | /** 29 | * @inheritDoc 30 | */ 31 | updateView: function () { 32 | if (!this.view) return; 33 | this.view.x = this.position.x - this.radius; 34 | this.view.y = this.position.y - this.radius; 35 | 36 | this.view.setRotation(this.lastReceivedEntityDescription.rotation * DAG2RAD); 37 | }, 38 | 39 | /** 40 | * @inheritDoc 41 | */ 42 | updatePosition: function (speedFactor, gameClock, gameTick) { 43 | this.position.x = this.b2Body.m_xf.position.x * DemoBox2D.Constants.PHYSICS_SCALE; 44 | this.position.y = this.b2Body.m_xf.position.y * DemoBox2D.Constants.PHYSICS_SCALE; 45 | this.rotation = this.b2Body.GetAngle(); 46 | }, 47 | 48 | /** 49 | * @inheritDoc 50 | */ 51 | constructEntityDescription: function () { 52 | // Send the regular entity description, but also send 'radius' and a rounded version 'rotation' 53 | return DemoBox2D.Box2DEntity.superclass.constructEntityDescription.call(this) + ',' + this.radius + "," + ~~(this.rotation * RAD2DEG); 54 | }, 55 | 56 | /** 57 | * @inheritDoc 58 | */ 59 | dealloc: function () { 60 | if (this.b2Body) { 61 | // Destroy box2d body - 62 | } 63 | DemoBox2D.Box2DEntity.superclass.dealloc.call(this); 64 | }, 65 | 66 | ///// ACCESSORS 67 | /** 68 | * Set the Box2D body that represents this entity 69 | * @param aBox2dBody 70 | */ 71 | setBox2DBody: function (aBox2dBody) { 72 | this.b2Body = aBox2dBody; 73 | }, 74 | getBox2DBody: function () { 75 | return this.b2Body 76 | } 77 | }; 78 | 79 | // extend RealtimeMultiplayerGame.model.GameEntity 80 | RealtimeMultiplayerGame.extend(DemoBox2D.Box2DEntity, RealtimeMultiplayerGame.model.GameEntity, null); 81 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/traits/ChaseTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | ChaseTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This trait will cause an entity to chase a target 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | var RATE = 0.2; 16 | BubbleDots.traits.ChaseTrait = function () { 17 | BubbleDots.traits.ChaseTrait.superclass.constructor.call(this); 18 | }; 19 | 20 | BubbleDots.traits.ChaseTrait.prototype = { 21 | displayName: "ChaseTrait", // Unique string name for this Trait 22 | chaseSpeed: 0.01, 23 | radius: 40, 24 | target: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 25 | offset: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | attach: function (anEntity) { 31 | BubbleDots.traits.ChaseTrait.superclass.attach.call(this, anEntity); 32 | this.intercept(['updatePosition']); 33 | }, 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | execute: function () { 39 | RATE += 0.3; 40 | this.radius = Math.random() * 20 + 10; 41 | this.offset = new RealtimeMultiplayerGame.model.Point(Math.cos(RATE) * this.radius, Math.sin(RATE) * -this.radius); 42 | this.chaseSpeed = Math.random() * 0.02 + 0.001; 43 | BubbleDots.traits.ChaseTrait.superclass.execute.call(this); 44 | }, 45 | 46 | /** 47 | * Intercepted properties 48 | */ 49 | updatePosition: function (speedFactor, gameClock, gameTick) { 50 | var trait = this.getTraitWithName("ChaseTrait"); 51 | 52 | // Move towards the target position overtime 53 | var delta = trait.target.position.subtractClone(this.position); 54 | delta.x += trait.offset.x; 55 | delta.y += trait.offset.y; 56 | 57 | delta.multiply(trait.chaseSpeed); 58 | this.acceleration.translatePoint(delta); 59 | 60 | // Call the original handleAcceleration 61 | trait.interceptedProperties._data.updatePosition.call(this, speedFactor, gameClock, gameTick); 62 | }, 63 | 64 | ///// ACCESSORS 65 | /** 66 | * Set the target this object will follow 67 | * @param {RealtimeMultiplayerGame.model.GameEntity} aTarget 68 | */ 69 | setTarget: function (aTarget) { 70 | this.target = aTarget; 71 | } 72 | }; 73 | 74 | // Extend BaseTrait 75 | RealtimeMultiplayerGame.extend(BubbleDots.traits.ChaseTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 76 | })(); -------------------------------------------------------------------------------- /js/DemoHelloWorld/CircleEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoHelloWorld.CircleEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoHelloWorld 8 | Abstract: 9 | This is the most basic entity i could come up with for a HelloWorldDemo of RealtimeMultiplayerNodeJS 10 | Basic Usage: 11 | // Create the GameEntity 12 | var circleEntity = new DemoHelloWorld.CircleEntity( anEntityid, aClientid ); 13 | circleEntity.entityType = DemoHelloWorld.Constants.ENTITY_TYPES.GENERIC_CIRCLE; 14 | circleEntity.radius = aRadius; 15 | circleEntity.position.set( Math.random() * DemoHelloWorld.Constants.GAME_WIDTH, Math.random() * DemoHelloWorld.Constants.GAME_HEIGHT); 16 | 17 | // Place the circle and collision circle into corresponding containers 18 | this.fieldController.addEntity( circleEntity ); 19 | Version: 20 | 1.0 21 | */ 22 | (function () { 23 | DemoHelloWorld.CircleEntity = function (anEntityid, aClientid) { 24 | DemoHelloWorld.CircleEntity.superclass.constructor.call(this, anEntityid, aClientid); 25 | this.speed = Math.random(); 26 | return this; 27 | }; 28 | 29 | DemoHelloWorld.CircleEntity.prototype = { 30 | speed: 0, 31 | radius: DemoHelloWorld.Constants.ENTITY_DEFAULT_RADIUS, 32 | entityType: DemoHelloWorld.Constants.ENTITY_TYPES.CIRCLE, 33 | 34 | /** 35 | * Update the entity's view - this is only called on the clientside 36 | */ 37 | updateView: function () { 38 | if (!this.view) return; 39 | this.view.x = this.position.x - this.radius; 40 | this.view.y = this.position.y - this.radius; 41 | }, 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | updatePosition: function (speedFactor, gameClock, gameTick) { 47 | // This is where you would move your entity here 48 | // Speedfactor is a number between 0.0 and 2.0, where 1.0 means its running at perfect framerate 49 | 50 | }, 51 | 52 | /** 53 | * Deallocate memory 54 | */ 55 | dealloc: function () { 56 | DemoHelloWorld.CircleEntity.superclass.dealloc.call(this); 57 | }, 58 | 59 | /** 60 | * Append radius to our entity description created by the super class 61 | */ 62 | constructEntityDescription: function () { 63 | // Note: "~~" is just a way to round the value without the Math.round function call 64 | return DemoHelloWorld.CircleEntity.superclass.constructEntityDescription.call(this) + ',' + ~~this.radius; 65 | } 66 | }; 67 | 68 | // extend RealtimeMultiplayerGame.model.GameEntity 69 | RealtimeMultiplayerGame.extend(DemoHelloWorld.CircleEntity, RealtimeMultiplayerGame.model.GameEntity, null); 70 | })(); -------------------------------------------------------------------------------- /css/application.css: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | // ========================================== \\ 4 | || || 5 | || Your styles ! || 6 | || || 7 | \\ ========================================== // 8 | */ 9 | 10 | body{ 11 | font-family:Helvetica, Helvetica Neue, Arial, sans-serif; 12 | } 13 | 14 | a, a:active, a:visited { color: #1c1c1c; } 15 | a:hover { color: #333333; } 16 | 17 | .wrapper{ 18 | margin:auto; 19 | width:950px; 20 | } 21 | 22 | #header-container{ 23 | background-color: #333333; 24 | height:130px; 25 | border-bottom:20px solid #9dfafa; 26 | margin-bottom:50px; 27 | -webkit-box-shadow:0 40px 40px -40px #AAA; 28 | -moz-box-shadow:0 40px 40px -40px #AAA; 29 | box-shadow:0 40px 40px -40px #AAA; 30 | } 31 | 32 | #title{ 33 | font-size: 40px; 34 | color:white; 35 | padding-top:80px; 36 | float:left; 37 | } 38 | 39 | h2{ 40 | font-size: 55px; 41 | } 42 | 43 | h3{ 44 | font-size: 20px; 45 | } 46 | 47 | nav{ 48 | float:right; 49 | margin-right: -52px; 50 | margin-top:100px; 51 | } 52 | 53 | nav ul, nav ul li{ 54 | display:inline; 55 | } 56 | 57 | nav a{ 58 | padding:20px; 59 | text-decoration:none; 60 | background-color: #9dfafa; 61 | } 62 | 63 | aside{ 64 | color:white; 65 | padding:10px; 66 | float:right; 67 | height:500px; 68 | width:250px; 69 | background-color: #333333; 70 | border-bottom:20px solid #333333; 71 | margin-bottom:50px; 72 | margin-right:-50px; 73 | overflow:hidden; 74 | } 75 | 76 | #main p{ 77 | font-family:Helvetica, Helvetica Neue, Arial; 78 | width:620px; 79 | text-shadow:none; 80 | } 81 | 82 | aside p { 83 | font-size:11px; 84 | font-family:monospace; 85 | } 86 | 87 | #main header h2{ 88 | padding-bottom:30px; 89 | } 90 | 91 | /* 92 | article header{ 93 | margin-bottom:50px; 94 | padding-bottom:30px; 95 | width:700px; 96 | -webkit-box-shadow:0 45px 60px -60px #AAA; 97 | -moz-box-shadow:0 45px 60px -60px #AAA; 98 | box-shadow:0 45px 60px -60px #AAA; 99 | } 100 | */ 101 | 102 | #footer-container{ 103 | background-color: #333333; 104 | height:240px; 105 | border-top:20px solid #9dfafa; 106 | margin-top:50px; 107 | -webkit-box-shadow:0 -40px 40px -40px #AAA; 108 | -moz-box-shadow:0 -40px 40px -40px #AAA; 109 | box-shadow:0 -40px 40px -40px #AAA; 110 | } 111 | 112 | #footer-container footer{ 113 | color:white; 114 | margin-top: 10px; 115 | 116 | } 117 | 118 | .info { 119 | position:absolute; 120 | right: 0px; 121 | top:5px; 122 | background-color:white; 123 | padding:10px; 124 | margin-top:5px; 125 | } 126 | 127 | #jquery-test{ 128 | top:45px; 129 | } 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /js/BubbleDots/traits/FoodTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | FoodTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | 10 | Basic Usage: 11 | 12 | */ 13 | (function () { 14 | BubbleDots.namespace("BubbleDots.traits"); 15 | 16 | BubbleDots.traits.FoodTrait = function () { 17 | BubbleDots.traits.FoodTrait.superclass.constructor.call(this); 18 | }; 19 | 20 | BubbleDots.traits.FoodTrait.prototype = { 21 | displayName: "FoodTrait", // Unique string name for this Trait 22 | color: "3", 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | attach: function (anEntity) { 28 | BubbleDots.traits.FoodTrait.superclass.attach.call(this, anEntity); 29 | this.intercept(['onCollision', 'color']); 30 | }, 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | execute: function () { 36 | BubbleDots.traits.FoodTrait.superclass.execute.call(this); 37 | }, 38 | 39 | /** 40 | * Intercepted properties 41 | */ 42 | /** 43 | * Called when this object has collided with another 44 | * @param a Object A in the collision pair, note this may be this object 45 | * @param b Object B in the collision pair, note this may be this object 46 | * @param collisionNormal A vector describing the collision 47 | */ 48 | onCollision: function (a, b, collisionNormal) { 49 | 50 | // We're either A or B, so perform a simple check against A to figure out which of the two objects we are 51 | var me = this === a ? a : b; 52 | var them = this === a ? b : a; 53 | 54 | // me.addTrait( ) 55 | 56 | BubbleDots.lib.TWEEN.remove(me._tween); 57 | me._tween = new BubbleDots.lib.TWEEN.Tween({radius: me.radius}) 58 | .to({radius: 2}, 500) 59 | .easing(BubbleDots.lib.TWEEN.Easing.Back.EaseInOut) 60 | .onUpdate(function () { 61 | me.setRadius(~~this.radius); 62 | }) 63 | .start(); 64 | 65 | // var newRadius = Math.max( BubbleDots.traits.FoodTrait.prototype.radius, them.radius+0.1 ); 66 | // them.radius = newRadius; 67 | // them.collisionCircle.setRadius( newRadius ); 68 | me.collisionCircle.collisionGroup = 0; 69 | // me.acceleration.translatePoint( collisionNormal.multiply(-10) ); 70 | 71 | var chaseTrait = this.addTraitAndExecute(new BubbleDots.traits.ChaseTrait()); 72 | chaseTrait.setTarget(them); 73 | this.addTraitAndExecute(new BubbleDots.traits.PerlinNoiseTrait()); 74 | // me.tempColor(); 75 | } 76 | 77 | }; 78 | 79 | // Extend BaseTrait 80 | RealtimeMultiplayerGame.extend(BubbleDots.traits.FoodTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 81 | })(); -------------------------------------------------------------------------------- /js/controller/traits/KeyboardInputTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BaseTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | Traits work by effectively 'hi-jacking' properties of their attachedEntity. 10 | These properties can by functions, or non-primitive data types. 11 | 12 | Instead of creating a new trivial subclass, considering creating a trait and attaching it to that object 13 | 14 | For example to make an entity invincible for a period of time you might make a trait like this 15 | 16 | [PSUEDO CODE START] 17 | // Inside a trait subclass 18 | attach: function(anEntity) 19 | { 20 | this.callSuper(); 21 | this.intercept(['onHit', 'getShotPower']); 22 | }, 23 | 24 | onHit: function() { 25 | // Do nothing, im invincible! 26 | }, 27 | 28 | getShotStrength: function() { 29 | return 100000000; // OMGBBQ! Thats high! 30 | } 31 | [PSUEDO CODE END] 32 | 33 | Be sure to call restore before detaching the trait! 34 | 35 | Basic Usage: 36 | 37 | // Let my character be controlled by the KB 38 | if(newEntity.connectionID === this.netChannel.connectionID) { 39 | aCharacter.addTraitAndExecute( new ClientControlledTrait() ); 40 | this.clientCharacter = aCharacter; 41 | } 42 | */ 43 | (function () { 44 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.controller.traits"); 45 | 46 | RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait = function () { 47 | RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait.superclass.constructor.call(this); 48 | }; 49 | 50 | RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait.prototype = { 51 | displayName: "KeyboardInputTrait", // Unique string name for this Trait 52 | /** 53 | * Attach the trait to the host object 54 | * @param anEntity 55 | */ 56 | attach: function (anEntity) { 57 | RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait.superclass.attach.call(this, anEntity); 58 | 59 | // Intercept those two properties from the attached enitity with our own 60 | this.intercept(['constructEntityDescription', 'handleInput']); 61 | this.attachedEntity.input = new RealtimeMultiplayerGame.Input.Keyboard(); 62 | this.attachedEntity.input.attachEvents(); 63 | }, 64 | 65 | /** 66 | * Implement our own intercepted version of the methods/properties 67 | */ 68 | constructEntityDescription: function (gameTick, wantsFullUpdate) { 69 | return { 70 | entityid: this.entityid, 71 | input: this.input.constructInputBitmask() 72 | } 73 | }, 74 | 75 | // Do nothing 76 | handleInput: function (gameClock) { 77 | } 78 | }; 79 | 80 | RealtimeMultiplayerGame.extend(RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait, RealtimeMultiplayerGame.controller.traits.BaseTrait); 81 | })(); -------------------------------------------------------------------------------- /js/model/ImprovedNoise.js: -------------------------------------------------------------------------------- 1 | // http://mrl.nyu.edu/~perlin/noise/ 2 | (function () { 3 | var p = [151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 4 | 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 5 | 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 6 | 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 7 | 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 8 | 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 9 | 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 10 | 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 11 | 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 12 | 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180]; 13 | 14 | for (var i = 0; i < 256; i++) { 15 | 16 | p[256 + i] = p[i]; 17 | 18 | } 19 | 20 | function fade(t) { 21 | 22 | return t * t * t * (t * (t * 6 - 15) + 10); 23 | 24 | } 25 | 26 | function lerp(t, a, b) { 27 | 28 | return a + t * (b - a); 29 | 30 | } 31 | 32 | function grad(hash, x, y, z) { 33 | 34 | var h = hash & 15; 35 | var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; 36 | return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); 37 | 38 | } 39 | 40 | // Retrieve the namespace 41 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.model"); 42 | RealtimeMultiplayerGame.model.noise = function (x, y, z) { 43 | 44 | var floorX = ~~x, floorY = ~~y, floorZ = ~~z; 45 | 46 | var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255; 47 | 48 | x -= floorX; 49 | y -= floorY; 50 | z -= floorZ; 51 | 52 | var xMinus1 = x - 1, yMinus1 = y - 1, zMinus1 = z - 1; 53 | 54 | var u = fade(x), v = fade(y), w = fade(z); 55 | 56 | var A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; 57 | 58 | return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), 59 | grad(p[BA], xMinus1, y, z)), 60 | lerp(u, grad(p[AB], x, yMinus1, z), 61 | grad(p[BB], xMinus1, yMinus1, z))), 62 | lerp(v, lerp(u, grad(p[AA + 1], x, y, zMinus1), 63 | grad(p[BA + 1], xMinus1, y, z - 1)), 64 | lerp(u, grad(p[AB + 1], x, yMinus1, zMinus1), 65 | grad(p[BB + 1], xMinus1, yMinus1, zMinus1)))); 66 | 67 | } 68 | })(); -------------------------------------------------------------------------------- /js/model/Constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | Constants.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class contains Constants used by RealtimeMuliplayerGame 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | var clientDropWait = RealtimeMultiplayerGame.Constants.CL_DEFAULT_MAXRATE 13 | */ 14 | (function () { 15 | RealtimeMultiplayerGame.Constants = { 16 | DEBUG_SETTING: { 17 | SERVER_NETCHANNEL_DEBUG: true, 18 | CLIENT_NETCHANNEL_DEBUG: true 19 | }, 20 | 21 | SERVER_SETTING: { 22 | CLIENT_ID: 0, // If an object has a client id of zero, that means it is owned by the server 23 | SOCKET_PROTOCOL: "http", 24 | SOCKET_DOMAIN: "localhost", 25 | SOCKET_PORT: 8081, 26 | 27 | /** @return {string} */ 28 | GET_URI: function () { 29 | return RealtimeMultiplayerGame.Constants.SERVER_SETTING.SOCKET_PROTOCOL 30 | + "://" + RealtimeMultiplayerGame.Constants.SERVER_SETTING.SOCKET_DOMAIN 31 | + ":" + RealtimeMultiplayerGame.Constants.SERVER_SETTING.SOCKET_PORT; 32 | } 33 | }, 34 | 35 | CLIENT_SETTING: { 36 | INTERP: 75, // How far back to interpolate the client-rendered world 37 | FAKE_LAG: 0, // Used to simulate latency 38 | UPDATE_RATE: Math.round(1000 / 30), // How often to request a world-update from the server 39 | CMD_RATE: Math.round(1000 / 31), // How often a client can send messages to server 40 | MAX_BUFFER: 64, 41 | EXPIRED_ENTITY_CHECK_RATE: 30, // How often we clear out entities that the server says no longer exist. Lower looks better but decreases framerate 42 | MAX_UPDATE_FAILURE_COUNT: 3 // How many times we allow ourselves to fail when getting behind the server time 43 | }, 44 | 45 | CMDS: { 46 | SERVER_CONNECT: 1 << 1, // Dispatched by the server if it acknowledges a client connection 47 | SERVER_MATCH_START: 1 << 2, // Server broadcast game start 48 | SERVER_END_GAME: 1 << 3, // Server broadcast game over 49 | PLAYER_CONNECT: 1 << 4, // Initial connection to the server, not in game yet 50 | PLAYER_JOINED: 1 << 5, // Player has joined the current game 51 | PLAYER_DISCONNECT: 1 << 6, // Player has disconnected 52 | PLAYER_UPDATE: 1 << 7, // Player is sending sampled input 53 | SERVER_FULL_UPDATE: 1 << 8 // Player is sending sampled input 54 | }, 55 | 56 | // The client sends this bitmask to the server 57 | // See (Keyboard.js) 58 | INPUT_BITMASK: { 59 | UP: 1 << 0, 60 | DOWN: 1 << 1, 61 | LEFT: 1 << 2, 62 | RIGHT: 1 << 3, 63 | SPACE: 1 << 4, 64 | SHIFT: 1 << 5, 65 | TAB: 1 << 6 66 | } 67 | } 68 | })(); -------------------------------------------------------------------------------- /js/model/NetChannelMessage.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | NetChannelMessage.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | A common class for sending messages between ServerNetChannel / ClientNetChannel 10 | It is a small value-object wrapper which contains a sequence number and clientid 11 | Basic Usage: 12 | // Create the data that will live in the message 13 | var command = {}; 14 | // Fill in the data 15 | command.cmd = aCommandConstant; 16 | command.data = {x:1, y:1}; 17 | 18 | // Create the message 19 | var message = new Message(this.outgoingSequence, true, command) 20 | 21 | // Send the message (can happen later on) 22 | message.messageTime = this.realTime; // Store to determin latency 23 | 24 | (from netchannel) 25 | this.connection.send(message.encodedSelf()); 26 | }; 27 | */ 28 | (function () { 29 | 30 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.model"); 31 | 32 | /** 33 | * Creates a NetChannelMessage 34 | * @param aSequenceNumber A sequence number, used to track messages between server / client 35 | * @param isReliable A message is 'reliable' if it must be sent, for example fireweapon / disconnect. It is 'unreliable', if it can be overwritten with newer data, i.e. currentPosition 36 | * @param aPayload The message to send 37 | */ 38 | //var message = new RealtimeMultiplayerGame.model.NetChannelMessage( this.outgoingSequenceNumber, this.clientID, isReliable, aCommandConstant, payload ); 39 | RealtimeMultiplayerGame.model.NetChannelMessage = function (aSequenceNumber, aClientid, isReliable, aCommandType, aPayload) { 40 | // Info 41 | this.seq = aSequenceNumber; 42 | this.id = aClientid; // Server gives us one when we first connect to it 43 | this.cmd = aCommandType; 44 | 45 | // Data 46 | this.payload = aPayload; 47 | 48 | // State 49 | this.messageTime = -1; 50 | this.isReliable = isReliable; 51 | }; 52 | 53 | RealtimeMultiplayerGame.model.NetChannelMessage.prototype = { 54 | // This message MUST be sent if it is 'reliable' (Connect / Disconnect). 55 | // If not it can be overwritten by newer messages (for example moving is unreliable, because once it's outdates its worthless if new information exist) 56 | isReliable: false, 57 | cmd: 0, 58 | aPayload: null, 59 | seq: -1, 60 | id: -1, 61 | messageTime: -1, 62 | 63 | /** 64 | * Wrap the message with useful information before sending, optional BiSON or something can be used to compress the message 65 | */ 66 | encodeSelf: function () { 67 | if (this.id == -1) { 68 | console.log("(Message) Sending message without clientid. Note this is ok, if it's the first message to the server."); 69 | } 70 | 71 | if (this.messageTime == -1) { 72 | console.log("(Message) Sending message without messageTime. Expected result is undefined"); 73 | } 74 | 75 | return {id: this.clientid, seq: this.sequenceNumber, cmds: this.unencodedMessage, t: this.messageTime} 76 | } 77 | } 78 | })() -------------------------------------------------------------------------------- /js/DemoCircles/DemoView.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractServerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base Game controller in RealtimeMultiplayerGame on the server side. 10 | It provides things such as dropping players, and contains a ServerNetChannel 11 | Basic Usage: 12 | [This class is not instantiated! - below is an example of using this class by extending it] 13 | 14 | (function(){ 15 | MyGameClass = function() { 16 | return this; 17 | } 18 | 19 | RealtimeMultiplayerGame.extend(MyGameClass, RealtimeMultiplayerGame.AbstractServerGame, null); 20 | }; 21 | Version: 22 | 1.0 23 | */ 24 | (function () { 25 | DemoApp.DemoView = function () { 26 | this.setupCAAT(); 27 | this.setupStats(); 28 | }; 29 | 30 | DemoApp.DemoView.prototype = { 31 | // Properties 32 | caatDirector: null, // CAAT Director instance 33 | caatScene: null, // CAAT Scene instance 34 | stats: null, // Stats.js instance 35 | 36 | // Methods 37 | setupCAAT: function () { 38 | this.caatScene = new CAAT.Scene(); // Create a scene, all directors must have at least one scene - this is where all your stuff goes 39 | this.caatScene.create(); // Notice we call create when creating this, and ShapeActor below. Both are Actors 40 | this.caatScene.setFillStyle('#000000'); 41 | 42 | this.caatDirector = new CAAT.Director().initialize(DemoApp.Constants.GAME_WIDTH, DemoApp.Constants.GAME_HEIGHT); // Create the director instance 43 | this.caatDirector.addScene(this.caatScene); // Immediately add the scene once it's created 44 | 45 | // Start the render loop, with at 60FPS 46 | // this.caatDirector.loop(60); 47 | }, 48 | 49 | /** 50 | * Updates our current view, passing along the current actual time (via Date().getTime()); 51 | * @param {Number} gameClockReal The current actual time, according to the game 52 | */ 53 | update: function (gameClockReal) { 54 | var delta = gameClockReal - this.caatDirector.timeline; 55 | this.caatDirector.render(delta); 56 | this.caatDirector.timeline = gameClockReal; 57 | }, 58 | 59 | /** 60 | * Creates a Stats.js instance and adds it to the page 61 | */ 62 | setupStats: function () { 63 | var container = document.createElement('div'); 64 | this.stats = new Stats(); 65 | this.stats.domElement.style.position = 'absolute'; 66 | this.stats.domElement.style.top = '0px'; 67 | container.appendChild(this.stats.domElement); 68 | document.body.appendChild(container); 69 | }, 70 | 71 | addEntity: function (anEntityView) { 72 | console.log("Adding Entity To CAAT", anEntityView); 73 | this.caatScene.addChild(anEntityView); 74 | }, 75 | 76 | removeEntity: function (anEntityView) { 77 | console.log("Removing Entity From CAAT", anEntityView); 78 | this.caatScene.removeChild(anEntityView); 79 | }, 80 | 81 | /** 82 | * Insert the CAATDirector canvas into an HTMLElement 83 | * @param {String} id An HTMLElement id 84 | */ 85 | insertIntoHTMLElementWithId: function (id) { 86 | document.getElementById(id).appendChild(this.caatDirector.canvas); 87 | }, 88 | 89 | // Memory 90 | dealloc: function () { 91 | this.director.destroy(); 92 | } 93 | }; 94 | })(); 95 | 96 | 97 | -------------------------------------------------------------------------------- /js/core/AbstractServerGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractServerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base Game controller in RealtimeMultiplayerGame on the server side. 10 | It provides things such as dropping players, and contains a ServerNetChannel 11 | Basic Usage: 12 | [This class is not instantiated! - below is an example of using this class by extending it] 13 | 14 | (function(){ 15 | MyGameClass = function() { 16 | return this; 17 | } 18 | 19 | RealtimeMultiplayerGame.extend(MyGameClass, RealtimeMultiplayerGame.AbstractServerGame, null); 20 | }; 21 | */ 22 | (function () { 23 | 24 | RealtimeMultiplayerGame.AbstractServerGame = function () { 25 | this.intervalFramerate += 6; 26 | RealtimeMultiplayerGame.AbstractServerGame.superclass.constructor.call(this); 27 | return this; 28 | }; 29 | 30 | RealtimeMultiplayerGame.AbstractServerGame.prototype = { 31 | cmdMap: {}, // Map the CMD constants to functions 32 | nextEntityID: 0, // Incremented for everytime a new object is created 33 | 34 | // Methods 35 | setupNetChannel: function () { 36 | this.netChannel = new RealtimeMultiplayerGame.network.ServerNetChannel(this); 37 | }, 38 | 39 | /** 40 | * Map RealtimeMultiplayerGame.Constants.CMDS to functions 41 | * If ServerNetChannel does not contain a function, it will check to see if it is a special function which the delegate wants to catch 42 | * If it is set, it will call that CMD on its delegate 43 | */ 44 | setupCmdMap: function () { 45 | RealtimeMultiplayerGame.AbstractServerGame.superclass.setupCmdMap.call(this); 46 | // this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.PLAYER_UPDATE] = this.shouldUpdatePlayer; 47 | // These are left in as an example 48 | // this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.PLAYER_JOINED] = this.onPlayerJoined; 49 | // this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.PLAYER_DISCONNECT] = this.onPlayerDisconnect; 50 | }, 51 | 52 | /** 53 | * Updates the gameworld 54 | * Creates a WorldEntityDescription which it sends to NetChannel 55 | */ 56 | tick: function () { 57 | RealtimeMultiplayerGame.AbstractServerGame.superclass.tick.call(this); 58 | 59 | // Allow all entities to update their position 60 | this.fieldController.getEntities().forEach(function (key, entity) { 61 | entity.updatePosition(this.speedFactor, this.gameClock, this.gameTick); 62 | }, this); 63 | 64 | // Create a new world-entity-description, 65 | var worldEntityDescription = new RealtimeMultiplayerGame.model.WorldEntityDescription(this, this.fieldController.getEntities()); 66 | this.netChannel.tick(this.gameClock, worldEntityDescription); 67 | 68 | if (this.gameClock > this.gameDuration) { 69 | this.shouldEndGame(); 70 | } 71 | }, 72 | 73 | shouldUpdatePlayer: function (client, data) { 74 | console.log("(AbstractServerGame)::onPlayerUpdate"); 75 | }, 76 | 77 | shouldRemovePlayer: function (clientid) { 78 | this.fieldController.removePlayer(clientid); 79 | }, 80 | 81 | shouldEndGame: function () { 82 | console.log("(AbstractServerGame)::shouldEndGame"); 83 | }, 84 | 85 | 86 | ///// Accessors 87 | getNextEntityID: function () { 88 | return ++this.nextEntityID; 89 | } 90 | } 91 | 92 | 93 | // Extend RealtimeMultiplayerGame.AbstractGame 94 | RealtimeMultiplayerGame.extend(RealtimeMultiplayerGame.AbstractServerGame, RealtimeMultiplayerGame.AbstractGame, null); 95 | })() -------------------------------------------------------------------------------- /js/lib/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r5 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){function w(d,K,n){var u,f,c;for(f=0;f<30;f++)for(u=0;u<73;u++){c=(u+f*74)*4;d[c]=d[c+4];d[c+1]=d[c+5];d[c+2]=d[c+6]}for(f=0;f<30;f++){c=(73+f*74)*4;if(f'+q+" MS ("+D+"-"+E+")";r.putImageData(F,0,0);J=l;if(l> 9 | z+1E3){o=Math.round(y*1E3/(l-z));A=Math.min(A,o);B=Math.max(B,o);w(C.data,Math.min(30,30-o/100*30),"fps");g.innerHTML=''+o+" FPS ("+A+"-"+B+")";p.putImageData(C,0,0);if(x==3){s=performance.memory.usedJSHeapSize*9.54E-7;G=Math.min(G,s);H=Math.max(H,s);w(I.data,Math.min(30,30-s/2),"mem");k.innerHTML=''+Math.round(s)+" MEM ("+Math.round(G)+"-"+Math.round(H)+")";t.putImageData(I,0,0)}z=l;y=0}}}}; 10 | -------------------------------------------------------------------------------- /js/BubbleDots/entities/PlayerEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BubbleDots.CircleEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | BubbleDots 8 | Abstract: 9 | This is the base entity for the demo game 10 | Basic Usage: 11 | 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | var count = 0; 17 | BubbleDots.PlayerEntity = function (anEntityid, aClientid) { 18 | BubbleDots.PlayerEntity.superclass.constructor.call(this, anEntityid, aClientid); 19 | this.entityType = BubbleDots.Constants.ENTITY_TYPES.PLAYER_ENTITY; 20 | this.initThrust(); 21 | }; 22 | 23 | BubbleDots.PlayerEntity.prototype = { 24 | _isThrusting: false, // We need a better variable name. 25 | _thrustLevel: 0, 26 | 27 | THRUST_DECREMENT: 0.001, // How much to decrease thrust by 28 | THRUST_FORCE: 0.3, // How much force to apply every tick when applying thrust 29 | 30 | initThrust: function () { 31 | this._thrustLevel = 100.0; 32 | this._isThrusting = false; 33 | }, 34 | 35 | startThrust: function () { 36 | this._isThrusting = true; 37 | // this.velocity.y *= 0.5; 38 | }, 39 | 40 | applyThrust: function () { 41 | this._thrustLevel -= BubbleDots.PlayerEntity.prototype.THRUST_DECREMENT; 42 | if (this._thrustLevel > 0.0) { 43 | this.acceleration.y -= BubbleDots.PlayerEntity.prototype.THRUST_FORCE; 44 | } 45 | }, 46 | 47 | stopThrust: function () { 48 | this._isThrusting = false; 49 | }, 50 | 51 | /** 52 | * Update position of this entity - this is only called on the serverside 53 | * @param {Number} speedFactor A number signifying how much faster or slower we are moving than the target framerate 54 | * @param {Number} gameClock Current game time in seconds (zero based) 55 | * @param {Number} gameTick Current game tick (incrimented each frame) 56 | */ 57 | updatePosition: function (speedFactor, gameClock, gameTick) { 58 | this.handleInput(speedFactor); 59 | BubbleDots.PlayerEntity.superclass.updatePosition.call(this, speedFactor, gameClock, gameTick); 60 | }, 61 | 62 | handleInput: function (speedFactor) { 63 | var moveSpeed = 0.2; 64 | 65 | if (this.input.isLeft()) this.acceleration.x -= moveSpeed; 66 | if (this.input.isRight()) this.acceleration.x += moveSpeed; 67 | if (this.input.isDown()) this.acceleration.y += moveSpeed; 68 | 69 | // We're pressing up - apply thrust... 70 | // Call startThrust if we were not thrusting before 71 | if (this.input.isUp()) { 72 | if (!this._isThrusting) { 73 | this.startThrust(); 74 | } 75 | 76 | this.applyThrust(); 77 | } else if (this._isThrusting) { 78 | this.stopThrust(); 79 | } else { // Default behavior - increase _thrustLevel 80 | this._thrustLevel += BubbleDots.PlayerEntity.prototype.THRUST_DECREMENT * 2; 81 | this._thrustLevel = Math.min(this._thrustLevel, 100); 82 | } 83 | }, 84 | 85 | ///// ACCESSORS 86 | /** 87 | * Set the CollisionCircle for this game entity. 88 | * @param aCollisionCircle 89 | */ 90 | setCollisionCircle: function (aCollisionCircle) { 91 | BubbleDots.PlayerEntity.superclass.setCollisionCircle.call(this, aCollisionCircle); 92 | this.collisionCircle.collisionMask = 2; 93 | this.collisionCircle.collisionGroup = 1; 94 | this.collisionCircle.isFixed = true; 95 | }, 96 | setInput: function (input) { 97 | this.input = input; 98 | } 99 | }; 100 | 101 | // extend RealtimeMultiplayerGame.model.GameEntity 102 | RealtimeMultiplayerGame.extend(BubbleDots.PlayerEntity, BubbleDots.CircleEntity, null); 103 | })(); -------------------------------------------------------------------------------- /js/DemoHelloWorld/DemoView.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractServerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base Game controller in RealtimeMultiplayerGame on the server side. 10 | It provides things such as dropping players, and contains a ServerNetChannel 11 | Basic Usage: 12 | [This class is not instantiated! - below is an example of using this class by extending it] 13 | 14 | (function(){ 15 | MyGameClass = function() { 16 | return this; 17 | } 18 | 19 | RealtimeMultiplayerGame.extend(MyGameClass, RealtimeMultiplayerGame.AbstractServerGame, null); 20 | }; 21 | Version: 22 | 1.0 23 | */ 24 | (function () { 25 | DemoHelloWorld.DemoView = function () { 26 | this.setupCAAT(); 27 | this.setupStats(); 28 | }; 29 | 30 | DemoHelloWorld.DemoView.prototype = { 31 | // Properties 32 | caatDirector: null, // CAAT Director instance 33 | caatScene: null, // CAAT Scene instance 34 | stats: null, // Stats.js instance 35 | 36 | // Methods 37 | setupCAAT: function () { 38 | this.caatScene = new CAAT.Scene(); // Create a scene, all directors must have at least one scene - this is where all your stuff goes 39 | this.caatScene.create(); // Notice we call create when creating this, and ShapeActor below. Both are Actors 40 | this.caatScene.setFillStyle('#000000'); 41 | 42 | this.caatDirector = new CAAT.Director().initialize(DemoHelloWorld.Constants.GAME_WIDTH, DemoHelloWorld.Constants.GAME_HEIGHT); // Create the director instance 43 | this.caatDirector.addScene(this.caatScene); // Immediately add the scene once it's created 44 | }, 45 | 46 | /** 47 | * Updates our current view, passing along the current actual time (via Date().getTime()); 48 | * @param {Number} gameClockReal The current actual time, according to the game 49 | */ 50 | update: function (gameClockReal) { 51 | var delta = gameClockReal - this.caatDirector.timeline; 52 | this.caatDirector.render(delta); 53 | this.caatDirector.timeline = gameClockReal; 54 | }, 55 | 56 | /** 57 | * Creates a Stats.js instance and adds it to the page 58 | */ 59 | setupStats: function () { 60 | var container = document.createElement('div'); 61 | this.stats = new Stats(); 62 | this.stats.domElement.style.position = 'absolute'; 63 | this.stats.domElement.style.top = '0px'; 64 | container.appendChild(this.stats.domElement); 65 | document.body.appendChild(container); 66 | }, 67 | 68 | /** 69 | * Add an entity from the view - For now this method definitely has some symantic issues because it's being passed a concrete view instead of figuring out what the view is 70 | * @param anEntityView 71 | */ 72 | addEntity: function (anEntityView) { 73 | console.log("Adding Entity To CAAT", anEntityView); 74 | this.caatScene.addChild(anEntityView); 75 | }, 76 | 77 | /** 78 | * Remove an entity from the view - For now this method definitely has some symantic issues because it's being passed a concrete view instead of figuring out what the view is 79 | * @param anEntityView 80 | */ 81 | removeEntity: function (anEntityView) { 82 | console.log("Removing Entity From CAAT", anEntityView); 83 | this.caatScene.removeChild(anEntityView); 84 | }, 85 | 86 | /** 87 | * Insert the CAATDirector canvas into an HTMLElement 88 | * @param {String} id An HTMLElement id 89 | */ 90 | insertIntoHTMLElementWithId: function (id) { 91 | document.getElementById(id).appendChild(this.caatDirector.canvas); 92 | }, 93 | 94 | /** 95 | * @inheritDoc 96 | */ 97 | dealloc: function () { 98 | this.director.destroy(); 99 | } 100 | }; 101 | })(); 102 | 103 | 104 | -------------------------------------------------------------------------------- /js/lib/circlecollision/Circle.js: -------------------------------------------------------------------------------- 1 | /** 2 | #### ##### ##### #### ### # # ###### ###### ## ## ##### # # ######## ## # # ##### 3 | # # # # ### # # ##### ### ## ## ## # ## # # # # ## # ##### ### ### 4 | ### # # ##### #### # # # ###### ## ######### ##### ##### ##### # ## # # # # # ##### 5 | - 6 | File: 7 | PackedCircle.js 8 | Created By: 9 | Mario Gonzalez 10 | Project : 11 | None 12 | Abstract: 13 | A single packed circle. 14 | Contains a reference to it's div, and information pertaining to it state. 15 | Basic Usage: 16 | http://onedayitwillmake.com/CirclePackJS/ 17 | */ 18 | 19 | (function() { 20 | 21 | // Retrieve the namespace 22 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.modules.circlecollision"); 23 | 24 | /** 25 | * @constructor 26 | */ 27 | RealtimeMultiplayerGame.modules.circlecollision.PackedCircle = function() 28 | { 29 | this.boundsRule = RealtimeMultiplayerGame.modules.circlecollision.PackedCircle.BOUNDS_RULE_IGNORE; 30 | this.position = new RealtimeMultiplayerGame.model.Point(0,0); 31 | this.offset = new RealtimeMultiplayerGame.model.Point(0,0); 32 | this.targetPosition = new RealtimeMultiplayerGame.model.Point(0,0); 33 | return this; 34 | }; 35 | 36 | RealtimeMultiplayerGame.modules.circlecollision.PackedCircle.prototype = { 37 | id: 0, 38 | delegate: null, 39 | position: new RealtimeMultiplayerGame.model.Point(0,0), 40 | offset: new RealtimeMultiplayerGame.model.Point(0,0), // Offset from delegates position by this much 41 | radius: 0, 42 | radiusSquared: 0, 43 | 44 | targetPosition: null, // Where it wants to go 45 | targetChaseSpeed: 0.02, 46 | 47 | isFixed: false, 48 | boundsRule: 0, 49 | collisionMask: 0, 50 | collisionGroup: 0, 51 | 52 | BOUNDS_RULE_WRAP: 1, // Wrap to otherside 53 | BOUNDS_RULE_CONSTRAINT: 2, // Constrain within bounds 54 | BOUNDS_RULE_DESTROY: 4, // Destroy when it reaches the edge 55 | BOUNDS_RULE_IGNORE: 8, // Ignore when reaching bounds 56 | 57 | containsPoint: function(aPoint) 58 | { 59 | var distanceSquared = this.position.getDistanceSquared(aPoint); 60 | return distanceSquared < this.radiusSquared; 61 | }, 62 | 63 | getDistanceSquaredFromPosition: function(aPosition) 64 | { 65 | var distanceSquared = this.position.getDistanceSquared(aPosition); 66 | // if it's shorter than either radius, we intersect 67 | return distanceSquared < this.radiusSquared; 68 | }, 69 | 70 | intersects: function(aCircle) 71 | { 72 | var distanceSquared = this.position.getDistanceSquared(aCircle.position); 73 | return (distanceSquared < this.radiusSquared || distanceSquared < aCircle.radiusSquared); 74 | }, 75 | 76 | /** 77 | * ACCESSORS 78 | */ 79 | setPosition: function(aPosition) 80 | { 81 | this.position = aPosition; 82 | return this; 83 | }, 84 | 85 | setDelegate: function(aDelegate) 86 | { 87 | this.delegate = aDelegate; 88 | return this; 89 | }, 90 | 91 | setOffset: function(aPosition) 92 | { 93 | this.offset = aPosition; 94 | return this; 95 | }, 96 | 97 | setTargetPosition: function(aTargetPosition) 98 | { 99 | this.targetPosition = aTargetPosition; 100 | return this; 101 | }, 102 | 103 | setTargetChaseSpeed: function(aTargetChaseSpeed) 104 | { 105 | this.targetChaseSpeed = aTargetChaseSpeed; 106 | return this; 107 | }, 108 | 109 | setIsFixed: function(value) 110 | { 111 | this.isFixed = value; 112 | return this; 113 | }, 114 | 115 | setCollisionMask: function(aCollisionMask) 116 | { 117 | this.collisionMask = aCollisionMask; 118 | return this; 119 | }, 120 | 121 | setCollisionGroup: function(aCollisionGroup) 122 | { 123 | this.collisionGroup = aCollisionGroup; 124 | return this; 125 | }, 126 | 127 | setRadius: function(aRadius) 128 | { 129 | this.radius = aRadius; 130 | this.radiusSquared = this.radius*this.radius; 131 | return this; 132 | }, 133 | 134 | initialize : function(overrides) 135 | { 136 | if (overrides) 137 | { 138 | for (var i in overrides) 139 | { 140 | this[i] = overrides[i]; 141 | } 142 | } 143 | 144 | return this; 145 | }, 146 | 147 | dealloc: function() 148 | { 149 | this.position = null; 150 | this.offset = null; 151 | this.delegate = null; 152 | this.targetPosition = null; 153 | } 154 | }; 155 | })(); -------------------------------------------------------------------------------- /DemoBubbleDots.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | BubbleDots 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |

Fork this project on http://github.com

61 |
62 |
63 |

BubbleDots

64 | 69 |
70 |
71 |
72 | 75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 |
83 |
84 |
85 | 91 | 92 | 93 | 94 | 95 | 99 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /js/BubbleDots/lib/Tween.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | BubbleDots.namespace("BubbleDots.lib"); 3 | 4 | // Tween.js - http://github.com/sole/tween.js 5 | var TWEEN=TWEEN||function(){var a,e,c,d,f=[];return{start:function(g){c=setInterval(this.update,1E3/(g||60))},stop:function(){clearInterval(c)},add:function(g){f.push(g)},remove:function(g){a=f.indexOf(g);a!==-1&&f.splice(a,1)},update:function(){a=0;e=f.length;for(d=(new Date).getTime();a1?1:b;i=n(b);for(h in c)a[h]=e[h]+c[h]*i;l!==null&&l.call(a,i);if(b==1){m!==null&&m.call(a);k!==null&&k.start();return false}return true}};TWEEN.Easing={Linear:{},Quadratic:{},Cubic:{},Quartic:{},Quintic:{},Sinusoidal:{},Exponential:{},Circular:{},Elastic:{},Back:{},Bounce:{}};TWEEN.Easing.Linear.EaseNone=function(a){return a}; 8 | TWEEN.Easing.Quadratic.EaseIn=function(a){return a*a};TWEEN.Easing.Quadratic.EaseOut=function(a){return-a*(a-2)};TWEEN.Easing.Quadratic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a;return-0.5*(--a*(a-2)-1)};TWEEN.Easing.Cubic.EaseIn=function(a){return a*a*a};TWEEN.Easing.Cubic.EaseOut=function(a){return--a*a*a+1};TWEEN.Easing.Cubic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a;return 0.5*((a-=2)*a*a+2)};TWEEN.Easing.Quartic.EaseIn=function(a){return a*a*a*a}; 9 | TWEEN.Easing.Quartic.EaseOut=function(a){return-(--a*a*a*a-1)};TWEEN.Easing.Quartic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a;return-0.5*((a-=2)*a*a*a-2)};TWEEN.Easing.Quintic.EaseIn=function(a){return a*a*a*a*a};TWEEN.Easing.Quintic.EaseOut=function(a){return(a-=1)*a*a*a*a+1};TWEEN.Easing.Quintic.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*a*a*a;return 0.5*((a-=2)*a*a*a*a+2)};TWEEN.Easing.Sinusoidal.EaseIn=function(a){return-Math.cos(a*Math.PI/2)+1}; 10 | TWEEN.Easing.Sinusoidal.EaseOut=function(a){return Math.sin(a*Math.PI/2)};TWEEN.Easing.Sinusoidal.EaseInOut=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};TWEEN.Easing.Exponential.EaseIn=function(a){return a==0?0:Math.pow(2,10*(a-1))};TWEEN.Easing.Exponential.EaseOut=function(a){return a==1?1:-Math.pow(2,-10*a)+1};TWEEN.Easing.Exponential.EaseInOut=function(a){if(a==0)return 0;if(a==1)return 1;if((a*=2)<1)return 0.5*Math.pow(2,10*(a-1));return 0.5*(-Math.pow(2,-10*(a-1))+2)}; 11 | TWEEN.Easing.Circular.EaseIn=function(a){return-(Math.sqrt(1-a*a)-1)};TWEEN.Easing.Circular.EaseOut=function(a){return Math.sqrt(1- --a*a)};TWEEN.Easing.Circular.EaseInOut=function(a){if((a/=0.5)<1)return-0.5*(Math.sqrt(1-a*a)-1);return 0.5*(Math.sqrt(1-(a-=2)*a)+1)};TWEEN.Easing.Elastic.EaseIn=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return-(c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d))}; 12 | TWEEN.Easing.Elastic.EaseOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);return c*Math.pow(2,-10*a)*Math.sin((a-e)*2*Math.PI/d)+1}; 13 | TWEEN.Easing.Elastic.EaseInOut=function(a){var e,c=0.1,d=0.4;if(a==0)return 0;if(a==1)return 1;d||(d=0.3);if(!c||c<1){c=1;e=d/4}else e=d/(2*Math.PI)*Math.asin(1/c);if((a*=2)<1)return-0.5*c*Math.pow(2,10*(a-=1))*Math.sin((a-e)*2*Math.PI/d);return c*Math.pow(2,-10*(a-=1))*Math.sin((a-e)*2*Math.PI/d)*0.5+1};TWEEN.Easing.Back.EaseIn=function(a){return a*a*(2.70158*a-1.70158)};TWEEN.Easing.Back.EaseOut=function(a){return(a-=1)*a*(2.70158*a+1.70158)+1}; 14 | TWEEN.Easing.Back.EaseInOut=function(a){if((a*=2)<1)return 0.5*a*a*(3.5949095*a-2.5949095);return 0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)};TWEEN.Easing.Bounce.EaseIn=function(a){return 1-TWEEN.Easing.Bounce.EaseOut(1-a)};TWEEN.Easing.Bounce.EaseOut=function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375}; 15 | TWEEN.Easing.Bounce.EaseInOut=function(a){if(a<0.5)return TWEEN.Easing.Bounce.EaseIn(a*2)*0.5;return TWEEN.Easing.Bounce.EaseOut(a*2-1)*0.5+0.5}; 16 | 17 | BubbleDots.lib.TWEEN = TWEEN; 18 | })(); -------------------------------------------------------------------------------- /DemoHelloWorld.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | RealtimeMultiplayerNodeJs 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |

Fork this project on http://github.com

63 |
64 |
65 |

RealtimeMultiplayerNode.js

66 | 73 |
74 |
75 |
76 | 79 |
80 |
81 |
82 |
83 |
84 | 85 | 86 |
87 |
88 |
89 | 95 | 96 | 97 | 98 | 99 | 103 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /DemoBox2DApp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Realtime Multiplayer Node.js 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |

Fork this project on http://github.com

64 |
65 |
66 |

RealtimeMultiplayerNode.js

67 | 74 |
75 |
76 |
77 | 80 |
81 |
82 |
83 |
84 |
85 | 86 | 87 |
88 |
89 |
90 | 96 | 97 | 98 | 99 | 100 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /DemoCircles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | RealtimeMultiplayerNodeJs 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |

Fork this project on http://github.com

61 |
62 |
63 |

RealtimeMultiplayerNode.js

64 | 71 |
72 |
73 |
74 | 77 |
78 |
79 |
80 |
81 |
82 | 83 | 84 |
85 |
86 |
87 | 93 | 94 | 95 | 96 | 97 | 101 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /js/controller/traits/BaseTrait.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BaseTrait.js 4 | Created By: 5 | Mario Gonzalez 6 | Project : 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | Traits work by effectively 'hi-jacking' properties of their attachedEntity. 10 | These properties can by functions, or non-primitive data types. 11 | 12 | Instead of creating a new trivial subclass, considering creating a trait and attaching it to that object 13 | 14 | For example to make an entity invincible for a period of time you might make a trait like this 15 | 16 | [PSUEDO CODE START] 17 | // Inside a trait subclass 18 | attach: function(anEntity) 19 | { 20 | this.callSuper(); 21 | this.intercept(['onHit', 'getShotPower']); 22 | }, 23 | 24 | onHit: function() { 25 | // Do nothing, im invincible! 26 | }, 27 | 28 | getShotStrength: function() { 29 | return 100000000; // OMGBBQ! Thats high! 30 | } 31 | [PSUEDO CODE END] 32 | 33 | Be sure to call restore before detaching the trait! 34 | 35 | Basic Usage: 36 | 37 | // Let my character be controlled by the KB 38 | if(newEntity.connectionID === this.netChannel.connectionID) { 39 | aCharacter.addTraitAndExecute( new ClientControlledTrait() ); 40 | this.clientCharacter = aCharacter; 41 | } 42 | */ 43 | (function () { 44 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.controller.traits"); 45 | 46 | RealtimeMultiplayerGame.controller.traits.BaseTrait = function () { 47 | this.interceptedProperties = new SortedLookupTable(); 48 | return this; 49 | }; 50 | 51 | RealtimeMultiplayerGame.controller.traits.BaseTrait.prototype = { 52 | interceptedProperties: null, // SortedLookupTable of traits we've intercepted so they can be applied back 53 | attachedEntity: null, // Trait host 54 | detachTimeout: 0, // Store detach setTimeout 55 | displayName: "BaseTrait", // Unique string name for this Trait 56 | 57 | // If a trait can stack, then it doesn't matter if it's already attached. 58 | // If it cannot stack, it is not applied if it's currently active. 59 | // For example, you can not be frozen after being frozen. 60 | // However you can be sped up multiple times 61 | canStack: false, 62 | 63 | /** 64 | * Attach the trait to the host object 65 | * @param anEntity 66 | */ 67 | attach: function (anEntity) { 68 | this.attachedEntity = anEntity; 69 | }, 70 | 71 | /** 72 | * Execute the trait 73 | * For example if you needed to cause an animation to start when a character is 'unfrozen', this is when you would do it 74 | */ 75 | execute: function () { 76 | 77 | }, 78 | 79 | /** 80 | * Detaches a trait from an 'attachedEntity' and restores the properties 81 | */ 82 | detach: function (force) { 83 | clearTimeout(this.detachTimeout); 84 | this.restore(); 85 | 86 | this.interceptedProperties.dealloc(); 87 | this.interceptProperties = null; 88 | this.attachedEntity = null; 89 | }, 90 | 91 | /** 92 | * Detach after N milliseconds, for example freeze trait might call this to unfreeze 93 | * @param aDelay 94 | */ 95 | detachAfterDelay: function (aDelay) { 96 | var that = this; 97 | this.detachTimeout = setTimeout(function () { 98 | that.attachedEntity.removeTraitWithName(that.displayName); 99 | }, aDelay); 100 | }, 101 | 102 | /** 103 | * Intercept properties from the entity we are attached to. 104 | * For example, if we intercept handleInput, then our own 'handleInput' function gets called. 105 | * We can reset all the properties by calling, this.restore(); 106 | * @param arrayOfProperties 107 | */ 108 | intercept: function (arrayOfProperties) { 109 | var len = arrayOfProperties.length; 110 | while (len--) { 111 | var aKey = arrayOfProperties[len]; 112 | this.interceptedProperties.setObjectForKey(this.attachedEntity[aKey], aKey); 113 | this.attachedEntity[aKey] = this[aKey]; 114 | } 115 | }, 116 | 117 | /** 118 | * Restores traits that were intercepted. 119 | * Be sure to call this when removing the trait! 120 | */ 121 | restore: function () { 122 | this.interceptedProperties.forEach(function (key, aStoredProperty) { 123 | this.attachedEntity[key] = aStoredProperty; 124 | }, this); 125 | } 126 | } 127 | })(); -------------------------------------------------------------------------------- /js/lib/SortedLookupTable.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | SortedLookupTable.js 4 | Created By: 5 | (Class from http://blog.jcoglan.com/2010/10/18/i-am-a-fast-loop/) 6 | Copy->Pasted->Modified by Mario Gonzalez 7 | 8 | Project : 9 | Ogilvy Holiday Card 2010 10 | Abstract: 11 | 12 | A sorted LookupTable is a data structure that provides us a way to iterate thru objects at a speed 13 | comparable to reverse while, but also have named keys as we would if we used an object (which provides very slow iteration) 14 | It also gives us O(log n) removal of objects. 15 | Basic Usage: 16 | 17 | http://blog.jcoglan.com/2010/10/18/i-am-a-fast-loop/ 18 | */ 19 | // @getify's solution 20 | //var Set = (function() 21 | //{ 22 | // var indexOf = Array.prototype.indexOf; 23 | // 24 | // if (typeof indexOf !== 'function') 25 | // { 26 | // indexOf = function(value) 27 | // { 28 | // for (var index = 0, length = this.length; index < length; index++) 29 | // { 30 | // if (this[index] === value) 31 | // { 32 | // return index; 33 | // } 34 | // } 35 | // return -1; 36 | // }; 37 | // } 38 | // 39 | // function Set() 40 | // { 41 | // this.set = []; 42 | // } 43 | // 44 | // Set.prototype = { 45 | // 'constructor': Set, 46 | // 'put': function(value, key) 47 | // { 48 | // var index = indexOf.call(this.set, key); 49 | // if (index !== -1 && index % 2 === 0) 50 | // { 51 | // this.set.splice(index, 2); 52 | // } 53 | // this.set.push(key, value); 54 | // }, 55 | // 'get': function(key) 56 | // { 57 | // var index = indexOf.call(this.set, key); 58 | // return (index !== -1 && index % 2 === 0) ? this.set[++index] : null; 59 | // }, 60 | // 'containsKey': function(key) 61 | // { 62 | // var index = indexOf.call(this.set, key); 63 | // return (index !== -1 && index % 2 === 0); 64 | // }, 65 | // 'containsValue': function(value) 66 | // { 67 | // var index = indexOf.call(this.set, value); 68 | // return (index !== -1 && index % 2 !== 0); 69 | // }, 70 | // 'remove': function(key) 71 | // { 72 | // var index = indexOf.call(this.set, key), 73 | // value = null; 74 | // if (index !== -1 && index % 2 === 0) 75 | // { 76 | // value = this.set.splice(index, 2)[1]; 77 | // } 78 | // return value; 79 | // }, 80 | // 81 | // 'forEach': function(block, context) 82 | // { 83 | // var set = this.set, 84 | // i = this.set.length-1, 85 | // key; 86 | // 87 | // while (i > 0) 88 | // { 89 | // block.call(context, set[i - 1], set[key]); 90 | // i-=2; 91 | // } 92 | // } 93 | // }; 94 | // 95 | // return Set; 96 | //}()); 97 | 98 | (function() { 99 | /** 100 | * LookupTable 101 | */ 102 | LookupTable = function() 103 | { 104 | this._keys = []; 105 | this._data = {}; 106 | this.nextUUID = 0; 107 | }; 108 | 109 | 110 | LookupTable.prototype.setObjectForKey = function(value, key) 111 | { 112 | if (!this._data.hasOwnProperty(key)) this._keys.push(key); 113 | this._data[key] = value; 114 | 115 | return value; 116 | }; 117 | 118 | LookupTable.prototype.objectForKey = function(key) 119 | { 120 | return this._data[key]; 121 | }; 122 | 123 | LookupTable.prototype.forEach = function(block, context) 124 | { 125 | var keys = this._keys, 126 | data = this._data, 127 | i = keys.length, 128 | key; 129 | 130 | while (i--) 131 | { 132 | key = keys[i]; 133 | block.call(context, key, data[key]); 134 | } 135 | }; 136 | 137 | LookupTable.prototype.count = function() 138 | { 139 | return this._keys.length; 140 | }; 141 | 142 | LookupTable.prototype.dealloc = function() 143 | { 144 | delete this._keys; 145 | delete this._data; 146 | }; 147 | 148 | 149 | 150 | /** 151 | * Sorted LookupTable, 152 | */ 153 | SortedLookupTable = function() 154 | { 155 | LookupTable.call(this); 156 | }; 157 | 158 | SortedLookupTable.prototype = new LookupTable(); 159 | 160 | SortedLookupTable.prototype.setObjectForKey = function(value, key) 161 | { 162 | if( !this._data.hasOwnProperty( key ) ) 163 | { 164 | var index = this._indexOf(key); 165 | this._keys.splice(index, 0, key); 166 | } 167 | this._data[key] = value; 168 | 169 | return value; 170 | }; 171 | 172 | SortedLookupTable.prototype.remove = function(key) 173 | { 174 | if (!this._data.hasOwnProperty(key)) return; 175 | delete this._data[key]; 176 | var index = this._indexOf(key); 177 | this._keys.splice(index, 1); 178 | }; 179 | 180 | SortedLookupTable.prototype._indexOf = function(key) 181 | { 182 | var keys = this._keys, 183 | n = keys.length, 184 | i = 0, 185 | d = n; 186 | 187 | if (n === 0) return 0; 188 | if (key < keys[0]) return 0; 189 | if (key > keys[n - 1]) return n; 190 | 191 | while (key !== keys[i] && d > 0.5) { 192 | d = d / 2; 193 | i += (key > keys[i] ? 1: -1) * Math.round(d); 194 | if (key > keys[i - 1] && key < keys[i]) d = 0; 195 | } 196 | return i; 197 | }; 198 | 199 | return SortedLookupTable; 200 | })(); -------------------------------------------------------------------------------- /js/DemoHelloWorld/DemoServerGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoServerGame 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoHelloWorld 8 | Abstract: 9 | This is a concrete server instance of our game 10 | Basic Usage: 11 | DemoServerGame = new DemoHelloWorld.DemoServerGame(); 12 | DemoServerGame.start(); 13 | DemoServerGame.explodeEveryone(); 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | require("../model/ImprovedNoise.js"); 19 | 20 | DemoHelloWorld.DemoServerGame = function () { 21 | DemoHelloWorld.DemoServerGame.superclass.constructor.call(this); 22 | this.setGameDuration(DemoHelloWorld.Constants.GAME_DURATION); 23 | this.setupRandomField(); 24 | return this; 25 | }; 26 | 27 | DemoHelloWorld.DemoServerGame.prototype = { 28 | 29 | /** 30 | * Map RealtimeMultiplayerGame.Constants.CMDS to functions 31 | * If ServerNetChannel does not contain a function, it will check to see if it is a special function which the delegate wants to catch 32 | * If it is set, it will call that CMD on its delegate 33 | */ 34 | setupCmdMap: function () { 35 | DemoHelloWorld.DemoServerGame.superclass.setupCmdMap.call(this); 36 | this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.PLAYER_UPDATE] = this.shouldUpdatePlayer; 37 | }, 38 | 39 | /** 40 | * Randomly places some CircleEntities into game 41 | */ 42 | setupRandomField: function () { 43 | //RealtimeMultiplayerGame.model.noise(10, 10, i/total) 44 | var total = DemoHelloWorld.Constants.MAX_CIRCLES; 45 | for (var i = 0; i < total; i++) { 46 | var radius = DemoHelloWorld.Constants.ENTITY_DEFAULT_RADIUS + Math.random() * 5; 47 | this.createCircleEntity(radius, this.getNextEntityID(), RealtimeMultiplayerGame.Constants.SERVER_SETTING.CLIENT_ID); 48 | } 49 | }, 50 | 51 | /** 52 | * Helper method to create a single CircleEntity 53 | * @param {Number} aRadius 54 | * @param {Number} anEntityid 55 | * @param {Number} aClientid 56 | */ 57 | createCircleEntity: function (aRadius, anEntityid, aClientid) { 58 | 59 | // Create the GameEntity 60 | var circleEntity = new DemoHelloWorld.CircleEntity(anEntityid, aClientid); 61 | circleEntity.entityType = DemoHelloWorld.Constants.ENTITY_TYPES.CIRCLE; 62 | circleEntity.radius = aRadius; 63 | circleEntity.position.set(Math.random() * DemoHelloWorld.Constants.GAME_WIDTH, Math.random() * DemoHelloWorld.Constants.GAME_HEIGHT); 64 | 65 | // Place the circle and collision circle into corresponding containers 66 | this.fieldController.addEntity(circleEntity); 67 | return circleEntity; 68 | }, 69 | 70 | /** 71 | * Updates the game 72 | * Creates a WorldEntityDescription which it sends to NetChannel 73 | */ 74 | tick: function () { 75 | // Do some fancy stuff for our own game 76 | 77 | // Loop through each entity and move it to the left 78 | this.fieldController.getEntities().forEach(function (key, entity) { 79 | entity.position.x -= entity.speed; 80 | if (entity.position.x < 0) { // reset 81 | entity.position.x = DemoHelloWorld.Constants.GAME_WIDTH; 82 | } 83 | }, this); 84 | 85 | // Note we call superclass's implementation after we're done 86 | DemoHelloWorld.DemoServerGame.superclass.tick.call(this); 87 | }, 88 | 89 | /** 90 | * @inheritDoc 91 | */ 92 | shouldAddPlayer: function (aClientid, data) { 93 | // A player has joined the game - do something fancy 94 | this.createCircleEntity(50, this.getNextEntityID(), aClientid); 95 | }, 96 | 97 | /** 98 | * @inheritDoc 99 | */ 100 | shouldUpdatePlayer: function (aClientid, data) { 101 | var entity = this.fieldController.getEntityWithid(data.payload.entityid); 102 | entity.input.deconstructInputBitmask(data.payload.input); 103 | }, 104 | 105 | /** 106 | * @inheritDoc 107 | */ 108 | shouldRemovePlayer: function (aClientid) { 109 | DemoHelloWorld.DemoServerGame.superclass.shouldRemovePlayer.call(this, aClientid); 110 | console.log("DEMO::REMOVEPLAYER"); 111 | }, 112 | 113 | /** 114 | * @inheritDoc 115 | */ 116 | dealloc: function () { 117 | DemoHelloWorld.DemoServerGame.superclass.dealloc.call(this); 118 | } 119 | }; 120 | 121 | // extend RealtimeMultiplayerGame.AbstractServerGame 122 | RealtimeMultiplayerGame.extend(DemoHelloWorld.DemoServerGame, RealtimeMultiplayerGame.AbstractServerGame, null); 123 | })() -------------------------------------------------------------------------------- /js/core/AbstractGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base Game controller in RealtimeMultiplayerGame it provides things such as, keeping track of the current game clock, starting and stopping the game clock 10 | Basic Usage: 11 | [This class is not instantiated! - below is an example of using this class by extending it] 12 | 13 | (function(){ 14 | MyGameClass = function() { 15 | return this; 16 | } 17 | 18 | RealtimeMultiplayerGame.extend(MyGameClass, RealtimeMultiplayerGame.AbstractGame, null); 19 | }; 20 | */ 21 | (function () { 22 | RealtimeMultiplayerGame.AbstractGame = function () { 23 | this.setupNetChannel(); 24 | this.setupCmdMap(); 25 | this.fieldController = new RealtimeMultiplayerGame.Controller.FieldController(); 26 | return this; 27 | }; 28 | 29 | RealtimeMultiplayerGame.AbstractGame.prototype = { 30 | // Properties 31 | gameClockReal: 0, // Actual time via "new Date().getTime();" 32 | gameClock: 0, // Seconds since start 33 | gameTick: 0, // Ticks since start 34 | isRunning: true, 35 | speedFactor: 1, // Used to create Framerate Independent Motion (FRIM) - 1.0 means running at exactly the correct speed, 0.5 means half-framerate. (otherwise faster machines which can update themselves more accurately will have an advantage) 36 | intervalGameTick: null, // Setinterval for gametick 37 | intervalFramerate: 60, // Try to call our tick function this often, intervalFramerate, is used to determin how often to call settimeout - we can set to lower numbers for slower computers 38 | intervalTargetDelta: NaN, // this.targetDelta, milliseconds between frames. Normally it is 16ms or 60FPS. The framerate the game is designed against - used to create framerate independent motion 39 | gameDuration: Number.MAX_VALUE, // Gameduration 40 | 41 | netChannel: null, // ServerNetChannel / ClientNetChannel determined by subclass 42 | fieldController: null, // FieldController 43 | cmdMap: {}, 44 | 45 | /** 46 | * Setup the ClientNetChannel or ServerNetChannel 47 | */ 48 | setupNetChannel: function () { 49 | }, 50 | 51 | /** 52 | * setup the command mapping for the events recevied from netchannel 53 | */ 54 | setupCmdMap: function () { 55 | }, 56 | 57 | // Methods 58 | tick: function () { 59 | // Store previous time and update current 60 | var oldTime = this.gameClockReal; 61 | this.gameClockReal = new Date().getTime(); 62 | 63 | // Our clock is zero based, so if for example it says 10,000 - that means the game started 10 seconds ago 64 | var delta = this.gameClockReal - oldTime; 65 | this.gameClock += delta; 66 | this.gameTick++; 67 | 68 | // Framerate Independent Motion - 69 | // 1.0 means running at exactly the correct speed, 0.5 means half-framerate. (otherwise faster machines which can update themselves more accurately will have an advantage) 70 | this.speedFactor = delta / ( this.intervalTargetDelta ); 71 | if (this.speedFactor <= 0) this.speedFactor = 1; 72 | 73 | this.fieldController.tick(this.speedFactor, this.gameClockReal, this.gameTick); 74 | }, 75 | 76 | 77 | /** 78 | * Start/Restart the game tick 79 | */ 80 | startGameClock: function () { 81 | var that = this; 82 | this.gameClockReal = new Date().getTime(); 83 | this.intervalTargetDelta = Math.floor(1000 / this.intervalFramerate); 84 | this.intervalGameTick = setInterval(function () { 85 | that.tick() 86 | }, this.intervalTargetDelta); 87 | }, 88 | 89 | /** 90 | * Stop the game tick 91 | */ 92 | stopGameClock: function () { 93 | clearInterval(RealtimeMultiplayerGame.AbstractGame.prototype.intervalGameTick); 94 | clearTimeout(RealtimeMultiplayerGame.AbstractGame.prototype.intervalGameTick); 95 | }, 96 | 97 | setGameDuration: function () { 98 | }, 99 | 100 | // Memory 101 | dealloc: function () { 102 | if (this.netChannel) this.netChannel.dealloc(); 103 | this.netChannel = null; 104 | 105 | clearInterval(this.intervalGameTick); 106 | }, 107 | 108 | log: function () { 109 | // OVERRIDE or USE CONSOLE.LOG 110 | }, 111 | 112 | ///// Accessors 113 | getGameClock: function () { 114 | return this.gameClock; 115 | }, 116 | getGameTick: function () { 117 | return this.gameTick; 118 | } 119 | } 120 | })(); -------------------------------------------------------------------------------- /js/input/Keyboard.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.Input"); 3 | /** 4 | * A helper class to detect the current state of the controls of the game. 5 | */ 6 | RealtimeMultiplayerGame.Input.Keyboard = function () { 7 | this.keys = {'tab': false, 'shift': false, 'space': false, 'up': false, 'down': false, 'left': false, "right": false } 8 | }; 9 | 10 | RealtimeMultiplayerGame.Input.Keyboard.prototype = { 11 | keyCodes: { '16': 'shift', '32': 'space', '37': 'left', '38': 'up', '39': 'right', '40': 'down', '9': 'tab'}, 12 | keyPressed: 0, 13 | 14 | dealloc: function () { 15 | // TODO: remove keyup/keydown events 16 | }, 17 | 18 | keyDown: function (e) { 19 | if (e.keyCode in this.keyCodes) { 20 | // if we're already pressing down on the same key, then we don't want to increment 21 | // our key pressed count 22 | if (!this.keys[ this.keyCodes[ e.keyCode ] ]) { 23 | this.keyPressed++; 24 | } 25 | 26 | this.handler(e.keyCode, true); 27 | e.preventDefault(); 28 | } 29 | }, 30 | keyUp: function (e) { 31 | if (e.keyCode in this.keyCodes) { 32 | this.handler(e.keyCode, false); 33 | this.keyPressed--; 34 | e.preventDefault(); 35 | } 36 | }, 37 | 38 | /** 39 | * Attach events to the HTML element 40 | * We don't care about a time clock here, we attach events, we only want 41 | * to know if something's happened. 42 | */ 43 | attachEvents: function () { 44 | var that = this; 45 | document.addEventListener('keydown', function (e) { 46 | that.keyDown(e); 47 | }, false); 48 | document.addEventListener('keyup', function (e) { 49 | that.keyUp(e); 50 | }, false); 51 | }, 52 | 53 | isKeyPressed: function () { 54 | return this.keyPressed > 0; 55 | }, 56 | 57 | /** 58 | * Map it to something useful so we know what it is 59 | */ 60 | handler: function (keyCode, enabled) { 61 | this.keys[ this.keyCodes[ keyCode] ] = enabled; 62 | }, 63 | 64 | /** 65 | * Constructs a bitmask based on current keyboard state 66 | * @return A bitfield containing input states 67 | */ 68 | constructInputBitmask: function () { 69 | var input = 0; 70 | 71 | // Check each key 72 | if (this.keys['up']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.UP; 73 | if (this.keys['down']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.DOWN; 74 | if (this.keys['left']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.LEFT; 75 | if (this.keys['right']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.RIGHT; 76 | if (this.keys['space']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.SPACE; 77 | if (this.keys['shift']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.SHIFT; 78 | if (this.keys['tab']) input |= RealtimeMultiplayerGame.Constants.INPUT_BITMASK.TAB; 79 | 80 | return input; 81 | }, 82 | 83 | 84 | /** 85 | * Sets the 'key down' properties based on an input mask 86 | * @param inputBitmask A bitfield containing input flags 87 | */ 88 | deconstructInputBitmask: function (inputBitmask) { 89 | this.keys['up'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.UP); 90 | this.keys['down'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.DOWN); 91 | this.keys['left'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.LEFT); 92 | this.keys['right'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.RIGHT); 93 | this.keys['space'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.SPACE); 94 | this.keys['shift'] = (inputBitmask & RealtimeMultiplayerGame.Constants.INPUT_BITMASK.SHIFT); 95 | }, 96 | 97 | /** 98 | * Accessors 99 | */ 100 | // Some helper methods to find out if we're going in a specific direction 101 | isLeft: function () { 102 | return this.keys['left']; 103 | }, 104 | isUp: function () { 105 | return this.keys['up']; 106 | }, 107 | isRight: function () { 108 | return this.keys['right']; 109 | }, 110 | isDown: function () { 111 | return this.keys['down']; 112 | }, 113 | isSpace: function () { 114 | return this.keys['space']; 115 | }, 116 | isShift: function () { 117 | return this.keys['shift']; 118 | }, 119 | isTab: function () { 120 | return this.keys['tab']; 121 | } 122 | }; 123 | })(); -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | 2 | /* ==== Scroll down to find where to put your styles :) ==== */ 3 | 4 | /* HTML5 ✰ Boilerplate */ 5 | 6 | html, body, div, span, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, 9 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, figcaption, figure, 13 | footer, header, hgroup, menu, nav, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | 28 | blockquote, q { quotes: none; } 29 | blockquote:before, blockquote:after, 30 | q:before, q:after { content: ''; content: none; } 31 | ins { background-color: #ff9; color: #000; text-decoration: none; } 32 | mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } 33 | del { text-decoration: line-through; } 34 | abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } 35 | table { border-collapse: collapse; border-spacing: 0; } 36 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 37 | input, select { vertical-align: middle; } 38 | 39 | body { font:13px/1.231 sans-serif; *font-size:small; } 40 | select, input, textarea, button { font:99% sans-serif; } 41 | pre, code, kbd, samp { font-family: monospace, sans-serif; } 42 | 43 | html { overflow-y: scroll; } 44 | a:hover, a:active { outline: none; } 45 | ul, ol { margin-left: 2em; } 46 | ol { list-style-type: decimal; } 47 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } 48 | small { font-size: 85%; } 49 | strong, th { font-weight: bold; } 50 | td { vertical-align: top; } 51 | 52 | sub, sup { font-size: 75%; line-height: 0; position: relative; } 53 | sup { top: -0.5em; } 54 | sub { bottom: -0.25em; } 55 | 56 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } 57 | textarea { overflow: auto; } 58 | .ie6 legend, .ie7 legend { margin-left: -7px; } 59 | input[type="radio"] { vertical-align: text-bottom; } 60 | input[type="checkbox"] { vertical-align: bottom; } 61 | .ie7 input[type="checkbox"] { vertical-align: baseline; } 62 | .ie6 input { vertical-align: text-bottom; } 63 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } 64 | button, input, select, textarea { margin: 0; } 65 | input:valid, textarea:valid { } 66 | input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } 67 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } 68 | 69 | ::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } 70 | ::selection { background:#FF5E99; color:#fff; text-shadow: none; } 71 | a:link { -webkit-tap-highlight-color: #FF5E99; } 72 | 73 | button { width: auto; overflow: visible; } 74 | .ie7 img { -ms-interpolation-mode: bicubic; } 75 | 76 | body, select, input, textarea { color: #444; } 77 | h1, h2, h3, h4, h5, h6 { font-weight: bold; } 78 | a, a:active, a:visited { color: #607890; } 79 | a:hover { color: #036; } 80 | 81 | 82 | 83 | 84 | 85 | .ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } 86 | .hidden { display: none; visibility: hidden; } 87 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 88 | .visuallyhidden.focusable:active, 89 | .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 90 | .invisible { visibility: hidden; } 91 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 92 | .clearfix:after { clear: both; } 93 | .clearfix { zoom: 1; } 94 | 95 | 96 | @media all and (orientation:portrait) { 97 | 98 | } 99 | 100 | @media all and (orientation:landscape) { 101 | 102 | } 103 | 104 | @media screen and (max-device-width: 480px) { 105 | 106 | /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ 107 | } 108 | 109 | 110 | @media print { 111 | * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; 112 | -ms-filter: none !important; } 113 | a, a:visited { color: #444 !important; text-decoration: underline; } 114 | a[href]:after { content: " (" attr(href) ")"; } 115 | abbr[title]:after { content: " (" attr(title) ")"; } 116 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } 117 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 118 | thead { display: table-header-group; } 119 | tr, img { page-break-inside: avoid; } 120 | @page { margin: 0.5cm; } 121 | p, h2, h3 { orphans: 3; widows: 3; } 122 | h2, h3{ page-break-after: avoid; } 123 | } -------------------------------------------------------------------------------- /js/DemoCircles/CircleEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoApp.CircleEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoApp 8 | Abstract: 9 | This is the base entity for the demo game 10 | Basic Usage: 11 | 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | 17 | var nOffset = Math.random() * 2000; 18 | DemoApp.CircleEntity = function (anEntityid, aClientid) { 19 | DemoApp.CircleEntity.superclass.constructor.call(this, anEntityid, aClientid); 20 | 21 | this.setColor("FFFFFF"); 22 | this.velocity = new RealtimeMultiplayerGame.model.Point(0, 0); 23 | this.acceleration = new RealtimeMultiplayerGame.model.Point(0, 0); 24 | return this; 25 | }; 26 | 27 | DemoApp.CircleEntity.prototype = { 28 | radius: DemoApp.Constants.ENTITY_DEFAULT_RADIUS, 29 | velocity: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 30 | acceleration: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 31 | collisionCircle: null, // An instance of RealtimeMultiplayerGame.modules.circlecollision.PackedCircle 32 | entityType: DemoApp.Constants.ENTITY_TYPES.GENERIC_CIRCLE, 33 | 34 | /** 35 | * Update the entity's view - this is only called on the clientside 36 | */ 37 | updateView: function () { 38 | if (!this.view) return; 39 | this.view.x = this.position.x - this.radius; 40 | this.view.y = this.position.y - this.radius; 41 | 42 | var diameter = this.lastReceivedEntityDescription.radius * 2; 43 | this.view.setSize(diameter, diameter); 44 | this.view.setFillStyle("#" + this.lastReceivedEntityDescription.color); 45 | }, 46 | 47 | /** 48 | * Update position of this entity - this is only called on the serverside 49 | * @param {Number} speedFactor A number signifying how much faster or slower we are moving than the target framerate 50 | * @param {Number} gameClock Current game time in seconds (zero based) 51 | * @param {Number} gameTick Current game tick (incrimented each frame) 52 | */ 53 | updatePosition: function (speedFactor, gameClock, gameTick) { 54 | 55 | // Modify velocity using perlin noise 56 | var theta = 0.008; 57 | 58 | var noise = RealtimeMultiplayerGame.model.noise(nOffset + this.position.x * theta, nOffset + this.position.y * theta, gameTick * 0.003); 59 | var angle = noise * 12; 60 | var speed = 0.2; 61 | this.acceleration.x += Math.cos(angle) * speed - 0.3; 62 | this.acceleration.y -= Math.sin(angle) * speed; 63 | 64 | 65 | this.velocity.translatePoint(this.acceleration); 66 | this.velocity.limit(5); 67 | this.velocity.multiply(0.9); 68 | this.acceleration.set(0, 0); 69 | this.collisionCircle.position.translatePoint(this.velocity); 70 | this.position = this.collisionCircle.position.clone(); 71 | }, 72 | 73 | tempColor: function () { 74 | var that = this; 75 | 76 | clearTimeout(this.timeout); 77 | this.color = "FF0000"; 78 | this.timeout = setTimeout(function () { 79 | that.setColor(that.originalColor); 80 | }, 50); 81 | }, 82 | 83 | /** 84 | * Deallocate memory 85 | */ 86 | dealloc: function () { 87 | this.collisionCircle.dealloc(); 88 | this.collisionCircle = null; 89 | DemoApp.CircleEntity.superclass.dealloc.call(this); 90 | }, 91 | 92 | constructEntityDescription: function () { 93 | return DemoApp.CircleEntity.superclass.constructEntityDescription.call(this) + ',' + this.radius + ',' + this.color; 94 | }, 95 | 96 | ///// ACCESSORS 97 | /** 98 | * Set the CollisionCircle for this game entity. 99 | * @param aCollisionCircle 100 | */ 101 | setCollisionCircle: function (aCollisionCircle) { 102 | this.collisionCircle = aCollisionCircle; 103 | this.collisionCircle.setDelegate(this); 104 | this.collisionCircle.setPosition(this.position.clone()); 105 | this.collisionCircle.setRadius(this.radius); 106 | this.collisionCircle.collisionMask = 1; 107 | this.collisionCircle.collisionGroup = 1; 108 | }, 109 | getCollisionCircle: function () { 110 | return this.collisionCircle 111 | }, 112 | 113 | /** 114 | * Set the color of this entity, a property originalColor is also stored 115 | * @param aColor 116 | */ 117 | setColor: function (aColor) { 118 | if (!this.originalColor) { 119 | this.originalColor = aColor; 120 | } 121 | 122 | this.color = aColor; 123 | }, 124 | getColor: function () { 125 | return this.color 126 | }, 127 | getOriginalColor: function () { 128 | return this.originalColor 129 | } 130 | }; 131 | 132 | // extend RealtimeMultiplayerGame.model.GameEntity 133 | RealtimeMultiplayerGame.extend(DemoApp.CircleEntity, RealtimeMultiplayerGame.model.GameEntity, null); 134 | })(); -------------------------------------------------------------------------------- /js/BubbleDots/BubbleDotsClientGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoServerGame 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | BubbleDots 8 | Abstract: 9 | This is a concrete server instance of our game 10 | Basic Usage: 11 | DemoServerGame = new BubbleDots.DemoServerGame(); 12 | DemoServerGame.start(); 13 | DemoServerGame.explodeEveryone(); 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | 19 | BubbleDots.DemoClientGame = function () { 20 | BubbleDots.DemoClientGame.superclass.constructor.call(this); 21 | 22 | this.startGameClock(); 23 | return this; 24 | }; 25 | 26 | BubbleDots.DemoClientGame.prototype = { 27 | setupView: function (images) { 28 | this.view = new BubbleDots.DemoView(images); 29 | this.view.insertIntoHTMLElementWithId("gamecontainer"); 30 | 31 | BubbleDots.DemoClientGame.superclass.setupView.call(this); 32 | }, 33 | 34 | /** 35 | * @inheritDoc 36 | */ 37 | tick: function () { 38 | BubbleDots.DemoClientGame.superclass.tick.call(this); 39 | this.view.stats.update(); 40 | this.view.update(this.gameClockReal); 41 | this.view.textfield.setText("Ping: " + this.netChannel.getLatency()); 42 | }, 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | createEntityFromDesc: function (entityDesc) { 48 | 49 | // Create a new BubbleDots entity 50 | var newEntity = new BubbleDots.CircleEntity(entityDesc.entityid, entityDesc.clientid); 51 | newEntity.position.set(entityDesc.x, entityDesc.y); 52 | 53 | var entityView = this.view.createEntityView(entityDesc); 54 | newEntity.setView(entityView); 55 | 56 | this.fieldController.addEntity(newEntity); 57 | 58 | // Our own character 59 | if (entityDesc.clientid == this.netChannel.getClientid() && entityDesc.entityType & BubbleDots.Constants.ENTITY_TYPES.PLAYER_ENTITY) { 60 | this.setupClientPlayer(newEntity); 61 | this.view.setFocusCharacter(entityView); 62 | } 63 | }, 64 | 65 | /** 66 | * Called when the player that represents this user is created 67 | * @param anEntity 68 | */ 69 | setupClientPlayer: function (anEntity) { 70 | anEntity.addTraitAndExecute(new RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait()); 71 | this.clientCharacter = anEntity; 72 | }, 73 | 74 | /** 75 | * @inheritDoc 76 | */ 77 | netChannelDidConnect: function (messageData) { 78 | BubbleDots.DemoClientGame.superclass.netChannelDidConnect.call(this, messageData); 79 | BubbleDots.DemoClientGame.prototype.log("DemoClientGame: Joining Game"); 80 | this.joinGame("Player" + this.netChannel.getClientid()); // Automatically join the game with some name 81 | }, 82 | 83 | /** 84 | * @inheritDoc 85 | */ 86 | netChannelDidDisconnect: function (messageData) { 87 | BubbleDots.DemoClientGame.superclass.netChannelDidDisconnect.call(this, messageData); 88 | BubbleDots.DemoClientGame.prototype.log("DemoClientGame: netChannelDidDisconnect"); // Display disconnect 89 | }, 90 | 91 | /** 92 | * An array containing values received from the entity 93 | * @param {String} singleWorldUpdate 94 | */ 95 | parseEntityDescriptionArray: function (entityDescAsArray) { 96 | var entityDescription = {}; 97 | // It is up to the user to make sure that their objects are following a certain order 98 | // We do this because we need the performance of sending the tiniest strings possible 99 | entityDescription.entityid = entityDescAsArray[0]; 100 | entityDescription.clientid = entityDescAsArray[1]; 101 | entityDescription.entityType = +entityDescAsArray[2]; 102 | entityDescription.x = +entityDescAsArray[3]; 103 | entityDescription.y = +entityDescAsArray[4]; 104 | entityDescription.scale = +entityDescAsArray[5]; 105 | entityDescription.color = entityDescAsArray[6]; 106 | return entityDescription; 107 | }, 108 | 109 | 110 | /** 111 | * This function logs something to the right panel 112 | * @param {Object} An object in the form of: { message: ['Client', 'domReady'] } 113 | */ 114 | log: (function () { 115 | var message = function (message) { 116 | var el = document.createElement('p'); 117 | el.innerHTML = '' + esc(message) + ': '; 118 | 119 | // Log if possible 120 | console.log(message); 121 | document.getElementsByTagName('aside')[0].appendChild(el); 122 | document.getElementsByTagName('aside')[0].scrollTop = 1000000; 123 | }; 124 | 125 | var esc = function (msg) { 126 | return msg.replace(//g, '>'); 127 | }; 128 | 129 | return message; 130 | })() 131 | }; 132 | 133 | // extend RealtimeMultiplayerGame.AbstractClientGame 134 | RealtimeMultiplayerGame.extend(BubbleDots.DemoClientGame, RealtimeMultiplayerGame.AbstractClientGame, null); 135 | })(); -------------------------------------------------------------------------------- /js/DemoBox2D/DemoBox2DView.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoBox2DView.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class represents the view object in the Box2D Demo of RealtimeMultiplayerNodeJS 10 | It uses the CAAT canvas library to draw the entities 11 | Basic Usage: 12 | [Inside DemoBox2DClientGame] 13 | setupView: function() { 14 | this.view = new DemoBox2D.DemoView(); 15 | this.view.insertIntoHTMLElementWithId( "gamecontainer" ); 16 | DemoBox2D.DemoClientGame.superclass.setupView.call( this ); 17 | } 18 | Version: 19 | 1.0 20 | */ 21 | (function () { 22 | DemoBox2D.DemoView = function (aDelegate) { 23 | this.setDelegate(aDelegate); 24 | this.setupCAAT(); 25 | this.setupMouseEvents(); 26 | this.setupStats(); 27 | }; 28 | 29 | DemoBox2D.DemoView.prototype = { 30 | // Properties 31 | caatDirector: null, // CAAT Director instance 32 | caatScene: null, // CAAT Scene instance 33 | stats: null, // Stats.js instance 34 | delegate: null, // This is the object that will handle some MouseEvents we recieve from CAAT 35 | // Methods 36 | setupCAAT: function () { 37 | 38 | // Create a scene, all directors must have at least one scene - this is where all your stuff goes 39 | this.caatScene = new CAAT.Scene(); 40 | this.caatScene.create(); 41 | this.caatScene.setFillStyle('#000000'); 42 | 43 | // Create the director instance, and immediately add the scene once it's created 44 | this.caatDirector = new CAAT.Director().initialize(DemoBox2D.Constants.GAME_WIDTH, DemoBox2D.Constants.GAME_HEIGHT); 45 | this.caatDirector.addScene(this.caatScene); // 46 | }, 47 | 48 | /** 49 | * Setup MouseEvent which we will funnel to the delegate 50 | * @param delegate 51 | */ 52 | setupMouseEvents: function (delegate) { 53 | var that = this; 54 | this.caatScene.mouseDown = function (mouseEvent) { 55 | that.delegate.onViewMouseDown(mouseEvent); 56 | }; 57 | }, 58 | 59 | /** 60 | * Updates our current view, passing along the current actual time (via Date().getTime()); 61 | * @param {Number} gameClockReal The current actual time, according to the game 62 | */ 63 | update: function (gameClockReal) { 64 | var delta = gameClockReal - this.caatDirector.timeline; 65 | this.caatDirector.render(delta); 66 | this.caatDirector.timeline = gameClockReal; 67 | }, 68 | 69 | /** 70 | * Creates a Stats.js instance and adds it to the page 71 | */ 72 | setupStats: function () { 73 | var container = document.createElement('div'); 74 | this.stats = new Stats(); 75 | this.stats.domElement.style.position = 'absolute'; 76 | this.stats.domElement.style.top = '0px'; 77 | container.appendChild(this.stats.domElement); 78 | document.body.appendChild(container); 79 | }, 80 | 81 | /** 82 | * Creats / Adds an entity into canvas via CAAT 83 | * // TODO: Currently the entity is already created - however technically this is the function that should make it! 84 | * @param anEntityView 85 | */ 86 | addEntity: function (anEntityView) { 87 | this.caatScene.addChild(anEntityView); 88 | anEntityView.mouseEnabled = false; 89 | }, 90 | 91 | /** 92 | * Removes an entity via CAAT 93 | * @param anEntityView 94 | */ 95 | removeEntity: function (anEntityView) { 96 | this.caatScene.removeChild(anEntityView); 97 | }, 98 | 99 | /** 100 | * Insert the CAATDirector canvas into an HTMLElement 101 | * @param {String} id An HTMLElement id 102 | */ 103 | insertIntoHTMLElementWithId: function (id) { 104 | document.getElementById(id).appendChild(this.caatDirector.canvas); 105 | }, 106 | 107 | // Memory 108 | dealloc: function () { 109 | this.director.destroy(); 110 | }, 111 | 112 | // Accessors 113 | /** 114 | * Checks that an object contains the required methods and sets it as the delegate for this DemoBox2D.DemoView instance 115 | * @param {DemoBox2D.DemoViewDelegateProtocol} aDelegate A delegate that conforms to DemoBox2D.DemoViewDelegateProtocol 116 | */ 117 | setDelegate: function (aDelegate) { 118 | var theInterface = DemoBox2D.DemoViewDelegateProtocol; 119 | for (var member in theInterface) { 120 | if ((typeof aDelegate[member] != typeof theInterface[member])) { 121 | console.log("object failed to implement interface member " + member); 122 | return false; 123 | } 124 | } 125 | 126 | // Checks passed 127 | this.delegate = aDelegate; 128 | } 129 | }; 130 | 131 | // Override 132 | RealtimeMultiplayerGame.extend(DemoBox2D.DemoView, RealtimeMultiplayerGame.AbstractGameView, null); 133 | 134 | 135 | /** 136 | * This is the object that will handle some MouseEvents we recieve from CAAT 137 | * This is strictly specific to our Box2D demo, and is pretty bare but seems symantically correct following the structure RealtimeMultiplayerNodeJS has in place 138 | */ 139 | DemoBox2D.DemoViewDelegateProtocol = { 140 | onViewMouseDown: function (mouseEvent) { 141 | } 142 | }; 143 | })(); 144 | 145 | 146 | -------------------------------------------------------------------------------- /js/DemoCircles/DemoClientGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoServerGame 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoApp 8 | Abstract: 9 | This is a concrete server instance of our game 10 | Basic Usage: 11 | DemoServerGame = new DemoApp.DemoServerGame(); 12 | DemoServerGame.start(); 13 | DemoServerGame.explodeEveryone(); 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | 19 | DemoApp.DemoClientGame = function () { 20 | DemoApp.DemoClientGame.superclass.constructor.call(this); 21 | 22 | this.startGameClock(); 23 | return this; 24 | }; 25 | 26 | DemoApp.DemoClientGame.prototype = { 27 | setupView: function () { 28 | this.view = new DemoApp.DemoView(); 29 | this.view.insertIntoHTMLElementWithId("gamecontainer"); 30 | 31 | DemoApp.DemoClientGame.superclass.setupView.call(this); 32 | }, 33 | 34 | /** 35 | * @inheritDoc 36 | */ 37 | tick: function () { 38 | DemoApp.DemoClientGame.superclass.tick.call(this); 39 | this.view.stats.update(); 40 | this.view.update(this.gameClockReal); 41 | }, 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | createEntityFromDesc: function (entityDesc) { 47 | 48 | var diameter = entityDesc.radius * 2; 49 | 50 | // Create a view via CAAT 51 | var aCircleView = new CAAT.ShapeActor(); 52 | aCircleView.create(); 53 | aCircleView.setSize(diameter, diameter); 54 | aCircleView.setFillStyle("#" + CAAT.Color.prototype.hsvToRgb((entityDesc.entityid * 15) % 360, 40, 99).toHex()); // Random color 55 | aCircleView.setLocation(entityDesc.x, entityDesc.y); // Place in the center of the screen, use the director's width/height 56 | 57 | var newEntity = null; 58 | 59 | var isOwnedByMe = entityDesc.clientid == this.netChannel.clientid; 60 | // If this is a player entity 61 | if (entityDesc.entityType & DemoApp.Constants.ENTITY_TYPES.PLAYER_ENTITY) { 62 | newEntity = new DemoApp.PlayerEntity(entityDesc.entityid, entityDesc.clientid); 63 | 64 | // If it is a player entity and it's my player entity - attach a KeyboardInputTrait to it 65 | if (isOwnedByMe) { 66 | newEntity.addTraitAndExecute(new RealtimeMultiplayerGame.controller.traits.KeyboardInputTrait()); 67 | this.clientCharacter = newEntity; 68 | } 69 | } else { 70 | newEntity = new DemoApp.CircleEntity(entityDesc.entityid, entityDesc.clientid); 71 | } 72 | 73 | newEntity.position.set(entityDesc.x, entityDesc.y); 74 | newEntity.setView(aCircleView); 75 | 76 | this.fieldController.addEntity(newEntity); 77 | }, 78 | 79 | /** 80 | * Called by the ClientNetChannel, it sends us an array containing tightly packed values and expects us to return a meaningful object 81 | * It is left up to each game to implement this function because only the game knows what it needs to send. 82 | * However the 4 example projects in RealtimeMultiplayerNodeJS offer should be used ans examples 83 | * 84 | * @param {Array} entityDescAsArray An array of tightly packed values 85 | * @return {Object} An object which will be returned to you later on tied to a specific entity 86 | */ 87 | parseEntityDescriptionArray: function (entityDescAsArray) { 88 | var entityDescription = {}; 89 | 90 | // It is left upto each game to implement this function because only the game knows what it needs to send. 91 | // However the 4 example projects in RealtimeMultiplayerNodeJS offer this an example 92 | entityDescription.entityid = entityDescAsArray[0]; 93 | entityDescription.clientid = entityDescAsArray[1]; 94 | entityDescription.entityType = +entityDescAsArray[2]; 95 | entityDescription.x = +entityDescAsArray[3]; 96 | entityDescription.y = +entityDescAsArray[4]; 97 | entityDescription.radius = +entityDescAsArray[5]; 98 | entityDescription.color = entityDescAsArray[6]; 99 | return entityDescription; 100 | }, 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | netChannelDidConnect: function (messageData) { 106 | DemoApp.DemoClientGame.superclass.netChannelDidConnect.call(this, messageData); 107 | DemoApp.DemoClientGame.prototype.log("DemoClientGame: Joining Game"); 108 | this.joinGame("Player" + this.netChannel.getClientid()); // Automatically join the game with some name 109 | }, 110 | 111 | /** 112 | * @inheritDoc 113 | */ 114 | netChannelDidDisconnect: function (messageData) { 115 | DemoApp.DemoClientGame.superclass.netChannelDidDisconnect.call(this, messageData); 116 | DemoApp.DemoClientGame.prototype.log("DemoClientGame: netChannelDidDisconnect"); // Display disconnect 117 | }, 118 | 119 | /** 120 | * This function logs something to the right panel 121 | * @param {Object} An object in the form of: { message: ['Client', 'domReady'] } 122 | */ 123 | log: (function () { 124 | var message = function (message) { 125 | var el = document.createElement('p'); 126 | el.innerHTML = '' + esc(message) + ': '; 127 | 128 | // Log if possible 129 | console.log(message); 130 | document.getElementsByTagName('aside')[0].appendChild(el); 131 | document.getElementsByTagName('aside')[0].scrollTop = 1000000; 132 | }; 133 | 134 | var esc = function (msg) { 135 | return msg.replace(//g, '>'); 136 | }; 137 | 138 | return message; 139 | })() 140 | } 141 | 142 | // extend RealtimeMultiplayerGame.AbstractClientGame 143 | RealtimeMultiplayerGame.extend(DemoApp.DemoClientGame, RealtimeMultiplayerGame.AbstractClientGame, null); 144 | })() -------------------------------------------------------------------------------- /js/BubbleDots/entities/CircleEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | BubbleDots.CircleEntity 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | BubbleDots 8 | Abstract: 9 | This is the base entity for the demo game 10 | Basic Usage: 11 | 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | BubbleDots.CircleEntity = function (anEntityid, aClientid) { 17 | BubbleDots.CircleEntity.superclass.constructor.call(this, anEntityid, aClientid); 18 | 19 | this.entityType = BubbleDots.Constants.ENTITY_TYPES.CANDY_ENTITY; 20 | this.originalColor = null; 21 | this.velocity = new RealtimeMultiplayerGame.model.Point(0, 0); 22 | this.acceleration = new RealtimeMultiplayerGame.model.Point(0, 0); 23 | return this; 24 | }; 25 | 26 | BubbleDots.CircleEntity.prototype = { 27 | radius: BubbleDots.Constants.ENTITY_DEFAULT_RADIUS, 28 | velocity: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 29 | acceleration: RealtimeMultiplayerGame.model.Point.prototype.ZERO, 30 | collisionCircle: null, // An instance of RealtimeMultiplayerGame.modules.circlecollision.PackedCircle 31 | entityType: null, 32 | color: "2", 33 | originalColor: "2", 34 | _tween: null, 35 | 36 | // Movement properties 37 | velocityMax: 8.0, 38 | velocityDamping: 0.98, 39 | 40 | 41 | /** 42 | * Update the entity's view - this is only called on the clientside 43 | */ 44 | updateView: function () { 45 | if (!this.view) return; 46 | 47 | this.view.x = this.position.x;// - this.radius; 48 | this.view.y = this.position.y;// - this.radius; 49 | this.view.setScale(this.lastReceivedEntityDescription.scale * 0.01, this.lastReceivedEntityDescription.scale * 0.01); 50 | return; 51 | 52 | var diameter = this.lastReceivedEntityDescription.radius * 2; 53 | this.view.setSize(diameter, diameter); 54 | this.view.setFillStyle("#" + this.lastReceivedEntityDescription.color); // Random color 55 | }, 56 | 57 | /** 58 | * Update position of this entity - this is only called on the serverside 59 | * @param {Number} speedFactor A number signifying how much faster or slower we are moving than the target framerate 60 | * @param {Number} gameClock Current game time in seconds (zero based) 61 | * @param {Number} gameTick Current game tick (incrimented each frame) 62 | */ 63 | updatePosition: function (speedFactor, gameClock, gameTick) { 64 | this.handleAcceleration(speedFactor, gameClock, gameTick); 65 | }, 66 | 67 | handleAcceleration: function (speedFactor, gameClock, gameTick) { 68 | this.velocity.translatePoint(this.acceleration); 69 | // this.velocity.limit(this.velocityMax); 70 | this.velocity.multiply(this.velocityDamping); 71 | 72 | this.collisionCircle.position.translatePoint(this.velocity); 73 | this.position = this.collisionCircle.position.clone(); 74 | 75 | this.acceleration.set(0, 0); 76 | }, 77 | 78 | /** 79 | * Called when this object has collided with another 80 | * @param a Object A in the collision pair, note this may be this object 81 | * @param b Object B in the collision pair, note this may be this object 82 | * @param collisionNormal A vector describing the collision 83 | */ 84 | onCollision: function (a, b, collisionNormal) { 85 | }, 86 | 87 | tempColor: function () { 88 | var that = this; 89 | 90 | clearTimeout(this.timeout); 91 | this.color = "1"; 92 | this.timeout = setTimeout(function () { 93 | that.setColor(that.originalColor); 94 | }, 50); 95 | }, 96 | 97 | /** 98 | * Deallocate memory 99 | */ 100 | dealloc: function () { 101 | this.collisionCircle.dealloc(); 102 | this.collisionCircle = null; 103 | 104 | BubbleDots.CircleEntity.superclass.dealloc.call(this); 105 | }, 106 | 107 | constructEntityDescription: function () { 108 | var entityDesc = BubbleDots.CircleEntity.superclass.constructEntityDescription.call(this); 109 | entityDesc += ',' + ~~(this.scale * 100); 110 | entityDesc += ',' + this.color; 111 | 112 | return entityDesc; 113 | }, 114 | 115 | ///// ACCESSORS 116 | /** 117 | * Set the CollisionCircle for this game entity. 118 | * @param aCollisionCircle 119 | */ 120 | setCollisionCircle: function (aCollisionCircle) { 121 | this.collisionCircle = aCollisionCircle; 122 | this.collisionCircle.collisionMask = 1; 123 | this.collisionCircle.collisionGroup = 2; 124 | this.collisionCircle.setDelegate(this); 125 | this.collisionCircle.setPosition(this.position.clone()); 126 | this.collisionCircle.setRadius(this.radius); 127 | 128 | }, 129 | getCollisionCircle: function () { 130 | return this.collisionCircle 131 | }, 132 | 133 | /** 134 | * Set the color of this entity, a property originalColor is also stored 135 | * @param aColor 136 | */ 137 | setColor: function (aColor) { 138 | if (!this.originalColor) { 139 | this.originalColor = aColor; 140 | } 141 | 142 | this.color = aColor; 143 | }, 144 | getColor: function () { 145 | return this.color 146 | }, 147 | getOriginalColor: function () { 148 | return this.originalColor 149 | }, 150 | setRadius: function (aRadius) { 151 | this.radius = aRadius; 152 | this.collisionCircle.setRadius(this.radius); 153 | this.scale = this.radius / BubbleDots.Constants.ENTITY_DEFAULT_RADIUS; 154 | }, 155 | getRadius: function () { 156 | return this.radius; 157 | } 158 | 159 | }; 160 | 161 | // extend RealtimeMultiplayerGame.model.GameEntity 162 | RealtimeMultiplayerGame.extend(BubbleDots.CircleEntity, RealtimeMultiplayerGame.model.GameEntity, null); 163 | })(); -------------------------------------------------------------------------------- /js/DemoHelloWorld/DemoClientGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoServerGame 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoHelloWorld 8 | Abstract: 9 | This is a concrete server instance of our game 10 | Basic Usage: 11 | DemoServerGame = new DemoHelloWorld.DemoServerGame(); 12 | DemoServerGame.start(); 13 | DemoServerGame.explodeEveryone(); 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | 19 | DemoHelloWorld.DemoClientGame = function () { 20 | DemoHelloWorld.DemoClientGame.superclass.constructor.call(this); 21 | this.startGameClock(); 22 | return this; 23 | }; 24 | 25 | DemoHelloWorld.DemoClientGame.prototype = { 26 | setupView: function () { 27 | this.view = new DemoHelloWorld.DemoView(); 28 | this.view.insertIntoHTMLElementWithId("gamecontainer"); 29 | DemoHelloWorld.DemoClientGame.superclass.setupView.call(this); 30 | }, 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | tick: function () { 36 | DemoHelloWorld.DemoClientGame.superclass.tick.call(this); 37 | this.view.stats.update(); 38 | this.view.update(this.gameClockReal); 39 | }, 40 | 41 | /** 42 | * @inheritDoc 43 | */ 44 | createEntityFromDesc: function (entityDesc) { 45 | 46 | var diameter = entityDesc.radius * 2; 47 | 48 | // Create a view via CAAT 49 | var aCircleView = new CAAT.ShapeActor(); 50 | aCircleView.create(); 51 | aCircleView.setSize(diameter, diameter); 52 | aCircleView.setFillStyle("#" + CAAT.Color.prototype.hsvToRgb((entityDesc.entityid * 15) % 360, 40, 99).toHex()); // Random color 53 | aCircleView.setLocation(entityDesc.x, entityDesc.y); // Place in the center of the screen, use the director's width/height 54 | 55 | // Create the entity and add it to the fieldcontroller 56 | var newEntity = new DemoHelloWorld.CircleEntity(entityDesc.entityid, entityDesc.clientid); 57 | newEntity.position.set(entityDesc.x, entityDesc.y); 58 | newEntity.setView(aCircleView); 59 | 60 | this.fieldController.addEntity(newEntity); 61 | }, 62 | 63 | /** 64 | * Called by the ClientNetChannel, it sends us an array containing tightly packed values and expects us to return a meaningful object 65 | * It is left up to each game to implement this function because only the game knows what it needs to send. 66 | * However the 4 example projects in RealtimeMultiplayerNodeJS offer should be used ans examples 67 | * 68 | * @param {Array} entityDescAsArray An array of tightly packed values 69 | * @return {Object} An object which will be returned to you later on tied to a specific entity 70 | */ 71 | parseEntityDescriptionArray: function (entityDescAsArray) { 72 | var entityDescription = {}; 73 | 74 | // It is left upto each game to implement this function because only the game knows what it needs to send. 75 | // However the 4 example projects in RealtimeMultiplayerNodeJS offer this an example 76 | entityDescription.entityid = +entityDescAsArray[0]; 77 | entityDescription.clientid = +entityDescAsArray[1]; 78 | entityDescription.entityType = +entityDescAsArray[2]; 79 | entityDescription.x = +entityDescAsArray[3]; 80 | entityDescription.y = +entityDescAsArray[4]; 81 | entityDescription.radius = +entityDescAsArray[5]; 82 | 83 | return entityDescription; 84 | }, 85 | 86 | /** 87 | * @inheritDoc 88 | */ 89 | netChannelDidConnect: function (messageData) { 90 | DemoHelloWorld.DemoClientGame.superclass.netChannelDidConnect.call(this, messageData); 91 | this.joinGame("Player" + this.netChannel.getClientid()); // Automatically join the game with some name 92 | }, 93 | 94 | /** 95 | * @inheritDoc 96 | */ 97 | netChannelDidReceiveMessage: function (messageData) { 98 | DemoHelloWorld.DemoClientGame.superclass.netChannelDidReceiveMessage.call(this, messageData); 99 | // Do some stuff here 100 | }, 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | netChannelDidDisconnect: function (messageData) { 106 | DemoHelloWorld.DemoClientGame.superclass.netChannelDidDisconnect.call(this, messageData); 107 | // Do some stuff here 108 | }, 109 | 110 | /** 111 | * @inheritDoc 112 | */ 113 | joinGame: function (aNickname) { 114 | // This is called when the user has decided to join the game, in our HelloWorld example this happens automatically 115 | // In a regular game situation, this might happen after a user picked their chracter for example 116 | this.nickname = aNickname; 117 | // Create a 'join' message and queue it in ClientNetChannel 118 | this.netChannel.addMessageToQueue(true, RealtimeMultiplayerGame.Constants.CMDS.PLAYER_JOINED, { nickname: this.nickname }); 119 | }, 120 | 121 | /** 122 | * @inheritDoc 123 | */ 124 | log: (function () { 125 | var message = function (message) { 126 | var el = document.createElement('p'); 127 | el.innerHTML = '' + esc(message) + ': '; 128 | 129 | // Log if possible 130 | console.log(message); 131 | document.getElementsByTagName('aside')[0].appendChild(el); 132 | document.getElementsByTagName('aside')[0].scrollTop = 1000000; 133 | }; 134 | 135 | var esc = function (msg) { 136 | return msg.replace(//g, '>'); 137 | }; 138 | 139 | return message; 140 | })(), 141 | 142 | /** 143 | * Deallocate memory from your game, and let the superclass do the same 144 | */ 145 | dealloc: function () { 146 | DemoHelloWorld.DemoClientGame.superclass.dealloc.call(this); 147 | } 148 | }; 149 | 150 | // extend RealtimeMultiplayerGame.AbstractClientGame 151 | RealtimeMultiplayerGame.extend(DemoHelloWorld.DemoClientGame, RealtimeMultiplayerGame.AbstractClientGame, null); 152 | })() -------------------------------------------------------------------------------- /js/DemoBox2D/DemoBox2DClientGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoBox2DClientGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoBox2D 8 | Abstract: 9 | This is the client/browser side of the DemoBox2D app within RealtimeMultiplayerNodeJS 10 | Basic Usage: 11 | var clientGame = new DemoBox2D.DemoClientGame(); 12 | Version: 13 | 1.0 14 | */ 15 | (function () { 16 | 17 | DemoBox2D.DemoClientGame = function () { 18 | DemoBox2D.DemoClientGame.superclass.constructor.call(this); 19 | 20 | this.startGameClock(); 21 | return this; 22 | }; 23 | 24 | DemoBox2D.DemoClientGame.prototype = { 25 | setupView: function () { 26 | this.view = new DemoBox2D.DemoView(this); 27 | this.view.insertIntoHTMLElementWithId("gamecontainer"); 28 | this.view.delegate = this; 29 | DemoBox2D.DemoClientGame.superclass.setupView.call(this); 30 | }, 31 | 32 | /** 33 | * When the user clicks down, we will create a message and pass that to 34 | * @param aMouseEvent 35 | */ 36 | onViewMouseDown: function (aMouseEvent) { 37 | this.netChannel.addMessageToQueue(false, RealtimeMultiplayerGame.Constants.CMDS.PLAYER_UPDATE, { x: aMouseEvent.point.x, y: aMouseEvent.point.y }); 38 | }, 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | tick: function () { 44 | DemoBox2D.DemoClientGame.superclass.tick.call(this); 45 | this.view.stats.update(); 46 | this.view.update(this.gameClockReal); 47 | }, 48 | 49 | /** 50 | * @inheritDoc 51 | */ 52 | createEntityFromDesc: function (entityDesc) { 53 | var diameter = entityDesc.radius * DemoBox2D.Constants.PHYSICS_SCALE; 54 | console.log(entityDesc.entityType, DemoBox2D.Constants.ENTITY_TYPES.BOX); 55 | 56 | // Tell CAAT to create a circle or box depending on the info we receive 57 | var entityType = (entityDesc.entityType === DemoBox2D.Constants.ENTITY_TYPES.BOX) ? CAAT.ShapeActor.prototype.SHAPE_RECTANGLE : CAAT.ShapeActor.prototype.SHAPE_CIRCLE; 58 | 59 | // Create the entity 60 | var newEntity = new DemoBox2D.Box2DEntity(entityDesc.entityid, entityDesc.clientid); 61 | newEntity.position.set(entityDesc.x, entityDesc.y); 62 | 63 | // Create a view via CAAT 64 | var anEntityView = new CAAT.ShapeActor(); 65 | anEntityView.create(); 66 | anEntityView.setShape(entityType); 67 | anEntityView.setSize(diameter, diameter); 68 | anEntityView.setFillStyle("#" + CAAT.Color.prototype.hsvToRgb((entityDesc.entityid * 15) % 360, 40, 99).toHex()); // Random color 69 | anEntityView.setLocation(entityDesc.x, entityDesc.y); // Place in the center of the screen, use the director's width/height 70 | 71 | // Set the view 72 | newEntity.setView(anEntityView); 73 | 74 | // Add to the fieldcontroller 75 | this.fieldController.addEntity(newEntity); 76 | }, 77 | 78 | /** 79 | * Called by the ClientNetChannel, it sends us an array containing tightly packed values and expects us to return a meaningful object 80 | * It is left up to each game to implement this function because only the game knows what it needs to send. 81 | * However the 4 example projects in RealtimeMultiplayerNodeJS offer should be used ans examples 82 | * 83 | * @param {Array} entityDescAsArray An array of tightly packed values 84 | * @return {Object} An object which will be returned to you later on tied to a specific entity 85 | */ 86 | parseEntityDescriptionArray: function (entityDescAsArray) { 87 | var entityDescription = {}; 88 | 89 | // It is left upto each game to implement this function because only the game knows what it needs to send. 90 | // However the 4 example projects in RealtimeMultiplayerNodeJS offer this an example 91 | entityDescription.entityid = +entityDescAsArray[0]; 92 | entityDescription.clientid = +entityDescAsArray[1]; 93 | entityDescription.entityType = +entityDescAsArray[2]; 94 | entityDescription.x = +entityDescAsArray[3]; 95 | entityDescription.y = +entityDescAsArray[4]; 96 | entityDescription.radius = +entityDescAsArray[5]; 97 | entityDescription.rotation = +entityDescAsArray[6]; 98 | 99 | return entityDescription; 100 | }, 101 | 102 | /** 103 | * @inheritDoc 104 | */ 105 | netChannelDidConnect: function (messageData) { 106 | DemoBox2D.DemoClientGame.superclass.netChannelDidConnect.call(this, messageData); 107 | DemoBox2D.DemoClientGame.prototype.log("DemoClientGame: Joining Game"); 108 | this.joinGame("Player" + this.netChannel.getClientid()); // Automatically join the game with some name 109 | }, 110 | 111 | /** 112 | * @inheritDoc 113 | */ 114 | netChannelDidDisconnect: function (messageData) { 115 | DemoBox2D.DemoClientGame.superclass.netChannelDidDisconnect.call(this, messageData); 116 | DemoBox2D.DemoClientGame.prototype.log("DemoClientGame: netChannelDidDisconnect"); // Display disconnect 117 | }, 118 | 119 | /** 120 | * This function logs something to the right panel 121 | * @param {Object} An object in the form of: { message: ['Client', 'domReady'] } 122 | */ 123 | log: (function () { 124 | var message = function (message) { 125 | var el = document.createElement('p'); 126 | el.innerHTML = '' + esc(message) + ': '; 127 | 128 | // Log if possible 129 | console.log(message); 130 | document.getElementsByTagName('aside')[0].appendChild(el); 131 | document.getElementsByTagName('aside')[0].scrollTop = 1000000; 132 | }; 133 | 134 | var esc = function (msg) { 135 | return msg.replace(//g, '>'); 136 | }; 137 | 138 | return message; 139 | })() 140 | }; 141 | 142 | // extend RealtimeMultiplayerGame.AbstractClientGame 143 | RealtimeMultiplayerGame.extend(DemoBox2D.DemoClientGame, RealtimeMultiplayerGame.AbstractClientGame, null); 144 | })(); -------------------------------------------------------------------------------- /js/DemoCircles/DemoServerGame.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | DemoServerGame 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | DemoApp 8 | Abstract: 9 | This is a concrete server instance of our game 10 | Basic Usage: 11 | DemoServerGame = new DemoApp.DemoServerGame(); 12 | DemoServerGame.start(); 13 | DemoServerGame.explodeEveryone(); 14 | Version: 15 | 1.0 16 | */ 17 | (function () { 18 | require("../model/ImprovedNoise.js"); 19 | 20 | DemoApp.DemoServerGame = function () { 21 | DemoApp.DemoServerGame.superclass.constructor.call(this); 22 | 23 | this.setGameDuration(DemoApp.Constants.GAME_DURATION); 24 | this.setupCollisionManager(); 25 | this.setupRandomField(); 26 | return this; 27 | }; 28 | 29 | DemoApp.DemoServerGame.prototype = { 30 | collisionManager: null, 31 | 32 | /** 33 | * Map RealtimeMultiplayerGame.Constants.CMDS to functions 34 | * If ServerNetChannel does not contain a function, it will check to see if it is a special function which the delegate wants to catch 35 | * If it is set, it will call that CMD on its delegate 36 | */ 37 | setupCmdMap: function () { 38 | DemoApp.DemoServerGame.superclass.setupCmdMap.call(this); 39 | this.cmdMap[RealtimeMultiplayerGame.Constants.CMDS.PLAYER_UPDATE] = this.shouldUpdatePlayer; 40 | }, 41 | 42 | setupCollisionManager: function () { 43 | // Collision simulation 44 | this.collisionManager = new RealtimeMultiplayerGame.modules.circlecollision.CircleManager(); 45 | this.collisionManager.setBounds(0, 0, DemoApp.Constants.GAME_WIDTH, DemoApp.Constants.GAME_HEIGHT); 46 | this.collisionManager.setNumberOfCollisionPasses(2); 47 | this.collisionManager.setNumberOfTargetingPasses(0); 48 | this.collisionManager.setCallback(this.onCollisionManagerCollision, this); 49 | }, 50 | 51 | /** 52 | * Called when the collision manager detects a collision 53 | */ 54 | onCollisionManagerCollision: function (ci, cj, v) { 55 | ci.delegate.tempColor(); 56 | cj.delegate.tempColor(); 57 | }, 58 | 59 | /** 60 | * Randomly places some CircleEntities into game 61 | */ 62 | setupRandomField: function () { 63 | //RealtimeMultiplayerGame.model.noise(10, 10, i/total) 64 | var total = DemoApp.Constants.MAX_CIRCLES; 65 | for (var i = 0; i < total; i++) { 66 | var radius = DemoApp.Constants.ENTITY_DEFAULT_RADIUS + Math.random() * 5; 67 | this.createCircleEntity(radius, this.getNextEntityID(), RealtimeMultiplayerGame.Constants.SERVER_SETTING.CLIENT_ID); 68 | } 69 | }, 70 | 71 | /** 72 | * Helper method to create a single CircleEntity 73 | * @param {Number} aRadius 74 | * @param {Number} anEntityid 75 | * @param {Number} aClientid 76 | */ 77 | createCircleEntity: function (aRadius, anEntityid, aClientid) { 78 | // Create a randomly sized circle, that will represent this entity in the collision manager 79 | var collisionCircle = new RealtimeMultiplayerGame.modules.circlecollision.PackedCircle(); 80 | collisionCircle.setRadius(aRadius); 81 | 82 | // Create the GameEntity 83 | var circleEntity = new DemoApp.CircleEntity(anEntityid, aClientid); 84 | circleEntity.radius = aRadius; 85 | circleEntity.position.set(Math.random() * DemoApp.Constants.GAME_WIDTH, Math.random() * DemoApp.Constants.GAME_HEIGHT); 86 | circleEntity.setCollisionCircle(collisionCircle); 87 | 88 | // Place the circle and collision circle into corresponding containers 89 | this.collisionManager.addCircle(circleEntity.getCollisionCircle()); 90 | this.fieldController.addEntity(circleEntity); 91 | 92 | circleEntity.entityType = DemoApp.Constants.ENTITY_TYPES.GENERIC_CIRCLE; 93 | return circleEntity; 94 | }, 95 | 96 | createPlayerEntity: function (anEntityid, aClientid) { 97 | // Create the GameEntity 98 | var playerEntity = new DemoApp.PlayerEntity(anEntityid, aClientid); 99 | playerEntity.position.set(Math.random() * DemoApp.Constants.GAME_WIDTH, Math.random() * DemoApp.Constants.GAME_HEIGHT); 100 | 101 | var collisionCircle = new RealtimeMultiplayerGame.modules.circlecollision.PackedCircle(); 102 | collisionCircle.setRadius(playerEntity.radius); 103 | 104 | playerEntity.setInput(new RealtimeMultiplayerGame.Input.Keyboard()); 105 | playerEntity.setCollisionCircle(collisionCircle); 106 | 107 | // place player on field 108 | this.collisionManager.addCircle(playerEntity.getCollisionCircle()); 109 | this.fieldController.addPlayer(playerEntity); 110 | 111 | return playerEntity; 112 | }, 113 | 114 | /** 115 | * Updates the game 116 | * Creates a WorldEntityDescription which it sends to NetChannel 117 | */ 118 | tick: function () { 119 | // Use both the BOUNDARY_WRAP_X flag, and the BOUNDARY_CONSTRAIN_Y flags as the rule 120 | var boundsRule = RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_X; 121 | boundsRule |= RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_Y; 122 | 123 | this.collisionManager.handleBoundaryForAllCircles(boundsRule); 124 | this.collisionManager.handleCollisions(); 125 | 126 | // Note we call superclass's implementation after we're done 127 | DemoApp.DemoServerGame.superclass.tick.call(this); 128 | }, 129 | 130 | shouldAddPlayer: function (aClientid, data) { 131 | this.createPlayerEntity(this.getNextEntityID(), aClientid); 132 | }, 133 | 134 | shouldUpdatePlayer: function (aClientid, data) { 135 | var entity = this.fieldController.getEntityWithid(data.payload.entityid); 136 | entity.input.deconstructInputBitmask(data.payload.input); 137 | }, 138 | 139 | shouldRemovePlayer: function (aClientid) { 140 | DemoApp.DemoServerGame.superclass.shouldRemovePlayer.call(this, aClientid); 141 | console.log("DEMO::REMOVEPLAYER"); 142 | } 143 | }; 144 | 145 | // extend RealtimeMultiplayerGame.AbstractServerGame 146 | RealtimeMultiplayerGame.extend(DemoApp.DemoServerGame, RealtimeMultiplayerGame.AbstractServerGame, null); 147 | })(); -------------------------------------------------------------------------------- /js/model/GameEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | GameEntity.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base GameEntity class in RealtimeMultiplayerGame, it contains a position rotation, health 10 | Basic Usage: 11 | 12 | var badGuy = new RealtimeMultiplayerGame.GameEntity(); 13 | badGuy.position.x += 1; 14 | */ 15 | (function () { 16 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.model"); 17 | 18 | RealtimeMultiplayerGame.model.GameEntity = function (anEntityid, aClientid) { 19 | this.clientid = aClientid; 20 | this.entityid = anEntityid; 21 | this.traits = []; 22 | this.position = new RealtimeMultiplayerGame.model.Point(0, 0); 23 | return this; 24 | }; 25 | 26 | RealtimeMultiplayerGame.model.GameEntity.prototype = { 27 | // Connection info 28 | clientid: -1, // Owner of this object 29 | entityid: -1, // UUID for this entity 30 | entityType: -1, // A special interger representing the entityType sent via along with other network info 31 | position: RealtimeMultiplayerGame.model.Point.prototype.ZERO, // Current position of this entity 32 | rotation: 0, 33 | traits: null, // An array of our traits, in reverse added order 34 | view: null, 35 | lastReceivedEntityDescription: null, // The last received entity description (set by renderAtTime) 36 | 37 | /** 38 | * Update the view's position 39 | */ 40 | updateView: function () { 41 | // OVERRIDE 42 | }, 43 | 44 | /** 45 | * Updates the position of this GameEntity based on it's movement properties (velocity, acceleration, damping) 46 | * @param {Number} speedFactor A number signifying how much faster or slower we are moving than the target framerate 47 | * @param {Number} gameClock Current game time in seconds (zero based) 48 | * @param {Number} gameTick Current game tick (incrimented each frame) 49 | */ 50 | updatePosition: function (speedFactor, gameClock, gameTick) { 51 | // OVERRIDE 52 | }, 53 | 54 | /** 55 | * Construct an entity description for this object, it is essentually a CSV so you have to know how to read it on the receiving end 56 | * @param wantsFullUpdate If true, certain things that are only sent when changed are always sent 57 | */ 58 | constructEntityDescription: function (gameTick, wantsFullUpdate) { 59 | // Note: "~~" is just a way to round the value without the Math.round function call 60 | var returnString = this.entityid; 61 | returnString += "," + this.clientid; 62 | returnString += "," + this.entityType; 63 | returnString += "," + ~~this.position.x; 64 | returnString += "," + ~~this.position.y; 65 | 66 | return returnString; 67 | }, 68 | 69 | ////// TRAIT SUPPORT 70 | /** 71 | * Adds and attaches a trait (already created), to this entity. 72 | * The trait is only attached if we do not already have one of the same type attached, or don't care (aTrait.canStack = true) 73 | * @param {RealtimeMultiplayerGame.controller.traits.BaseTrait} aTrait A BaseTrait instance 74 | * @return {Boolean} Whether the trait was added 75 | */ 76 | addTrait: function (aTrait) { 77 | // Check if we already have this trait, if we do - make sure the trait allows stacking 78 | var existingVersionOfTrait = this.getTraitWithName(aTrait.displayName); 79 | if (existingVersionOfTrait && !existingVersionOfTrait.canStack) { 80 | return false; 81 | } 82 | 83 | // Remove existing version 84 | if (existingVersionOfTrait) { 85 | this.removeTraitWithName(aTrait.displayName); 86 | } 87 | 88 | 89 | this.traits.push(aTrait); 90 | aTrait.attach(this); 91 | 92 | return aTrait; 93 | }, 94 | 95 | /** 96 | * Calls addTrait and executes it immediately 97 | * @param aTrait 98 | */ 99 | addTraitAndExecute: function (aTrait) { 100 | var wasAdded = this.addTrait(aTrait); 101 | if (wasAdded) { 102 | aTrait.execute(); 103 | return aTrait; 104 | } 105 | 106 | return null; 107 | }, 108 | 109 | /** 110 | * Removes a trait with a matching .displayName property 111 | * @param aTraitName 112 | */ 113 | removeTraitWithName: function (aTraitName) { 114 | var len = this.traits.length; 115 | var removedTraits = null; 116 | for (var i = 0; i < len; ++i) { 117 | if (this.traits[i].displayName === aTraitName) { 118 | removedTraits = this.traits.splice(i, 1); 119 | break; 120 | } 121 | } 122 | 123 | // Detach removed traits 124 | if (removedTraits) { 125 | i = removedTraits.length; 126 | while (i--) { 127 | removedTraits[i].detach(); 128 | } 129 | } 130 | }, 131 | 132 | /** 133 | * Removes all traits contained in this entity 134 | */ 135 | removeAllTraits: function () { 136 | var i = this.traits.length; 137 | while (i--) { 138 | this.traits[i].detach(); 139 | } 140 | 141 | this.traits = []; 142 | }, 143 | 144 | ///// MEMORY 145 | dealloc: function () { 146 | this.position = null; 147 | this.removeAllTraits(); 148 | this.traits = null; 149 | }, 150 | 151 | ////// ACCESSORS 152 | setView: function (aView) { 153 | this.view = aView; 154 | }, 155 | getView: function () { 156 | return this.view; 157 | }, 158 | /** 159 | * Returns a trait with a matching .displayName property 160 | * @param aTraitName 161 | */ 162 | getTraitWithName: function (aTraitName) { 163 | var len = this.traits.length; 164 | var trait = null; 165 | for (var i = 0; i < len; ++i) { 166 | if (this.traits[i].displayName === aTraitName) { 167 | trait = this.traits[i]; 168 | break; 169 | } 170 | } 171 | return trait; 172 | } 173 | } 174 | })(); -------------------------------------------------------------------------------- /js/lib/dd_belatedpng.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DD_belatedPNG: Adds IE6 support: PNG images for CSS background-image and HTML . 3 | * Author: Drew Diller 4 | * Email: drew.diller@gmail.com 5 | * URL: http://www.dillerdesign.com/experiment/DD_belatedPNG/ 6 | * Version: 0.0.8a 7 | * Licensed under the MIT License: http://dillerdesign.com/experiment/DD_belatedPNG/#license 8 | * 9 | * Example usage: 10 | * DD_belatedPNG.fix('.png_bg'); // argument is a CSS selector 11 | * DD_belatedPNG.fixPng( someNode ); // argument is an HTMLDomElement 12 | **/ 13 | var DD_belatedPNG={ns:"DD_belatedPNG",imgSize:{},delay:10,nodesFixed:0,createVmlNameSpace:function(){if(document.namespaces&&!document.namespaces[this.ns]){document.namespaces.add(this.ns,"urn:schemas-microsoft-com:vml")}},createVmlStyleSheet:function(){var b,a;b=document.createElement("style");b.setAttribute("media","screen");document.documentElement.firstChild.insertBefore(b,document.documentElement.firstChild.firstChild);if(b.styleSheet){b=b.styleSheet;b.addRule(this.ns+"\\:*","{behavior:url(#default#VML)}");b.addRule(this.ns+"\\:shape","position:absolute;");b.addRule("img."+this.ns+"_sizeFinder","behavior:none; border:none; position:absolute; z-index:-1; top:-10000px; visibility:hidden;");this.screenStyleSheet=b;a=document.createElement("style");a.setAttribute("media","print");document.documentElement.firstChild.insertBefore(a,document.documentElement.firstChild.firstChild);a=a.styleSheet;a.addRule(this.ns+"\\:*","{display: none !important;}");a.addRule("img."+this.ns+"_sizeFinder","{display: none !important;}")}},readPropertyChange:function(){var b,c,a;b=event.srcElement;if(!b.vmlInitiated){return}if(event.propertyName.search("background")!=-1||event.propertyName.search("border")!=-1){DD_belatedPNG.applyVML(b)}if(event.propertyName=="style.display"){c=(b.currentStyle.display=="none")?"none":"block";for(a in b.vml){if(b.vml.hasOwnProperty(a)){b.vml[a].shape.style.display=c}}}if(event.propertyName.search("filter")!=-1){DD_belatedPNG.vmlOpacity(b)}},vmlOpacity:function(b){if(b.currentStyle.filter.search("lpha")!=-1){var a=b.currentStyle.filter;a=parseInt(a.substring(a.lastIndexOf("=")+1,a.lastIndexOf(")")),10)/100;b.vml.color.shape.style.filter=b.currentStyle.filter;b.vml.image.fill.opacity=a}},handlePseudoHover:function(a){setTimeout(function(){DD_belatedPNG.applyVML(a)},1)},fix:function(a){if(this.screenStyleSheet){var c,b;c=a.split(",");for(b=0;bn.H){i.B=n.H}d.vml.image.shape.style.clip="rect("+i.T+"px "+(i.R+a)+"px "+i.B+"px "+(i.L+a)+"px)"}else{d.vml.image.shape.style.clip="rect("+f.T+"px "+f.R+"px "+f.B+"px "+f.L+"px)"}},figurePercentage:function(d,c,f,a){var b,e;e=true;b=(f=="X");switch(a){case"left":case"top":d[f]=0;break;case"center":d[f]=0.5;break;case"right":case"bottom":d[f]=1;break;default:if(a.search("%")!=-1){d[f]=parseInt(a,10)/100}else{e=false}}d[f]=Math.ceil(e?((c[b?"W":"H"]*d[f])-(c[b?"w":"h"]*d[f])):parseInt(a,10));if(d[f]%2===0){d[f]++}return d[f]},fixPng:function(c){c.style.behavior="none";var g,b,f,a,d;if(c.nodeName=="BODY"||c.nodeName=="TD"||c.nodeName=="TR"){return}c.isImg=false;if(c.nodeName=="IMG"){if(c.src.toLowerCase().search(/\.png$/)!=-1){c.isImg=true;c.style.visibility="hidden"}else{return}}else{if(c.currentStyle.backgroundImage.toLowerCase().search(".png")==-1){return}}g=DD_belatedPNG;c.vml={color:{},image:{}};b={shape:{},fill:{}};for(a in c.vml){if(c.vml.hasOwnProperty(a)){for(d in b){if(b.hasOwnProperty(d)){f=g.ns+":"+d;c.vml[a][d]=document.createElement(f)}}c.vml[a].shape.stroked=false;c.vml[a].shape.appendChild(c.vml[a].fill);c.parentNode.insertBefore(c.vml[a].shape,c)}}c.vml.image.shape.fillcolor="none";c.vml.image.fill.type="tile";c.vml.color.fill.on=false;g.attachHandlers(c);g.giveLayout(c);g.giveLayout(c.offsetParent);c.vmlInitiated=true;g.applyVML(c)}};try{document.execCommand("BackgroundImageCache",false,true)}catch(r){}DD_belatedPNG.createVmlNameSpace();DD_belatedPNG.createVmlStyleSheet(); -------------------------------------------------------------------------------- /js/BubbleDots/BubbleDotsView.js: -------------------------------------------------------------------------------- 1 | /** 2 | File: 3 | AbstractServerGame.js 4 | Created By: 5 | Mario Gonzalez 6 | Project: 7 | RealtimeMultiplayerNodeJS 8 | Abstract: 9 | This class is the base Game controller in RealtimeMultiplayerGame on the server side. 10 | It provides things such as dropping players, and contains a ServerNetChannel 11 | Basic Usage: 12 | [This class is not instantiated! - below is an example of using this class by extending it] 13 | 14 | (function(){ 15 | MyGameClass = function() { 16 | return this; 17 | } 18 | 19 | RealtimeMultiplayerGame.extend(MyGameClass, RealtimeMultiplayerGame.AbstractServerGame, null); 20 | }; 21 | Version: 22 | 1.0 23 | */ 24 | (function () { 25 | BubbleDots.DemoView = function () { 26 | this.setupCAAT(); 27 | this.setupStats(); 28 | }; 29 | 30 | BubbleDots.DemoView.prototype = { 31 | // Properties 32 | caatDirector: null, // CAAT Director instance 33 | caatScene: null, // CAAT Scene instance 34 | caatRoot: null, 35 | focusCharacter: null, // The 'camera' will follow this player 36 | stats: null, // Stats.js instance 37 | textfield: null, // CAAT text 38 | 39 | // Methods 40 | setupCAAT: function () { 41 | this.caatScene = new CAAT.Scene(); // Create a scene, all directors must have at least one scene - this is where all your stuff goes 42 | this.caatScene.create(); // Notice we call create when creating this, and ShapeActor below. Both are Actors 43 | this.caatScene.setFillStyle('#323232'); 44 | 45 | this.caatDirector = new CAAT.Director().initialize(BubbleDots.Constants.GAME_WIDTH, BubbleDots.Constants.GAME_HEIGHT); // Create the director instance 46 | this.caatDirector.addScene(this.caatScene); // Immediately add the scene once it's created 47 | this.caatDirector.setImagesCache(BubbleDots.IMAGE_CACHE); 48 | 49 | 50 | this.caatRoot = new CAAT.ActorContainer() 51 | .setBounds(0, 0, this.caatScene.width, this.caatScene.height) 52 | .create() 53 | .enableEvents(false); 54 | this.caatScene.addChild(this.caatRoot); 55 | 56 | this.setupTextfield(); 57 | this.createGround(); 58 | }, 59 | 60 | setupTextfield: function () { 61 | // Create a textfield 62 | this.textfield = new CAAT.TextActor(); 63 | this.textfield.setFont("12px sans-serif"); 64 | this.textfield.textAlign = "left"; 65 | this.textfield.textBaseline = "top"; 66 | this.textfield.calcTextSize(this.caatDirector); 67 | this.textfield.setSize(this.textfield.textWidth, this.textfield.textHeight); 68 | this.textfield.create(); 69 | this.textfield.fillStyle = "#EEEEEE"; 70 | this.textfield.setLocation(10, 10); 71 | this.caatScene.addChild(this.textfield); 72 | }, 73 | 74 | /** 75 | * Updates our current view, passing along the current actual time (via Date().getTime()); 76 | * @param {Number} gameClockReal The current actual time, according to the game 77 | */ 78 | update: function (gameClockReal) { 79 | var delta = gameClockReal - this.caatDirector.timeline; 80 | 81 | if (this.focusCharacter) { 82 | this.followFocusCharacter(); 83 | } 84 | 85 | this.caatDirector.render(delta); 86 | this.caatDirector.timeline = gameClockReal; 87 | }, 88 | 89 | followFocusCharacter: function () { 90 | var camSpeed = 0.1; 91 | var targetX = -this.focusCharacter.x + this.caatScene.width / 2 - 100; 92 | var targetY = -this.focusCharacter.y + this.caatScene.height / 2 + 50; 93 | this.caatRoot.x -= (this.caatRoot.x - targetX) * camSpeed; 94 | this.caatRoot.y -= (this.caatRoot.y - targetY) * camSpeed * 2; 95 | }, 96 | 97 | /** 98 | * Creates a Stats.js instance and adds it to the page 99 | */ 100 | setupStats: function () { 101 | var container = document.createElement('div'); 102 | this.stats = new Stats(); 103 | this.stats.domElement.style.position = 'absolute'; 104 | this.stats.domElement.style.top = '0px'; 105 | container.appendChild(this.stats.domElement); 106 | document.body.appendChild(container); 107 | }, 108 | 109 | addEntity: function (anEntityView) { 110 | // console.log( "Adding Entity To CAAT", anEntityView ); 111 | this.caatRoot.addChild(anEntityView); 112 | }, 113 | 114 | removeEntity: function (anEntityView) { 115 | console.log("Removing Entity From CAAT", anEntityView); 116 | this.caatRoot.removeChild(anEntityView); 117 | }, 118 | 119 | /** 120 | * Create a view for an entity in CAAT using the entity description 121 | * @param {Object} entityDesc An object containing properties for this entity, sent from the server 122 | */ 123 | createEntityView: function (entityDesc) { 124 | // Retrieve the image from caatDirector (stored in the preloading sequence in script.js) 125 | var imageName = "particle" + entityDesc.color; 126 | var imageRef = this.caatDirector.getImage(imageName); 127 | var caatImage = new CAAT.CompoundImage() 128 | .initialize(imageRef, 1, 1); 129 | 130 | // Create the actor using the image 131 | var actor = this.CAATSprite = new CAAT.SpriteActor() 132 | .create() 133 | .setSpriteImage(caatImage) 134 | .setLocation(entityDesc.x, entityDesc.y); 135 | 136 | return actor; 137 | }, 138 | 139 | createGround: function () { 140 | // Retrieve the image from caatDirector (stored in the preloading sequence in script.js) 141 | var imageRef = this.caatDirector.getImage("ground"); 142 | var caatImage = new CAAT.CompoundImage() 143 | .initialize(imageRef, 1, 1); 144 | 145 | for (var i = 0; i < 10; ++i) { 146 | // Create the actor using the image 147 | var actor = this.CAATSprite = new CAAT.SpriteActor() 148 | .create() 149 | .setSpriteImage(caatImage) 150 | .setLocation(i * caatImage.width, 470); 151 | 152 | this.caatRoot.addChild(actor); 153 | } 154 | 155 | return actor; 156 | }, 157 | 158 | /** 159 | * Insert the CAATDirector canvas into an HTMLElement 160 | * @param {String} id An HTMLElement id 161 | */ 162 | insertIntoHTMLElementWithId: function (id) { 163 | document.getElementById(id).appendChild(this.caatDirector.canvas); 164 | }, 165 | 166 | // Memory 167 | dealloc: function () { 168 | this.director.destroy(); 169 | }, 170 | 171 | setFocusCharacter: function (entity) { 172 | this.focusCharacter = entity; 173 | } 174 | }; 175 | })(); 176 | 177 | 178 | -------------------------------------------------------------------------------- /js/controller/FieldController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.Controller"); 3 | 4 | RealtimeMultiplayerGame.Controller.FieldController = function () { 5 | this.entities = new SortedLookupTable(); 6 | this.players = new SortedLookupTable(); 7 | }; 8 | 9 | RealtimeMultiplayerGame.Controller.FieldController.prototype = { 10 | entities: null, // A SortedLookupTable for all entities 11 | players: null, // A SortedLookupTable for players only, stored using client.getClientid() 12 | 13 | /** 14 | * Update all entities 15 | * @param {Number} speedFactor A number signifying how much faster or slower we are moving than the target framerate 16 | * @param {Number} gameClock Current game time in seconds (zero based) 17 | * @param {Number} gameTick Current game tick (incrimented each frame) 18 | */ 19 | tick: function (speedFactor, gameClock, gameTick) { 20 | // DO SOME STUFF 21 | }, 22 | 23 | /** 24 | * Internal function. Adds an entity to our collection, and adds it to the view if we have one 25 | * @param anEntity An entity to add, should already be created and contain a unique entityid 26 | */ 27 | addEntity: function (anEntity) { 28 | this.entities.setObjectForKey(anEntity, anEntity.entityid); 29 | 30 | // If we have a view, then add the player to it 31 | if (this.view) { 32 | this.view.addEntity(anEntity.getView()); 33 | } 34 | 35 | }, 36 | 37 | /** 38 | * Updates the entity based on new information (called by AbstractClientGame::renderAtTime) 39 | * @param {int} entityid entityid we want to update 40 | * @param {RealtimeMultiplayerGame.model.Point} newPosition position 41 | * @param {Number} newRotation rotation 42 | * @param {Object} newEntityDescription The full contents of the the snapshots newEntityDescription 43 | */ 44 | updateEntity: function (entityid, newPosition, newRotation, newEntityDescription) { 45 | var entity = this.entities.objectForKey(entityid); 46 | 47 | if (entity != null) { 48 | entity.position.x = newPosition.x; 49 | entity.position.y = newPosition.y; 50 | entity.rotation = newRotation; 51 | entity.lastReceivedEntityDescription = newEntityDescription; 52 | } else { 53 | console.log("(FieldController)::updateEntity - Error: Cannot find entity with entityid", entityid); 54 | } 55 | }, 56 | 57 | ///// Memory 58 | 59 | addPlayer: function (aPlayerEntity) { 60 | this.addEntity(aPlayerEntity); 61 | this.players.setObjectForKey(aPlayerEntity, aPlayerEntity.clientid); 62 | }, 63 | 64 | /** 65 | * Remove a player. 66 | * Does player stuff, then calls removeEntity. 67 | * @param clientid ConnectionID of the player who jumped out of the game 68 | */ 69 | removePlayer: function (clientid) { 70 | var player = this.players.objectForKey(clientid); 71 | if (!player) { 72 | console.log("(FieldController), No 'Character' with clientid " + clientid + " ignoring..."); 73 | return; 74 | } 75 | 76 | this.removeEntity(player.entityid); 77 | this.players.remove(player.clientid); 78 | }, 79 | 80 | 81 | /** 82 | * Removes an entity by it's ID 83 | * @param entityid 84 | */ 85 | removeEntity: function (entityid) { 86 | var entity = this.entities.objectForKey(entityid); 87 | 88 | if (this.view) 89 | this.view.removeEntity(entity.view); 90 | 91 | entity.dealloc(); 92 | this.entities.remove(entityid); 93 | }, 94 | 95 | /** 96 | * Checks an array of "active entities", against the existing ones. 97 | * It's used to remove entities that expired in between two updates 98 | * @param activeEntities 99 | */ 100 | removeExpiredEntities: function (activeEntities) { 101 | var entityKeysArray = this.entities._keys; 102 | var i = entityKeysArray.length; 103 | var key; 104 | var totalRemoved = 0; 105 | 106 | while (i--) { 107 | key = entityKeysArray[i]; 108 | 109 | // This entity is still active. Move along. 110 | if (activeEntities[key]) 111 | continue; 112 | 113 | // This entity is not active, check if it belongs to the server 114 | var entity = this.entities.objectForKey(key); 115 | var isPlayer = this.players.objectForKey(entity.clientid) != null; 116 | 117 | 118 | // Remove special way if player (which calls removeEntity on itself as well), or just remove it as an entity 119 | if (isPlayer) { 120 | this.removePlayer(entity.clientid); 121 | } else { 122 | this.removeEntity(entity.entityid); 123 | } 124 | 125 | totalRemoved++; 126 | } 127 | 128 | }, 129 | 130 | dealloc: function () { 131 | this.players.forEach(function (key, entity) { 132 | this.removePlayer(entity.clientid); 133 | }, this); 134 | this.players.dealloc(); 135 | this.players = null; 136 | 137 | this.entities.forEach(function (key, entity) { 138 | this.removeEntity(entity.entityid); 139 | }, this); 140 | this.entities.dealloc(); 141 | this.entities = null; 142 | 143 | 144 | this.view = null; 145 | }, 146 | 147 | ///// Accessors 148 | // Will be called on client side 149 | setView: function (aView) { 150 | var theInterface = RealtimeMultiplayerGame.Controller.FieldControllerViewProtocol; 151 | for (var member in theInterface) { 152 | if ((typeof aView[member] != typeof theInterface[member])) { 153 | console.log("object failed to implement interface member " + member); 154 | return false; 155 | } 156 | } 157 | 158 | // Checks passed 159 | this.view = aView; 160 | }, 161 | getView: function () { 162 | return this.view 163 | }, 164 | getEntities: function () { 165 | return this.entities 166 | }, 167 | getPlayers: function () { 168 | return this.players; 169 | }, 170 | getEntityWithid: function (anEntityid) { 171 | return this.entities.objectForKey(anEntityid); 172 | }, 173 | getPlayerWithid: function (aClientid) { 174 | return this.players.objectForKey(aClientid); 175 | } 176 | }; 177 | 178 | /** 179 | * Required methods for the FieldControllerView delegate 180 | */ 181 | RealtimeMultiplayerGame.Controller.FieldControllerViewProtocol = { 182 | addEntity: function (anEntityView) { 183 | }, 184 | dealloc: function () { 185 | } 186 | } 187 | })(); --------------------------------------------------------------------------------