From 9c374d141f0e15a30f3ca0be19dc9cea7ba16d9b Mon Sep 17 00:00:00 2001 From: Jake Ben-Tovim Date: Sun, 1 May 2016 20:16:41 -0400 Subject: [PATCH] Begin prep for abilities and UI, broke up code a little, ported window-manager Added window-manager module that was used in Leap of Faith and Lumina repositories. Turns out the entire module seems to only use Victor for the x,y data storage object and not for any vector math (which makes sense with a UI engine), so it now uses a simple Vector(x, y) from the utilities (no longer relying on Victor). Set up ability enum and internal scrolling between them in the client. InputManager was useless so set it up to accept and re-emit eventual abilityChange events so clients can listen for them to know the other user's current ability and display it in the interface. --- client/css/style.css | 5 + client/kinesthesia.js | 55 +- client/utilities.js | 125 ++++ client/window-manager.js | 1169 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/InputManager.js | 47 +- src/game.js | 2 +- src/views/base.jade | 2 +- src/views/game.jade | 3 + 9 files changed, 1357 insertions(+), 52 deletions(-) create mode 100644 client/utilities.js create mode 100644 client/window-manager.js diff --git a/client/css/style.css b/client/css/style.css index 8a5603c..6171171 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -239,6 +239,11 @@ canvas { background-size: cover; } +#UI { + z-index: 1; + position: absolute; +} + #canvasContainer { width: 100%; text-align: center; diff --git a/client/kinesthesia.js b/client/kinesthesia.js index 34bc8ba..e3161bd 100644 --- a/client/kinesthesia.js +++ b/client/kinesthesia.js @@ -1,7 +1,10 @@ "use strict"; +// if game exists use the existing copy +// else create a new object literal +var game = game || {}; // IIFE for entire game -(function() { +game.main = (function() { // web socket to use var socket; @@ -10,11 +13,25 @@ var socket; var canvas; var canvasPos; var ctx; +var windowManager = game.windowManager; // reference to the engine's window manager // game/server feedback box var msgBox; //{ GAME VARS +// abilities the player can switch between +var ABILITIES = { + CANNONBALL: { + name: "Cannonball" + }, + GRENADE: { + name: "Grenade" + }, + GRAVITY_WELL: { + name: "Gravity Well" + } +}; + // size constants var playerRad = 4; var gemRad = 15; @@ -39,7 +56,7 @@ var player = { x: -100, y: -100 }, - grabbed: 0, + currentAbility: ABILITIES['CANNONBALL'], score: 0 }; @@ -51,17 +68,12 @@ var opponent = { x: -100, y: -100 }, - grabbed: 0, + currentAbility: ABILITIES['CANNONBALL'], score: 0 }; //} GAME VARS -// FUNCTION: clamp value between min and max -function clamp(value, min, max) { - return Math.max(min, Math.min(value, max)); -} - // FUNCTION: connect socket and setup callbacks function setupSocket() { @@ -78,8 +90,6 @@ function setupSocket() { socket.emit("sendId", { id: userdata._id }); }); - - // Listens for notifaction from the server that we're the host of the game socket.on("notifyHost", function() { IS_HOST = true; @@ -217,6 +227,7 @@ function initializeGame() { // Sets up matter world and input callbacks for using abilities initializeMatter(); initializeInput(); + setupUI(); // The host starts up the world and begins an update loop if (IS_HOST) { @@ -270,6 +281,9 @@ function initializeMatter() { function initializeInput() { // setup canvas mouse down behavior canvas.addEventListener('mousedown', function(e) { + e.stopPropagation(); + e.preventDefault(); + player.pos.x = clamp(e.x - canvasPos.left, 0, canvas.width); player.pos.y = clamp(e.y - canvasPos.top, 0, canvas.height); @@ -287,6 +301,7 @@ function initializeInput() { return; } + // prep the new body var newBody = Bodies.circle(lastClick.x, lastClick.y, gemRad); var vel = { x: Math.min(25, Math.abs(lastClick.x - player.pos.x)) * Math.sign(lastClick.x - player.pos.x), @@ -296,6 +311,25 @@ function initializeInput() { Body.setVelocity(newBody, vel); add(newBody); }); + + // scrolling to change between abilities + document.addEventListener('wheel', function(e) { + // scroll down - next ability + if (e.deltaY > 0) { + player.currentAbility = next(ABILITIES, player.currentAbility); + } + // scroll up - previous + else { + player.currentAbility = previous(ABILITIES, player.currentAbility); + } + }); +} + +// INITIAL UI SETUP +function setupUI() { + windowManager.makeUI("abilityHUD", 0, 0, 100, 50); + windowManager.makeText("abilityHUD", "username", 5, 5, canvas.width/10, canvas.height/5, userdata.username, "12pt 'Roboto'", "white"); + windowManager.toggleUI("abilityHUD"); } // INITAL GAME SETUP: sets up starting world objects @@ -385,6 +419,7 @@ function emitBodies() { function update() { // emit player position socket.emit("update", {pos: player.pos}); + game.windowManager.updateAndDraw([]); // request next frame requestAnimationFrame(update); diff --git a/client/utilities.js b/client/utilities.js new file mode 100644 index 0000000..ccf3ae6 --- /dev/null +++ b/client/utilities.js @@ -0,0 +1,125 @@ +"use strict"; + +var KEY = { // "enum" equating keycodes to names (e.g. keycode 32 = spacebar) + TAB: 9, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ONE: 49, + TWO: 50, + THREE: 51, + FOUR: 52, + FIVE: 53, + SIX: 54, + A: 65, + C: 67, + D: 68, + E: 69, + H: 72, + P: 80, + Q: 81, + R: 82, + S: 83, + W: 87, + X: 88, + Z: 90 +}; + +// get mouse pos on canvas +function getMouse(e){ + var mouse = {position: {}} + mouse.position = Vector(e.pageX - e.target.offsetLeft, e.pageY - e.target.offsetTop); + return mouse; +}; + +// returns random within a range +function rand(min, max) { + return Math.random() * (max - min) + min; +}; + +// returns a value that is constrained between min and max (inclusive) +function clamp(val, min, max){ + return Math.max(min, Math.min(max, val)); +}; + +// fills a text with correct CSS and cleans up after itself +function fillText(ctx, string, x, y, css, color) { + ctx.save(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.font = css; + ctx.fillStyle = color; + ctx.fillText(string, x, y); + ctx.restore(); +}; + +// fills a text with correct CSS and cleans up after itself +function fillTextAligned(ctx, string, x, y, css, color) { + ctx.save(); + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.font = css; + ctx.fillStyle = color; + ctx.fillText(string, x, y); + ctx.restore(); +}; + + // activate fullscreen +function requestFullscreen(element) { + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.mozRequestFullscreen) { + element.mozRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } + // no response if unsupported +}; + +// This gives Array a randomElement() method +Array.prototype.randomElement = function(){ + return this[Math.floor(Math.random() * this.length)]; +} + +// HELPER: gets next key in a JSON object +// from StackOverflow: http://stackoverflow.com/questions/12505598/get-next-key-value-pair-in-an-object +function next(db, key) { + // sort through and find the key after + var found = false; + //console.log(key); + for(var k in db){ + //console.log(k); + if (found) { return db[k]; } + if (db[k] == key) { found = true; } + } + + // if we reach here and found is true, then we found our key but it was the last one + // so we'll grab the first one + if (found) for (var k in db) return db[k]; +} + +// HELPER: gets the previous key in a JSON object +function previous(db, key) { + // sort through and find the key after + var last = undefined; + for(var k in db){ + if (db[k] == key) { if (last) return db[last]; } + last = k; + } + + // if we reach here, then we found our key but it was when last wasn't set + // this means our key was first, so return the last one + return db[last]; +}; + +// Helper: a basic x/y vector constructor for use in utilities/window manager +function Vector(x, y) { + return { + x: x, + y: y + }; +} diff --git a/client/window-manager.js b/client/window-manager.js new file mode 100644 index 0000000..7ff0838 --- /dev/null +++ b/client/window-manager.js @@ -0,0 +1,1169 @@ + // window-manager.js +"use strict"; +// if game exists use the existing copy +// else create a new object literal +var game = game || {}; + +game.windowManager = (function(){ + console.log("Loaded window-manager.js module"); + var canvas; // reference to game's canvas + var ctx; // 2D canvas context + + var uiElements = []; // UI elements on the screen + // FUNCTION: find named object in array + uiElements.find = function(name){ + for(var i=0; i < this.length; i++){ + if(this[i].name == name){return this[i]}; + }; + }; + + // FUNCTION: initalize canvas variables for window manager + function init() { + canvas = document.querySelector("#UI"); + ctx = canvas.getContext("2d"); + + canvas.addEventListener("mousedown", checkMouse); // click event to check mouse on UI + canvas.addEventListener("touchstart", checkMouse); // tap event to check touch on UI + + updateAndDraw(); + } + + // FUNCTION: update and draw window + function updateAndDraw(trackers){ + for(var i=0; i < uiElements.length; i++){ + uiElements[i].updateAndDraw(trackers); + } + } + + // FUNCTION: check clicks on UI + function checkMouse(e){ + var mouse = getMouse(e); // mouse position + var elem; // UI element + var but; // button + var clicked = false; + + // check if any UI elements were clicked + for(var i=0; i < uiElements.length; i++){ + elem = uiElements[i]; + //console.log("Element bounds: " + elem.position.x + ", " + elem.position.y + ", " + (elem.position.x + elem.size.x) + ", " + (elem.position.y + elem.size.y)); + if(mouse.position.x >= elem.position.x && mouse.position.x <= (elem.position.x + elem.bounds.x) && mouse.position.y >= elem.position.y && mouse.position.y <= (elem.position.y + elem.bounds.y) && elem.isActive){ + clicked = true; + // check if any buttons were clicked inside the clicked element + for(var j=0; j < elem.subElements.length; j++){ + but = elem.subElements[j]; + // attempt to call the element's click event if it has one (meaning it's a button) + if (but.onClick != undefined && mouse.position.x >= elem.position.x + but.offset.x && mouse.position.x <= elem.position.x + but.offset.x + but.bounds.x && mouse.position.y >= elem.position.y + but.offset.y && mouse.position.y <= elem.position.y + but.offset.y + but.bounds.y && but.isActive){ + but.onClick(); + break; + } + } + break; + } + } + //console.log(clicked); + return clicked; + } + + // FUNCTION: make new UI object + function makeUI(name, xPos, yPos, width, height) { + uiElements.push(new UI(name, xPos, yPos, width, height)); + } + + // FUNCTION: make a new button + function makeButton(uiName, butName, offsetX, offsetY, width, height, clickEvent){ + uiElements.find(uiName).subElements.push(new Button(uiName, butName, offsetX, offsetY, width, height, clickEvent)); + } + + // FUNCTION: make a new bar + function makeBar(uiName, barName, offsetX, offsetY, width, height, tgtVar, tgtMax, tgtMin){ + uiElements.find(uiName).subElements.push(new Bar(uiName, barName, offsetX, offsetY, width, height, tgtVar, tgtMax, tgtMin)); + } + + // FUNCTION: make a new text box + function makeText(uiName, textName, offsetX, offsetY, width, height, string, css, color){ + uiElements.find(uiName).subElements.push(new Text(uiName, textName, offsetX, offsetY, width, height, string, css, color)); + } + + // FUNCTION: make a new image + function makeImage(uiName, imageName, offsetX, offsetY, width, height, image){ + uiElements.find(uiName).subElements.push(new ImageBox(uiName, imageName, offsetX, offsetY, width, height, image)); + } + + // FUNCTION: modify UI variables + function modifyUI(uiName, varName, args){ + var elem = uiElements.find(uiName); + switch(varName){ + case("all"): + elem.setName(args.name); + elem.setPosition(args.xPos, args,yPos); + elem.setBounds(args.width, args.height); + elem.setBorder(args.color, args.width); + elem.setFill(args.color); + elem.setImage(args.image); + for(var i=0; i < this.subElements.length; i++){ + subElements[i].updatePosition(); + } + break; + case("name"): + elem.setName(args.name); + break; + case("position"): + elem.setPosition(args.xPos, args,yPos); + for(var i=0; i < this.subElements.length; i++){ + subElements[i].updatePosition(); + } + break; + case("bounds"): + elem.setBounds(args.width, args.height); + break; + case("border"): + elem.setBorder(args.color, args.width); + break; + case("fill"): + elem.setFill(args.color); + break; + case("image"): + elem.setImage(args.image); + break; + } + } + + // FUNCTION: toggle UI + function toggleUI(name){ + uiElements.find(name).toggleActive(); + } + + // FUNCTION: forcibly deactivates UI element + function deactivateUI(name){ + if (name === "all") + for (var i = 0; i < uiElements.length; ++i) + uiElements[i].deactivate(); + else + uiElements.find(name).deactivate(); + }; + + // FUNCTION: forcibly activate UI element + function activateUI(name){ + if (name === "all") + for (var i = 0; i < uiElements.length; ++i) + uiElements[i].activate(); + else + uiElements.find(name).activate(); + }; + + // FUNCTION: toggle whether UI pauses game when active + function toggleUIPausing(name){ + uiElements.find(name).togglePause(); + } + + // FUNCTION: forcibly activate UI pausing + function activateUIPausing(name){ + uiElements.find(name).activatePause(); + }; + + // FUNCTION: forcibly deactivate UI pausing + function deactivateUIPausing(name){ + uiElements.find(name).deactivatePause(); + }; + + // FUNCTION: toggle element + function toggleElement(uiName, elemName){ + uiElements.find(uiName).subElements.find(elemName).toggleActive(); + } + + // FUNCTION: forcibly deactivate subelement + function deactivateElement(uiName, elemName){ + uiElements.find(uiName).subElements.find(elemName).deactivate(); + } + + // FUNCTION: forcibly activate subelement + function activateElement(uiName, elemName){ + uiElements.find(uiName).subElements.find(elemName).activate(); + } + + // FUNCTION: modify button variables + function modifyButton(uiName, buttonName, varName, args){ + var but = uiElements.find(uiName).subElements.find(buttonName); + switch(varName){ + case("all"): + but.setName(args.name); + but.setOffset(args.xOff, args.yOff); + but.setBounds(args.width, args.height); + but.setBorder(args.color, args.width); + but.setFill(args.color); + but.setImage(args.image); + but.setText(args.string, args.css, args.color); + but.setClick(args.click); + but.setHover(args.hover); + break; + case("name"): + but.setName(args.name); + break; + case("offset"): + but.setOffset(args.xOff, args.yOff); + break; + case("position"): + but.setPosition(args.xPos, args.yPos); + break; + case("bounds"): + but.setBounds(args.width, args.height); + break; + case("border"): + but.setBorder(args.color, args.width); + break; + case("fill"): + but.setFill(args.color); + break; + case("image"): + but.setImage(args.image); + break; + case("text"): + but.setText(args.string, args.css, args.color); + break; + case("click"): + but.setClick(args.click); + break; + case("hover"): + but.setHover(args.hover); + break; + } + } + + //FUNCTION: modify status bar variables + function modifyBar(uiName, barName, varName, args){ + var bar = uiElements.find(uiName).subElements.find(barName); + switch(varName){ + case("all"): + bar.setName(args.name); + bar.setOffset(args.xOff, args,yOff); + bar.setBounds(args.width, args.height); + bar.setBorder(args.color, args.width); + bar.setFill(args.color); + bar.setImage(args.image); + bar.setText(args.string, args.css, args.color); + bar.setTarget(args.tgtVar, args.tgtMax, args.tgtMin); + break; + case("name"): + bar.setName(args.name); + break; + case("offset"): + bar.setOffset(args.xOff, args,yOff); + break; + case("position"): + but.setPosition(args.xPos, args.yPos); + break; + case("bounds"): + bar.setBounds(args.width, args.height); + break; + case("border"): + bar.setBorder(args.color, args.width); + break; + case("fill"): + bar.setFill(args.backColor, args.foreColor); + break; + case("image"): + bar.setImage(args.backImage, args.foreImage); + break; + case("text"): + bar.setText(args.string, args.css, args.color); + break; + case("target"): + bar.setTarget(args.tgtVar, args.tgtMax, args.tgtMin); + break; + } + } + + // FUNCTION: modify text variables + function modifyText(uiName, textName, varName, args){ + var text = uiElements.find(uiName).subElements.find(textName); + switch(varName){ + case("all"): + text.setName(args.name); + text.setOffset(args.xOff, args,yOff); + text.setBounds(args.width, args.height); + text.setBorder(args.color, args.width); + text.setFill(args.color); + text.setImage(args.image); + text.setText(args.string, args.css, args.color); + text.setTarget(args.targets); + break; + case("name"): + text.setName(args.name); + break; + case("offset"): + bar.setOffset(args.xOff, args,yOff); + break; + case("position"): + but.setPosition(args.xPos, args.yPos); + break; + case("bounds"): + bar.setBounds(args.width, args.height); + break; + case("border"): + text.setBorder(args.color, args.width); + break; + case("padding"): + text.setPadding(args.top, args.right, args.bottom, args.left, args.line); + case("fill"): + text.setFill(args.color); + break; + case("image"): + text.setImage(args.image); + break; + case("text"): + text.setText(args.string, args.css, args.color); + break; + case("target"): + text.setTarget(args.targets); + } + } + + // FUNCTION: modify image variables + function modifyImage(uiName, imageName, varName, args){ + var img = uiElements.find(uiName).subElements.find(imageName); + switch(varName){ + case("all"): + img.setName(args.name); + img.setOffset(args.xOff, args.yOff); + img.setBounds(args.width, args.height); + img.setBorder(args.color, args.width); + img.setFill(args.color); + img.setImage(args.image); + break; + case("name"): + img.setName(args.name); + break; + case("offset"): + img.setOffset(args.xOff, args.yOff); + break; + case("position"): + img.setPosition(args.xPos, args.yPos); + break; + case("bounds"): + img.setBounds(args.width, args.height); + break; + case("border"): + img.setBorder(args.color, args.width); + break; + case("fill"): + img.setFill(args.color); + break; + case("image"): + img.setImage(args.image); + break; + } + } + + // CLASS: user interface object + var UI = function(name, xPos, yPos, width, height) { + // element name + this.name = name; + + // base position of UI element + this.position = new Vector(xPos, yPos); + + // element bounds + this.bounds = new Vector(width, height); + + // border styling + this.border = { + color: "", + width: 0, + }; + + this.fillColor = ""; // background fill color + this.image = new Image(); // background image + this.isActive = false; // if the element is active and displayed + this.doesPause = false; // if the element pauses the game when active + + this.subElements = []; + // FUNCTION: find named object in array + this.subElements.find = function(name){ + for(var i=0; i < this.length; i++){ + if(this[i].name == name){return this[i]}; + }; + }; + + // UI MODIFIERS + // MUTATOR: set name + this.setName = function(newName){ + this.name = newName; + }; + + // MUTATOR: set UI position + this.setPosition = function(xPos, yPos){ + this.position = new Vector(xPos, yPos); + }; + + // MUTATOR: set up bounding rectangle + this.setBounds = function(width, height){ + this.bounds = new Vector(width, height); + }; + + // MUTATOR: set border + this.setBorder = function(color, width){ + this.border = {color:color, width:width}; // set color to "" to stop border drawing + }; + + // MUTATOR: set fill + this.setFill = function(color){ + this.fillColor = color; // set to "" to stop color fill + }; + + // MUTATOR: set background image + this.setImage = function(image){ + this.image = image; // set to null to stop image drawing + }; + + // FUNCTION: toggle whether element is active + this.toggleActive = function(){ + this.isActive = !this.isActive; + }; + + // FUNCTION: forcibly deactivates the element + this.deactivate = function(){ + this.isActive = false; + }; + + // FUNCTION: forcibly activate the element + this.activate = function(){ + this.isActive = true; + }; + + // FUNCTION: toggle whether element pauses game + this.togglePause = function(){ + this.doesPause = !this.doesPause; + }; + + // FUNCTION: forcibly activate pausing + this.activatePause = function(){ + this.doesPause = true; + }; + + // FUNCTION: forcibly deactivate pausing + this.deactivatePause = function(){ + this.doesPause = false; + }; + // UI MODIFIERS + + // FUNCTION: update and draw UI element + this.updateAndDraw = function(trackers){ + if (this.isActive){ + // fill color + if (this.fillColor != ""){ + ctx.fillStyle = this.fillColor; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // stroke border + if(this.border.color != ""){ + ctx.strokeStyle = this.border.color; + ctx.lineWidth = this.border.width; + ctx.strokeRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // draw image + if(this.image.src != null && this.image.src != "" && this.image.src != undefined) { + ctx.drawImage(this.image, this.position.x, this.position.y); + } + + // update tracked variables + for(var i=0; i < trackers.length; i++){ + var elem = this.subElements.find(trackers[i].name); + if(elem != null){ + elem.trackers = trackers[i].value; + bar.target.value = trackers[i].value; + } + } + + // update and draw elements + for(var i=0; i < this.subElements.length; i++){ + this.subElements[i].updateAndDraw(); + } + } + }; + }; + + // CLASS: button object + var Button = function(parentName, name, offsetX, offsetY, width, height, clickEvent) { + // reference names + this.parentName = parentName; + this.name = name; + + // offset from base UI element + this.offset = new Vector(offsetX, offsetY); + + // position of the parent element + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + + // global position of subelement + this.position = new Vector(this.parPosition.x + this.offset.x, this.parPosition.y + this.offset.y); + + // button bounds + this.bounds = new Vector(width, height); + + // border styling + this.border = { + color: "", + width: 0, + }; + + this.fillColor = "gray"; // background fill color + this.image = new Image(); // background image + this.isActive = true; // if the element is active and displayed + + // text on button + this.text = { + string: "", + css: "", + color: "", + }; + + this.onClick = clickEvent; // event to fire on click + this.onHover = undefined; // event to fire on hover + this.imageLoaded = false; + + this.image.onload = function() { this.imageLoaded = true; } + + // FUNCTION: update and draw button if active + this.updateAndDraw = function() { + if (this.isActive && (this.imageLoaded || this.image.src === "")) { + // fill color + if(this.fillColor != ""){ + ctx.fillStyle = this.fillColor; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // stroke border + if(this.border.color != ""){ + ctx.strokeStyle = this.border.color; + ctx.lineWidth = this.border.width; + ctx.strokeRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // draw image + if(this.image != undefined && this.image != null && this.image.src != undefined && this.image.src != null && this.image.src != ""){ + if (!this.logged) { this.logged= true; + console.log(this.image);} + ctx.drawImage(this.image, this.position.x, this.position.y); + } + + // print text + if (this.text.string != "") { + fillText(ctx, this.text.string, (this.position.x + this.bounds.x / 2), (this.position.y + this.bounds.y / 2), this.text.css, this.text.color); + } + } + } + + //{ BUTTON FUNCTIONS + // MUTATOR: set name + this.setName = function(newName){ + this.name = newName; + } + + // MUTATOR: set offset + this.setOffset = function(xOffset, yOffset){ + this.offset = new Vector(offsetX, offsetY); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set position + this.setPosition = function(xPos, yPos){ + this.position = new Vector(xPos, yPos); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.offset = new Vector(position.x - parPosition.x, position.y - parPosition.y); + } + + // FUNCTION: update position from parent + this.updatePos = function(){ + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set bounds + this.setBounds = function(width, height){ + this.bounds = new Vector(width, height); + } + + // MUTATOR: set border styling + this.setBorder = function(color, width){ + this.border = {color:color, width:width}; + } + + // MUTATOR: set color + this.setFill = function(color){ + this.fillColor = color; + } + + // MUTATOR: set image + this.setImage = function(image){ + this.image = image; + } + + // MUTATOR: set text + this.setText = function(string, css, color){ + this.text = {string:string, css:css, color:color}; + } + + // MUTATOR: set click event + this.setClick = function(event){ + this.onClick = event; + } + + // MUTATOR: set hover event + this.setHover = function(event){ + this.onHover = event; + } + + // FUNCTION: toggle whether is active + this.toggleActive = function(){ + this.isActive = !this.isActive; + } + + // FUNCTION: forcibly deactivates the element + this.deactivate = function(){ + this.isActive = false; + }; + + // FUNCTION: forcibly activate the element + this.activate = function(){ + this.isActive = true; + }; + //} BUTTON FUNCTIONS + }; + + // CLASS: status bar object + var Bar = function(parentName, name, offsetX, offsetY, width, height, tgtVar, tgtMax, tgtMin) { + // reference name + this.parentName = parentName; + this.name = name; + + // offset from base UI element + this.offset = new Vector(offsetX, offsetY); + + // position of the parent element + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + + // global position of subelement + this.position = new Vector(this.parPosition.x + this.offset.x, this.parPosition.y + this.offset.y); + + // bar bounds + this.bounds = new Vector(width, height); + + // border styling + this.border = { + color: "", + width: 0, + }; + + // fill colors + this.color = { + back: "gray", + fore: "green", + } + + // fill images + this.image = { + back: new Image(), + fore: new Image() + } + + this.isActive = true; // if the element is active and displayed + + // value of tracked variable + this.trackers = tgtVar; + + // variable bounds to be tracked by bar + this.target = { + max: tgtMax, + min: tgtMin, + } + + // text on bar + this.text = { + string: "", + css: "", + color: "", + }; + + // FUNCTION: update and draw bar if active + this.updateAndDraw = function() { + if (this.isActive){ + // percent fill of bar + var percent = clamp(this.trackers / (this.target.max - this.target.min), 0.0, 1.0); + + // fill background color + if(this.color.back != ""){ + ctx.fillStyle = this.color.back; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // stroke border + if(this.border.color != ""){ + ctx.strokeStyle = this.border.color; + ctx.lineWidth = this.border.width; + ctx.strokeRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // fill foreground color + if(this.color.fore != ""){ + ctx.fillStyle = this.color.fore; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x * percent, this.bounds.y); + } + + // draw background image + if(this.image.back.src != null){ + ctx.drawImage(this.image.back, this.position.x, this.position.y); + } + + // draw foreground image + if(this.image.fore.src != ""){ + ctx.drawImage(this.image.fore, 0, 0, this.bounds.x * percent, this.bounds.y, this.position.x, this.position.y, this.bounds.x * percent, this.bounds.y); + } + // print text + if(this.text.string != "") { + fillText(ctx, this.text.string, (this.position.x + this.bounds.x / 2), (this.position.y + this.bounds.y / 2), this.text.css, this.text.color); + } + } + } + + //{ BAR FUNCTIONS + // MUTATOR: set name + this.setName = function(newName){ + this.name = newName; + } + + // MUTATOR: set offset + this.setOffset = function(xOffset, yOffset){ + this.offset = new Vector(offsetX, offsetY); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set position + this.setPosition = function(xPos, yPos){ + this.position = new Vector(xPos, yPos); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.offset = new Vector(position.x - parPosition.x, position.y - parPosition.y); + } + + // FUNCTION: update position from parent + this.updatePos = function(){ + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set bounds + this.setBounds = function(width, height){ + this.bounds = new Vector(width, height); + } + + // MUTATOR: set border styling + this.setBorder = function(color, width){ + this.border = {color:color, width:width}; + } + + // MUTATOR: set color + this.setFill = function(backColor, foreColor){ + this.color = {back: backColor, fore: foreColor}; + } + + // MUTATOR: set image + this.setImage = function(backImage, foreImage){ + this.image.back = backImage; + this.image.fore = foreImage; + } + + // MUTATOR: set text + this.setText = function(string, css, color){ + this.text = {string:string, css:css, color:color}; + } + + // MUTATOR: set target + this.setTarget = function(tgtVar, tgtMax, tgtMin){ + this.trackers = tgtVar; + this.target = {max: tgtMax, min: tgtMin}; + } + + // FUNCTION: toggle whether is active + this.toggleActive = function(){ + this.isActive = !this.isActive; + } + + // FUNCTION: forcibly deactivates the element + this.deactivate = function(){ + this.isActive = false; + }; + + // FUNCTION: forcibly activate the element + this.activate = function(){ + this.isActive = true; + }; + //} BAR FUNCTIONS + } + + // CLASS: text box object + var Text = function(parentName, name, offsetX, offsetY, width, height, string, css, color) { + // reference name + this.parentName = parentName; + this.name = name; + + // offset from base UI element + this.offset = new Vector(offsetX, offsetY); + + // position of the parent element + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + + // global position of subelement + this.position = new Vector(this.parPosition.x + this.offset.x, this.parPosition.y + this.offset.y); + + // text box bounds + this.bounds = new Vector(width, height); + if (this.bounds.x === "default") this.bounds.x = ctx.measureText(string).width; + if (this.bounds.y === "default") this.bounds.y = this.bounds.x*1.5; + + // border styling + this.border = { + color: "", + width: 0, + }; + + // text padding + this.padding = { + top: 0, + right: 0, + bottom: 0, + left: 0, + line: 0, + }; + + // fill colors + this.color = "rgba(0, 0, 0, 0)"; + + // fill images + this.image = new Image(); + + // if the element is active and displayed + this.isActive = true; + + // text + this.text = { + string: string, + output: string, + css: css, + color: color, + }; + + // data to track in formatted string + this.trackers = []; + + // make sure it doesn't draw an unloaded image + this.imageLoaded = false; + this.image.onload = function() { this.imageLoaded = true; } + + // FUNCTION: update and draw if active + this.updateAndDraw = function() { + if (this.isActive && (this.imageLoaded || this.image.src === "")){ + // fill background color + if(this.color != "") { + ctx.fillStyle = this.color; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // stroke border + if(this.border.color != ""){ + ctx.strokeStyle = this.border.color; + ctx.lineWidth = this.border.width; + ctx.strokeRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // draw background image + if(this.image != undefined && this.image != null && this.image.src != undefined && this.image.src != null && this.image.src != ""){ + ctx.drawImage(this.image, this.position.x, this.position.y); + } + // update formatted text + if(this.trackers.length != 0){ + var trackIndex = 0; + var str = this.text.string; + for(var i=0; i < str.length-1; i++){ + if(str.charAt(i) == "%" && str.charAt(i + 1) == "v"){ + str = (str.substr(0,i) + this.trackers[trackIndex] + str.substr(i+2)); + i += this.trackers[trackIndex].length; + trackIndex++; + } + } + this.text.output = str; + } + + // print text + if(this.text.output != "") { + // save canvas context and set up drawing variables + ctx.save(); + ctx.textAlign = "left"; + ctx.textBaseline = "top"; + ctx.font = this.text.css; + ctx.fillStyle = this.text.color; + + // prepare variables for drawing string with wrapping + var str = this.text.output; + var line = 0; + var xPos = 0; + var height = (ctx.measureText(str).width/str.length) * 1.5; + + // loop through letters + for(var i = 0; i < str.length; i++){ + // if currently looped character is a space or endline, or we've reached the end of the string, draw the word + if (str.charAt(i) == " " || (str.charAt(i) == "%" && str.charAt(i+1) == "n") || i == str.length - 1) { + // get the current word + var subtext = ((str.charAt(i) == "%" && str.charAt(i+1)) ? str.substr(0, i) : str.substr(0, i+1)); + var measured = ctx.measureText(subtext); + + // wrap down to next line if the current word: + // 1 - would go outside the textbox (xPos + it's width > box size - padding) + // 2 - isn't wider than the textbox on its own (xPos > 0 - only wraps if it's not the first word) + if (xPos + measured.width > this.bounds.x - this.padding.left - this.padding.right && xPos > 0) { + ++line; + xPos = 0; + } + + // draw the text + ctx.fillText(subtext, this.position.x + this.padding.left + xPos, this.position.y + this.padding.top + ((height + this.padding.line)*line)); + // update new line + if (str.charAt(i) == "%" && str.charAt(i+1) == "n") { + ++line; + xPos = 0; + str = str.substr(i+2); + } + else { + // update drawing variables + xPos += measured.width; // slide draw position over + str = str.substr(i); // cut out the word we just drew from the string + } + i = 0; // start at the beginning of the new substring + } + } + ctx.restore(); + } + } + }; + + //{ TEXT FUNCTIONS + // MUTATOR: set name + this.setName = function(newName){ + this.name = newName; + }; + + // MUTATOR: set offset + this.setOffset = function(xOffset, yOffset){ + this.offset = new Vector(offsetX, offsetY); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set position + this.setPosition = function(xPos, yPos){ + this.position = new Vector(xPos, yPos); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.offset = new Vector(position.x - parPosition.x, position.y - parPosition.y); + } + + // FUNCTION: update position from parent + this.updatePos = function(){ + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set bounds + this.setBounds = function(width, height){ + this.bounds = new Vector(width, height); + } + + // MUTATOR: set border styling + this.setBorder = function(color, width){ + this.border = {color:color, width:width}; + }; + + // MUTATOR: set text padding + this.setPadding = function(top, right, bottom, left, line){ + this.padding = {top:top, right:right, bottom:bottom, left:left, line:line}; + }; + + // MUTATOR: set color + this.setFill = function(color){ + this.color = {color: color}; + }; + + // MUTATOR: set image + this.setImage = function(image){ + this.image = image; + }; + + // MUTATOR: set text + this.setText = function(string, css, color){ + this.text = {string:string, output:string, css:css, color:color}; + }; + + // MUTATOR: set targets + this.setTarget = function(targets){ + this.trackers = targets; + }; + + // FUNCTION: toggle if active + this.toggleActive = function(){ + this.isActive = !this.isActive; + }; + + // FUNCTION: forcibly deactivates the element + this.deactivate = function(){ + this.isActive = false; + }; + + // FUNCTION: forcibly activate the element + this.activate = function(){ + this.isActive = true; + }; + //} TEXT FUNCTIONS + } + + // CLASS: image object + var ImageBox = function(parentName, name, offsetX, offsetY, width, height, image) { + // reference names + this.parentName = parentName; + this.name = name; + + // offset from base UI element + this.offset = new Vector(offsetX, offsetY); + + // position of the parent element + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + + // global position of subelement + this.position = new Vector(this.parPosition.x + this.offset.x, this.parPosition.y + this.offset.y); + + // button bounds + this.bounds = new Vector(width, height); + + // border styling + this.border = { + color: "", + width: 0, + }; + + this.fillColor = "rgba(0, 0, 0, 0)"; // background fill color + this.image = image; // image + this.isActive = true; // if the element is active and displayed + + // FUNCTION: update and draw button if active + this.updateAndDraw = function() { + if (this.isActive) { + // fill color + if(this.fillColor != ""){ + ctx.fillStyle = this.fillColor; + ctx.fillRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // stroke border + if(this.border.color != ""){ + ctx.strokeStyle = this.border.color; + ctx.lineWidth = this.border.width; + ctx.strokeRect(this.position.x, this.position.y, this.bounds.x, this.bounds.y); + } + + // draw image + if(this.image.src != null){ + ctx.drawImage(this.image, this.position.x, this.position.y); + } + } + } + + //{ IMAGE FUNCTIONS + // MUTATOR: set name + this.setName = function(newName){ + this.name = newName; + } + + // MUTATOR: set offset + this.setOffset = function(xOffset, yOffset){ + this.offset = new Vector(offsetX, offsetY); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set position + this.setPosition = function(xPos, yPos){ + this.position = new Vector(xPos, yPos); + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.offset = new Vector(position.x - parPosition.x, position.y - parPosition.y); + } + + // FUNCTION: update position from parent + this.updatePos = function(){ + this.parPosition = new Vector(uiElements.find(this.parentName).position.x, uiElements.find(this.parentName).position.y); + this.position = new Vector(parPosition.x + offset.x, parPosition.y + offset.y); + } + + // MUTATOR: set bounds + this.setBounds = function(width, height){ + this.bounds = new Vector(width, height); + } + + // MUTATOR: set border styling + this.setBorder = function(color, width){ + this.border = {color:color, width:width}; + } + + // MUTATOR: set color + this.setFill = function(color){ + this.fillColor = color; + } + + // MUTATOR: set image + this.setImage = function(image){ + this.image = image; + } + + // FUNCTION: toggle whether is active + this.toggleActive = function(){ + this.isActive = !this.isActive; + } + + // FUNCTION: forcibly deactivates the element + this.deactivate = function(){ + this.isActive = false; + }; + + // FUNCTION: forcibly activate the element + this.activate = function(){ + this.isActive = true; + }; + //} + } + + window.addEventListener("load", init); + + return { + init: init, + updateAndDraw: updateAndDraw, + checkMouse: checkMouse, + makeUI: makeUI, + makeButton: makeButton, + makeBar: makeBar, + makeText: makeText, + makeImage: makeImage, + modifyUI: modifyUI, + toggleUI: toggleUI, + activateUI: activateUI, + deactivateUI: deactivateUI, + toggleUIPausing: toggleUIPausing, + activateUIPausing: activateUIPausing, + deactivateUIPausing: deactivateUIPausing, + toggleElement: toggleElement, + deactivateElement: deactivateElement, + activateElement: activateElement, + modifyButton: modifyButton, + modifyBar: modifyBar, + modifyText: modifyText, + modifyImage: modifyImage + } +}()); \ No newline at end of file diff --git a/package.json b/package.json index e990ada..6966434 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "jade": "^1.11.0", "matter-js": "^0.9.1", "mongoose": "^4.4.11", + "pug": "^2.0.0-alpha6", "pixi.js": "^3.0.10", "serve-favicon": "^2.3.0", "socket.io": "^1.4.5", diff --git a/src/InputManager.js b/src/InputManager.js index 19a1c90..1b5b7cf 100644 --- a/src/InputManager.js +++ b/src/InputManager.js @@ -8,48 +8,15 @@ class InputManager { this.io = io; this.Manager = manager; this.p1 = player1; - this.p2 = player2; - - // Pass the player sockets to input event handlers - this.onClick(this.p1); - this.onClick(this.p2); - this.onRelease(this.p1); - this.onRelease(this.p2); + this.p2 = player2; } - // Callback for user click - onClick(socket) { - socket.on("click", function(data) { - - socket.lastClick = data.pos; - - }.bind(this)); - } - - // Callback for user click release - onRelease(socket) { - socket.on("release", function(data) { - - // make sure lastClick has been declared, otherwise declare and bail out - if (socket.lastClick == undefined) { - socket.lastClick = data.pos; - return; - } - - //var newBody = this.Manager.Bodies.circle(socket.lastClick.x, socket.lastClick.y, 15); - var vel = { - x: Math.min(30, Math.abs(socket.lastClick.x - data.pos.x)) * Math.sign(socket.lastClick.x - data.pos.x), - y: Math.min(30, Math.abs(socket.lastClick.y - data.pos.y)) * Math.sign(socket.lastClick.y - data.pos.y) - }; - - this.io.sockets.in(this.Manager.room).emit("sendOrUpdateBody", { - position: data.pos, - velocity: vel, - label: "Circle Body", - circleRadius: 15 - }); - - }.bind(this)); + + // Notifies other users of player ability change + onAbilityChange(socket) { + socket.on("abilityChange", function(data) { + if (this.io.sockets.in(this.Manager.room).emit("abilityChange", data)); + }); } } diff --git a/src/game.js b/src/game.js index e042f0f..deb309f 100644 --- a/src/game.js +++ b/src/game.js @@ -119,7 +119,7 @@ function createRoom() { queue[1].join(name); // notify users of new game - io.sockets.in(name).emit("msg", {msg: "Opponent connected. Starting the game. Drag the gems into yor base to score."}); + io.sockets.in(name).emit("gameMsg", {msg: "Opponent connected. Starting the game. Get the gems into your base to score."}); // create the new game var newGame = new GameManager(io, name, queue[0], queue[1]); diff --git a/src/views/base.jade b/src/views/base.jade index f53d62b..a614a50 100644 --- a/src/views/base.jade +++ b/src/views/base.jade @@ -4,7 +4,7 @@ html(lang="en") meta(charset="utf-8") title Kinesthesia script(src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js") - link(rel="shortcut icon", href="/assets/images/gem.png") + link(rel="shortcut icon", href="/assets/images/favicon.png") link(rel="stylesheet", type="text/css", href='https://fonts.googleapis.com/css?family=Roboto') link(rel="stylesheet", type="text/css", href='https://fonts.googleapis.com/css?family=Audiowide') block style diff --git a/src/views/game.jade b/src/views/game.jade index ed773b9..1e1ef3b 100644 --- a/src/views/game.jade +++ b/src/views/game.jade @@ -4,9 +4,12 @@ block append content article h3#serverInfo Waiting for game to start div#canvasContainer + canvas(width="1200", height="640", id="UI") input#token(type="hidden", name="_csrf", value="#{csrfToken}") block append scripts script(src="/assets/matter.min.js") script(src="/assets/pixi.min.js") + script(src="/assets/utilities.js") + script(src="/assets/window-manager.js") script(src="/assets/kinesthesia.js") \ No newline at end of file