Added leaderboard, music & respawn screen
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<Game v-if="isLoggedIn"/>
|
||||
<Game v-if="isLoggedIn && isSocketConnected"/>
|
||||
<div v-else class="container mainMenu">
|
||||
<img src="./assets/logo.png" alt="TronIo logo" width="150px"/>
|
||||
<h1 class="title">tron.io</h1>
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
<script>
|
||||
import { send } from './store/socketPlugin'
|
||||
import { sound } from '@/game/sound.js'
|
||||
import Game from './components/Game'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
@@ -29,17 +31,20 @@ export default {
|
||||
return this.$store.state.game.loggedIn
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
sound.startBackgroundMusic()
|
||||
},
|
||||
methods: {
|
||||
loginPrompt () {
|
||||
this.$buefy.dialog.prompt({
|
||||
message: 'Choose a name',
|
||||
trapFocus: true,
|
||||
inputAttrs: {
|
||||
placeholder: 'pedro',
|
||||
minlength: 3,
|
||||
maxlength: 30
|
||||
placeholder: 'user',
|
||||
minlength: 1,
|
||||
maxlength: 15
|
||||
},
|
||||
confirmText: 'KK',
|
||||
confirmText: 'Go',
|
||||
onConfirm: (name) => {
|
||||
localStorage.setItem('name', name)
|
||||
this.login(name)
|
||||
|
||||
BIN
client/src/assets/sound/explosion.mp3
Normal file
BIN
client/src/assets/sound/explosion.mp3
Normal file
Binary file not shown.
BIN
client/src/assets/sound/revengeOfCats.mp3
Normal file
BIN
client/src/assets/sound/revengeOfCats.mp3
Normal file
Binary file not shown.
@@ -12,7 +12,7 @@ $blue: #3498db;
|
||||
$purple: #8e44ad;
|
||||
$red: #e74c3c;
|
||||
$white-ter: #ecf0f1;
|
||||
$primary: $turquoise;
|
||||
$primary: $purple;
|
||||
$yellow-invert: #fff;
|
||||
|
||||
$family-sans-serif: "Gravity Regular", "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="game">
|
||||
<b-modal v-model="playerIsDead">
|
||||
<h1 class="title">DED</h1>
|
||||
<h2 class="title">Score: {{player.score}}</h2>
|
||||
<h3 class="subtitle">Best score: {{player.bestScore}}</h3>
|
||||
<b-button @click="respawn">Respawn</b-button>
|
||||
</b-modal>
|
||||
<canvas
|
||||
class="game-canvas"
|
||||
ref="canvas">
|
||||
@@ -9,6 +15,9 @@
|
||||
|
||||
<script>
|
||||
import { send } from '@/store/socketPlugin'
|
||||
import { render } from '@/game/render.js'
|
||||
import { sound } from '@/game/sound.js'
|
||||
|
||||
export default {
|
||||
name: 'Game',
|
||||
data () {
|
||||
@@ -22,7 +31,6 @@ export default {
|
||||
y: 0
|
||||
},
|
||||
stats: {
|
||||
leaderboard: [],
|
||||
totalWalls: 0,
|
||||
lastUpdateTime: 0,
|
||||
lastFrame: 0
|
||||
@@ -51,7 +59,7 @@ export default {
|
||||
if (pastUpdate.players === undefined) return []
|
||||
if (nextUpdate === undefined) return pastUpdate.players
|
||||
|
||||
return pastUpdate.players
|
||||
return nextUpdate.players
|
||||
|
||||
/*
|
||||
const currentTime = Date.now() / 1000
|
||||
@@ -61,26 +69,29 @@ export default {
|
||||
},
|
||||
isPaused () {
|
||||
return this.$store.state.game.paused
|
||||
},
|
||||
leaderboard () {
|
||||
return this.$store.state.game.leaderboard
|
||||
},
|
||||
playerIsDead () {
|
||||
if (this.player.state === 'DEAD') sound.explosion()
|
||||
return this.player.state === 'DEAD'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.canvas.width = window.innerWidth
|
||||
this.canvas.height = window.innerHeight
|
||||
this.setCanvasSize()
|
||||
this.canvas.addEventListener('mousemove', this.mouseEvent)
|
||||
this.canvas.addEventListener('touchmove', this.touchEvent)
|
||||
this.renderTimer = setInterval(this.render, 1000 / 120)
|
||||
this.canvas.addEventListener('resize', this.setCanvasSize)
|
||||
// this.renderTimer = setInterval(this.render, 1000 / 120)
|
||||
this.render()
|
||||
},
|
||||
methods: {
|
||||
render () {
|
||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
this.renderBorders()
|
||||
render.borders(this.context, this.canvas, this.camera, this.settings)
|
||||
|
||||
if (!this.players) return
|
||||
|
||||
this.players.sort((a, b) => {
|
||||
return b.walls.length - a.walls.length
|
||||
})
|
||||
this.stats.leaderboard = []
|
||||
this.stats.totalWalls = 0
|
||||
|
||||
this.players.forEach(player => {
|
||||
@@ -88,78 +99,25 @@ export default {
|
||||
this.camera.x = player.x
|
||||
this.camera.y = player.y
|
||||
}
|
||||
this.renderPlayer(player)
|
||||
this.renderWalls(player)
|
||||
this.stats.leaderboard.push(player.name + ' - ' + player.walls.length)
|
||||
|
||||
if (player.state === 'DEAD') this.context.globalAlpha = 0.1
|
||||
render.walls(this.context, this.canvas, this.camera, this.settings, player)
|
||||
render.player(this.context, this.canvas, this.camera, this.settings, player)
|
||||
if (player.state === 'DEAD') this.context.globalAlpha = 1
|
||||
|
||||
this.stats.totalWalls += player.walls.length
|
||||
})
|
||||
|
||||
this.renderLeaderboard()
|
||||
this.renderMouse()
|
||||
|
||||
this.renderDebug()
|
||||
render.leaderboard(this.context, this.canvas, this.leaderboard)
|
||||
render.mouse(this.context, this.mouse, this.player)
|
||||
render.debug(this.context, this.camera, this.mouse, this.canvas, this.stats)
|
||||
|
||||
this.stats.lastFrame = performance.now()
|
||||
},
|
||||
renderBorders () {
|
||||
this.context.strokeStyle = 'white'
|
||||
this.context.lineWidth = 1
|
||||
this.context.strokeRect(this.canvas.width / 2 - this.camera.x, this.canvas.height / 2 - this.camera.y, this.settings.arenaSize, this.settings.arenaSize)
|
||||
},
|
||||
renderPlayer (player) {
|
||||
this.context.save()
|
||||
|
||||
const canvasX = this.canvas.width / 2 + player.x - this.camera.x
|
||||
const canvasY = this.canvas.height / 2 + player.y - this.camera.y
|
||||
const nextUpdate = this.$store.state.game.updates[1]
|
||||
if (nextUpdate !== undefined) this.stats.lastUpdateTime = nextUpdate.time
|
||||
|
||||
this.context.translate(canvasX, canvasY)
|
||||
this.context.rotate(player.angle)
|
||||
this.context.fillStyle = player.color
|
||||
this.context.fillRect(-this.settings.playerSize, -this.settings.playerSize, this.settings.playerSize * 2, this.settings.playerSize * 2)
|
||||
this.context.translate(0, 0)
|
||||
|
||||
this.context.restore()
|
||||
},
|
||||
renderWalls (player) {
|
||||
this.context.beginPath()
|
||||
this.context.lineWidth = this.settings.wallSize
|
||||
this.context.strokeStyle = player.color
|
||||
player.walls.forEach(wall => {
|
||||
const canvasX = this.canvas.width / 2 + wall.x - this.camera.x
|
||||
const canvasY = this.canvas.height / 2 + wall.y - this.camera.y
|
||||
this.context.lineTo(canvasX, canvasY)
|
||||
})
|
||||
this.context.lineTo(this.canvas.width / 2 + player.x - this.camera.x, this.canvas.height / 2 + player.y - this.camera.y)
|
||||
this.context.stroke()
|
||||
},
|
||||
renderDebug () {
|
||||
const canvasX = this.canvas.width / 2
|
||||
const canvasY = this.canvas.height / 2
|
||||
|
||||
this.context.fillStyle = 'white'
|
||||
this.context.textAlign = 'start'
|
||||
this.context.fillText('camera x: ' + this.camera.x + ' y:' + this.camera.y, 10, 12)
|
||||
this.context.fillText('mouse x: ' + this.mouse.x + ' y:' + this.mouse.y, 10, 24)
|
||||
this.context.fillText('canvasX: ' + canvasX + ' canvasY: ' + canvasY, 10, 36)
|
||||
this.context.fillText('Total walls: ' + this.stats.totalWalls, 10, 48)
|
||||
this.context.fillText('Last update: ' + this.stats.lastUpdateTime, 10, 60)
|
||||
const fps = 1000 / (performance.now() - this.stats.lastFrame)
|
||||
this.context.fillText('FPS: ' + fps, 10, 72)
|
||||
},
|
||||
renderMouse () {
|
||||
this.context.beginPath()
|
||||
this.context.arc(this.mouse.x, this.mouse.y, 25, 0, 2 * Math.PI, false)
|
||||
this.context.lineWidth = 1
|
||||
this.context.strokeStyle = this.player.color
|
||||
this.context.stroke()
|
||||
},
|
||||
renderLeaderboard () {
|
||||
this.context.fillStyle = 'white'
|
||||
this.context.textAlign = 'end'
|
||||
this.context.fillText('Leaderboard: ', this.canvas.width - 50, 10)
|
||||
for (var i = 0; i < this.stats.leaderboard.length; i++) {
|
||||
this.context.fillText(this.players[i].name + ' - ' + this.players[i].walls.length, this.canvas.width - 50, 15 + (i + 1) * 10)
|
||||
}
|
||||
requestAnimationFrame(this.render, this.canvas)
|
||||
},
|
||||
mouseEvent (event) {
|
||||
var rect = this.canvas.getBoundingClientRect()
|
||||
@@ -211,6 +169,13 @@ export default {
|
||||
player.targetAngle = pastPlayer.targetAngle + (nextPlayer.targetAngle - pastPlayer.targetAngle) * dt
|
||||
|
||||
return player
|
||||
},
|
||||
setCanvasSize () {
|
||||
this.canvas.width = window.innerWidth
|
||||
this.canvas.height = window.innerHeight
|
||||
},
|
||||
respawn () {
|
||||
send({ type: 'respawn' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
client/src/game/render.js
Normal file
86
client/src/game/render.js
Normal file
@@ -0,0 +1,86 @@
|
||||
export const render = {
|
||||
borders (context, canvas, camera, settings) {
|
||||
context.strokeStyle = 'white'
|
||||
// context.lineWidth = 1
|
||||
// context.shadowBlur = 20
|
||||
context.shadowColor = 'white'
|
||||
context.strokeRect(canvas.width / 2 - camera.x, canvas.height / 2 - camera.y, settings.arenaSize, settings.arenaSize)
|
||||
},
|
||||
player (context, canvas, camera, settings, player) {
|
||||
const canvasX = canvas.width / 2 + player.x - camera.x
|
||||
const canvasY = canvas.height / 2 + player.y - camera.y
|
||||
|
||||
context.save()
|
||||
|
||||
context.translate(canvasX, canvasY)
|
||||
context.rotate(player.angle)
|
||||
context.fillStyle = player.color
|
||||
// context.shadowBlur = 10
|
||||
// context.shadowColor = player.color
|
||||
context.fillRect(-settings.playerSize, -settings.playerSize, settings.playerSize * 2, settings.playerSize * 2)
|
||||
context.translate(0, 0)
|
||||
|
||||
context.restore()
|
||||
|
||||
context.fillStyle = 'white'
|
||||
context.textAlign = 'center'
|
||||
context.fillText(player.name, canvasX, canvasY - settings.playerSize - 5)
|
||||
},
|
||||
walls (context, canvas, camera, settings, player) {
|
||||
context.beginPath()
|
||||
context.lineWidth = settings.wallSize
|
||||
context.strokeStyle = player.color
|
||||
// context.shadowBlur = 0
|
||||
// context.shadowColor = player.color
|
||||
player.walls.forEach(wall => {
|
||||
const canvasX = canvas.width / 2 + wall.x - camera.x
|
||||
const canvasY = canvas.height / 2 + wall.y - camera.y
|
||||
context.lineTo(canvasX, canvasY)
|
||||
})
|
||||
context.lineTo(canvas.width / 2 + player.x - camera.x, canvas.height / 2 + player.y - camera.y)
|
||||
context.stroke()
|
||||
},
|
||||
debug (context, camera, mouse, canvas, stats) {
|
||||
const canvasX = canvas.width / 2
|
||||
const canvasY = canvas.height / 2
|
||||
|
||||
context.fillStyle = 'white'
|
||||
context.textAlign = 'start'
|
||||
context.fillText('camera x: ' + camera.x + ' y:' + camera.y, 10, 12)
|
||||
context.fillText('mouse x: ' + mouse.x + ' y:' + mouse.y, 10, 24)
|
||||
context.fillText('canvasX: ' + canvasX + ' canvasY: ' + canvasY, 10, 36)
|
||||
context.fillText('Total walls: ' + stats.totalWalls, 10, 48)
|
||||
context.fillText('Server update: ' + stats.lastUpdateTime, 10, 60)
|
||||
const fps = 1000 / (performance.now() - stats.lastFrame)
|
||||
context.fillText('FPS: ' + fps, 10, 72)
|
||||
},
|
||||
mouse (context, mouse, player) {
|
||||
context.beginPath()
|
||||
context.arc(mouse.x, mouse.y, 25, 0, 2 * Math.PI, false)
|
||||
context.lineWidth = 1
|
||||
context.strokeStyle = player.color
|
||||
context.stroke()
|
||||
},
|
||||
leaderboard (context, canvas, leaderboard) {
|
||||
context.fillStyle = 'white'
|
||||
context.textAlign = 'end'
|
||||
context.fillText('Leaderboard: ', canvas.width - 50, 10)
|
||||
var i = 1
|
||||
leaderboard.forEach(player => {
|
||||
context.fillStyle = player.color
|
||||
context.fillText(player.name + ' - ' + player.score + ' (' + player.bestScore + ')', canvas.width - 50, 15 + i * 10)
|
||||
i++
|
||||
})
|
||||
},
|
||||
dead (context, canvas, player) {
|
||||
const canvasX = canvas.width / 2
|
||||
const canvasY = canvas.height / 2
|
||||
|
||||
context.fillStyle = 'white'
|
||||
context.textAlign = 'center'
|
||||
context.fillText('YO DED', canvasX, canvasY - 50)
|
||||
context.fillText('Score: ' + player.score, canvasX, canvasY)
|
||||
context.fillText('Best score so far ' + player.bestScore, canvasX, canvasY + 25)
|
||||
context.fillText('Tap to respawn', canvasX, canvasY + 50)
|
||||
}
|
||||
}
|
||||
23
client/src/game/sound.js
Normal file
23
client/src/game/sound.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const music = new Audio(require('@/assets/sound/revengeOfCats.mp3'))
|
||||
// const motor = new Audio(require('@/assets/sound/motor.mp3'))
|
||||
const explosion = new Audio(require('@/assets/sound/explosion.mp3'))
|
||||
|
||||
export const sound = {
|
||||
startBackgroundMusic () {
|
||||
music.loop = true
|
||||
music.play()
|
||||
},
|
||||
stopBackgroundMusic () {
|
||||
music.pause()
|
||||
},
|
||||
startMotor () {
|
||||
motor.loop = true
|
||||
motor.play()
|
||||
},
|
||||
stopMotor () {
|
||||
motor.pause()
|
||||
},
|
||||
explosion () {
|
||||
explosion.play()
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import Buefy from 'buefy'
|
||||
import './registerServiceWorker'
|
||||
|
||||
// import 'buefy/dist/buefy.css'
|
||||
import './assets/style.scss'
|
||||
import './assets/style/style.scss'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(Buefy)
|
||||
|
||||
@@ -4,8 +4,10 @@ const state = {
|
||||
y: 100,
|
||||
angle: 0,
|
||||
targetAngle: 0,
|
||||
color: 'red'
|
||||
color: 'red',
|
||||
state: 'DEAD'
|
||||
},
|
||||
leaderboard: [],
|
||||
updates: [],
|
||||
settings: {
|
||||
playerSize: 10,
|
||||
@@ -21,19 +23,11 @@ const state = {
|
||||
}
|
||||
|
||||
const getters = {
|
||||
// Interpolation position des joueurs
|
||||
currentPlayers: state => {
|
||||
if (state.updates.length < 2) return state.updates[0].players
|
||||
else {
|
||||
const dt = state.updates[0].time - state.updates[1].time
|
||||
return state.updates[1].players.map(player => interpolatePlayer(player, dt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
socketConnected ({ commit }) {
|
||||
commit('SET_CONNECTED')
|
||||
socketConnected ({ commit }, connected) {
|
||||
commit('SET_CONNECTED', connected)
|
||||
commit('CLEAR_UPDATE')
|
||||
},
|
||||
login ({ commit }, player) {
|
||||
@@ -45,10 +39,15 @@ const actions = {
|
||||
},
|
||||
update ({ commit }, update) {
|
||||
commit('ADD_UPDATE', update)
|
||||
commit('SET_LEADERBOARD', update.players)
|
||||
},
|
||||
dead ({ commit }, player) {
|
||||
commit('SET_PLAYER', player)
|
||||
commit('CLEAR_UPDATE')
|
||||
// commit('CLEAR_UPDATE')
|
||||
},
|
||||
spawn ({ commit }, player) {
|
||||
commit('SET_PLAYER', player)
|
||||
// commit('CLEAR_UPDATE')
|
||||
},
|
||||
error ({ commit }, error) {
|
||||
alert('Error: ' + error)
|
||||
@@ -71,8 +70,8 @@ const mutations = {
|
||||
SET_PAUSE (state) {
|
||||
state.paused = !state.paused
|
||||
},
|
||||
SET_CONNECTED (state) {
|
||||
state.socketConnected = !state.socketConnected
|
||||
SET_CONNECTED (state, connected) {
|
||||
state.socketConnected = connected
|
||||
},
|
||||
ADD_UPDATE (state, update) {
|
||||
if (state.updates.length > 2) {
|
||||
@@ -83,11 +82,23 @@ const mutations = {
|
||||
},
|
||||
CLEAR_UPDATE (state) {
|
||||
state.updates = []
|
||||
}
|
||||
}
|
||||
},
|
||||
SET_LEADERBOARD (state, players) {
|
||||
state.leaderboard = []
|
||||
|
||||
function interpolatePlayer (player, dt) {
|
||||
return player
|
||||
players.forEach(player => {
|
||||
state.leaderboard.push({
|
||||
name: player.name,
|
||||
score: player.score,
|
||||
bestScore: player.bestScore,
|
||||
color: player.color
|
||||
})
|
||||
})
|
||||
|
||||
state.leaderboard.sort((a, b) => {
|
||||
return b.score - a.score
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { ToastProgrammatic as Toast } from 'buefy'
|
||||
|
||||
// const connection = new WebSocket('ws://localhost:8181/socket')
|
||||
const connection = new WebSocket('wss://tronio.gltronic.ovh/socket')
|
||||
const connection = new WebSocket('ws://localhost:8181/socket')
|
||||
// const connection = new WebSocket('wss://tronio.gltronic.ovh/socket')
|
||||
|
||||
export default function createSocketPlugin () {
|
||||
return store => {
|
||||
connection.onopen = function () {
|
||||
console.log('[WS] connected')
|
||||
store.dispatch('game/socketConnected')
|
||||
store.dispatch('game/socketConnected', true)
|
||||
}
|
||||
|
||||
connection.onclose = function () {
|
||||
console.log('[WS] closed')
|
||||
store.dispatch('game/socketConnected', false)
|
||||
}
|
||||
|
||||
connection.onerror = function (error) {
|
||||
@@ -41,6 +42,9 @@ export default function createSocketPlugin () {
|
||||
case 'gamePlayerDead':
|
||||
store.dispatch('game/dead', data.message)
|
||||
break
|
||||
case 'gamePlayerSpawn':
|
||||
store.dispatch('game/spawn', data.message)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user