src/client/js/pixigame.js
import * as PIXI from 'pixi.js'
import { keyboard } from './lib/keyboard'
import { GLOBAL } from './global'
import { Player } from './obj/player'
import { hideElement, showElement, selectedBlueprints, updateCompoundButtons, selectedCompound, mouseX, mouseY } from './app'
import { socket, objects, teamColors } from './socket'
import { requestCreateCompound, createTiles } from './obj/create'
import { joystick } from './app'
export var isSetup // True after the stage is fully set up
export var player // The player being controlled by this client
export var screenCenterX // X-coordinate of the center of the screen
export var screenCenterY // Y-coordinate of the center of the screen
export var app // Pixi app
export var spritesheet // Spritesheet containing all sprites that need to be loaded
export let mouseDown = false // True if mouse is pressed down
let inGame = false // True after game has begun
let esc, space, blueprintKeys, moveKeys // Key handlers
let streamID = 0 // Current stream compound number. Resets when mouse/space is released; otherwise increments by one every time a compound is created.
export function loadTextures () {
if (!isSetup) {
// Initialization
let type = (PIXI.utils.isWebGLSupported()) ? 'WebGL' : 'canvas'
PIXI.utils.sayHello(type)
// Create a Pixi Application
app = new PIXI.Application(0, 0, {
view: document.getElementById('gameView')
})
// Add the canvas that Pixi automatically created for you to the HTML document
// document.body.appendChild(app.view);
// Renderer settings
app.renderer.autoResize = true
app.renderer.resize(window.innerWidth, window.innerHeight)
screenCenterX = window.innerWidth / 2 - GLOBAL.PLAYER_RADIUS
screenCenterY = window.innerHeight / 2 - GLOBAL.PLAYER_RADIUS
// Initiate resource loading
if (Object.keys(PIXI.loader.resources).length < 1) {
PIXI.loader
.add(GLOBAL.SPRITESHEET_DIR)
.load(registerCallbacks)
}
}
// If already initialized, use existing app variable
if (isSetup) {
console.info('Stage already initialized!')
clearStage()
registerCallbacks()
}
}
/**
* Sets up the stage. Call after init(), and begins the draw() loop once complete.
*/
function registerCallbacks () {
if (!isSetup) {
// Set up key listeners
esc = keyboard(GLOBAL.KEY_ESC)
space = keyboard(GLOBAL.KEY_SPACE)
// All the movement keys for easy access
moveKeys = [
keyboard(GLOBAL.KEY_A), // Left
keyboard(GLOBAL.KEY_D), // Right
keyboard(GLOBAL.KEY_W), // Up
keyboard(GLOBAL.KEY_S) // Down
]
// Set up the blueprint key listeners
blueprintKeys = [
keyboard(GLOBAL.KEY_1),
keyboard(GLOBAL.KEY_2),
keyboard(GLOBAL.KEY_3),
keyboard(GLOBAL.KEY_4)
]
// Escape key setup
esc.press = () => {
if (isFocused()) {
if (document.activeElement !== document.getElementById('chatInput')) {
toggleMenu()
}
else {
document.getElementById('chatInput').blur()
}
}
}
// Chat box styling on select
document.getElementById('chatInput').onfocus = () => {
document.getElementById('chatbox').style.boxShadow = '0px 0px 1rem 0px #311B92'
}
document.getElementById('chatInput').onblur = () => {
document.getElementById('chatbox').style.boxShadow = '0px 0px 1rem 0px rgba(180,180,180)'
}
// Bind each blueprint key
for (let key in blueprintKeys) {
blueprintKeys[key].press = () => {
if (isFocused() && inGame) {
updateCompoundButtons(key)
}
}
}
// Background
app.renderer.backgroundColor = 0xFFFFFF
// Resize
document.getElementsByTagName('body')[0].onresize = () => {
app.renderer.resize(window.innerWidth, window.innerHeight)
screenCenterX = window.innerWidth / 2 - GLOBAL.PLAYER_RADIUS
screenCenterY = window.innerHeight / 2 - GLOBAL.PLAYER_RADIUS
player.x = screenCenterX
player.y = screenCenterY
}
// Assign spritesheet object
spritesheet = PIXI.loader.resources[GLOBAL.SPRITESHEET_DIR].spritesheet
console.log(spritesheet)
// Begin game loop
app.ticker.add(delta => draw(delta))
}
isSetup = true
// Draw map
createTiles()
showGameUI()
}
/**
* Called once per frame. Updates all moving sprites on the stage.
* Also checks key inputs.
* @param {number} delta Time value from Pixi
*/
function draw (delta) {
// Handle this player and movement
if (player !== undefined) {
// Make sure player is not in chat before checking move
if (isFocused() && inGame) {
// Keyboard based controls
if ((moveKeys[0].isDown || joystick.mobileKey.leftDown === true) && player.vx > -GLOBAL.MAX_SPEED * player.speedMult) { // Left
movePlayer('left')
}
if ((moveKeys[1].isDown || joystick.mobileKey.rightDown === true) && player.vx < GLOBAL.MAX_SPEED * player.speedMult) { // Right
movePlayer('right')
}
if ((moveKeys[2].isDown || joystick.mobileKey.upDown === true) && player.vy < GLOBAL.MAX_SPEED * player.speedMult) { // Up
movePlayer('up')
}
if ((moveKeys[3].isDown || joystick.mobileKey.downDown === true) && player.vy > -GLOBAL.MAX_SPEED * player.speedMult) { // Down
movePlayer('down')
}
player.isMoving = false
// console.log(moveKeys[0].isDown, moveKeys[1].isDown, moveKeys[2].isDown, moveKeys[3].isDown, mouseDown)
for (let key of moveKeys) {
if (key.isDown) {
player.isMoving = true
}
}
}
else {
player.isMoving = false
// Because the document is not focused disable all keys(Stops moving!)
for (let key in moveKeys) {
moveKeys[key].isDown = false
moveKeys[key].isUp = true
}
}
// Slow down gradually - unaffected by chat input
if (!moveKeys[2].isDown && !moveKeys[3].isDown) {
player.vy *= GLOBAL.VELOCITY_STEP
}
if (!moveKeys[0].isDown && !moveKeys[1].isDown) {
player.vx *= GLOBAL.VELOCITY_STEP
}
// Shooting
space.press = () => {
if (selectedBlueprints[selectedCompound].type !== 'stream') {
shootHandler({ clientX: mouseX, clientY: mouseY }, false)
}
}
// Streams
if ((space.isDown || mouseDown) && selectedBlueprints[selectedCompound].type === 'stream') {
shootHandler({ clientX: mouseX, clientY: mouseY }, true)
}
// Reset stream count when space key is released
space.release = () => {
streamID = 0
}
// Move player
player.tick()
// Send coordinates
socket.emit('move', {
type: 'players',
id: player.id,
posX: player.posX,
posY: player.posY,
vx: player.vx,
vy: player.vy
})
}
// Handle objects except for this player
for (let objType in objects) {
for (let obj in objects[objType]) {
if (objType !== 'players' || player !== objects[objType][obj]) {
objects[objType][obj].tick()
}
}
}
}
/**
* Shows or hides the in-game menu box
*/
function toggleMenu () {
if (document.getElementById('menubox').offsetParent === null) {
showElement('menubox')
}
else {
hideElement('menubox')
}
}
/**
* Remove all elements pre-rendered on stage.
*/
function clearStage () {
for (var i = app.stage.children.length - 1; i >= 0; i--) {
app.stage.removeChild(app.stage.children[i])
}
}
/**
* Destroy everything in PIXI. DANGEROUS avoid!
*/
export function destroyPIXI () {
app.destroy(true, {
children: true,
texture: true,
baseTexture: true
})
PIXI.loader.reset()
isSetup = false
app = undefined
}
/**
* Call this function to hide loading div and show UI
*/
export function showGameUI () {
// Hide loading screen
hideElement('loading')
if (!inGame) {
showElement('lobby')
}
}
/**
* Creates a Player instance once the stage is fully set up and ready.
* @param {*} data Starting values to assign to the player. Generated from server
* @returns {Player} The Player object that was created
*/
export function createPlayer (data) {
if (isSetup) {
console.log('create player ' + data.id)
// console.log(data)
let newPlayer = new Player(spritesheet.textures[teamColors[data.team] + 'player.png'], data.id, data.name, data.room, data.team, data.health, data.posX, data.posY, data.vx, data.vy)
if (data.id === socket.id) {
player = newPlayer
}
return newPlayer
}
}
/**
* If the document is Focused return true otherwise false
**/
export function isFocused () {
return document.hasFocus() && document.activeElement !== document.getElementById('chatInput')
}
/**
* Starts the game after lobby closes.
* @param {boolean} emit True if this client should emit the event to the server.
* @param {*} teams Array of teams on the scoreboard.
*/
export function startGame (emit, teams) {
setIngame(true)
hideElement('lobby')
showElement('hud')
if (emit) {
socket.emit('startGame', {
start: true
})
}
// Init scoreboard
if (teams !== undefined) {
// Reset scoreboard from previous rounds
document.getElementById('score').innerHTML = ''
for (let i = 0; i < teams.length; i++) {
document.getElementById('score').innerHTML += '-<span id="team-score-' + i + '">0</span>'
document.getElementById('team-score-' + i).style.color = '#' + GLOBAL.TEAM_COLORS[i]
}
document.getElementById('score').style.fontSize = '3vw'
document.getElementById('score').innerHTML += '-'
}
}
/**
* Sets the value of inGame
* @param {boolean} newValue Value to set inGame to
*/
export function setIngame (newValue) {
inGame = newValue
}
/**
* @returns {boolean} Returns inGame variable
*/
export function getIngame () {
return inGame
}
/**
* Called on mouse up from app.js
* @param {*} e Click event
*/
export function mouseUpHandler (e) {
mouseDown = true
if (selectedBlueprints[selectedCompound] && selectedBlueprints[selectedCompound].type !== 'stream') {
shootHandler(e, false)
}
}
/**
* Called on mouse down from app.js
* @param {*} e Click event
*/
export function mouseDownHandler (e) {
mouseDown = false
streamID = 0
}
/**
* Handles shooting mechanics on mouse/spacebar click/hold.
* @param {*} e Click event
* @param {boolean} stream True if sending a stream (such as water); false otherwise.
*/
function shootHandler (e, stream) {
if (isFocused() && inGame && !player.spectatingl) {
if (stream) {
streamID++
}
requestCreateCompound(selectedBlueprints[selectedCompound], e.clientX, e.clientY, streamID)
}
}
/**
* Moves the player by changing its velocity.
* @param {string} direction up, down, left, or right
*/
export function movePlayer (direction) {
if (player.isMoving) {
if (direction === 'up') {
player.vy += GLOBAL.VELOCITY_STEP * player.speedMult
}
if (direction === 'down') {
player.vy += -GLOBAL.VELOCITY_STEP * player.speedMult
}
if (direction === 'right') {
player.vx += GLOBAL.VELOCITY_STEP * player.speedMult
}
if (direction === 'left') {
player.vx += -GLOBAL.VELOCITY_STEP * player.speedMult
}
}
}