leaderboard
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
AFRAME.registerComponent('keyboard-raycastable', {
|
||||
dependencies: ['super-keyboard'],
|
||||
|
||||
schema: {
|
||||
condition: {default: ''}
|
||||
},
|
||||
|
||||
play: function () {
|
||||
this.el.components['super-keyboard'].kbImg.setAttribute('bind-toggle__raycastable',
|
||||
'isSearching');
|
||||
this.data.condition);
|
||||
}
|
||||
});
|
||||
|
||||
142
src/components/leaderboard.js
Normal file
142
src/components/leaderboard.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const firebase = require('firebase/app');
|
||||
require('firebase/firestore');
|
||||
|
||||
const NUM_SCORES_DISPLAYED = 10;
|
||||
|
||||
/**
|
||||
* High score with Firebase cloud store.
|
||||
* Index: challengeId ASC difficulty ASC score DESC time ASC
|
||||
*/
|
||||
AFRAME.registerComponent('leaderboard', {
|
||||
schema: {
|
||||
apiKey: {type: 'string'},
|
||||
authDomain: {type: 'string'},
|
||||
databaseURL: {type: 'string'},
|
||||
projectId: {type: 'string'},
|
||||
storageBucket: {type: 'string'},
|
||||
messagingSenderId: {type: 'string'},
|
||||
|
||||
challengeId: {default: ''},
|
||||
menuSelectedChallengeId: {default: ''},
|
||||
isVictory: {default: false}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.qualifyingIndex = undefined;
|
||||
this.scores = [];
|
||||
this.eventDetail = {scores: this.scores};
|
||||
this.addEventDetail = {scoreData: undefined, index: undefined};
|
||||
|
||||
this.username = localStorage.getItem('supersaberusername') || 'Super Zealot';
|
||||
this.el.addEventListener('leaderboardusername', evt => {
|
||||
this.username = evt.detail.value;
|
||||
localStorage.setItem('supersaberusername', this.username);
|
||||
});
|
||||
this.el.addEventListener('leaderboardsubmit', this.addScore.bind(this));
|
||||
},
|
||||
|
||||
update: function (oldData) {
|
||||
// Initialize Cloud Firestore through Firebase.
|
||||
if (!firebase.apps.length && this.data.apiKey) {
|
||||
firebase.initializeApp({
|
||||
apiKey: this.data.apiKey,
|
||||
authDomain: this.data.authDomain,
|
||||
databaseURL: this.data.databaseURL,
|
||||
projectId: this.data.projectId,
|
||||
storageBucket: this.data.storageBucket,
|
||||
messagingSenderId: this.data.messagingSenderId
|
||||
});
|
||||
this.firestore = firebase.firestore();
|
||||
this.firestore.settings({timestampsInSnapshots: true});
|
||||
this.db = this.firestore.collection('scores');
|
||||
}
|
||||
|
||||
if (!oldData.isVictory && this.data.isVictory) {
|
||||
this.checkLeaderboardQualify();
|
||||
}
|
||||
|
||||
if (oldData.menuSelectedChallengeId !== this.data.menuSelectedChallengeId) {
|
||||
this.fetchScores(this.data.menuSelectedChallengeId);
|
||||
}
|
||||
|
||||
if (oldData.challengeId !== this.data.challengeId) {
|
||||
this.fetchScores(this.data.challengeId);
|
||||
}
|
||||
},
|
||||
|
||||
addScore: function () {
|
||||
const state = this.el.sceneEl.systems.state.state;
|
||||
if (!state.isVictory) { return; }
|
||||
const scoreData = {
|
||||
challengeId: state.challenge.id,
|
||||
score: state.score.score,
|
||||
username: this.username,
|
||||
difficulty: state.challenge.difficulty,
|
||||
time: new Date()
|
||||
};
|
||||
this.db.add(scoreData);
|
||||
this.addEventDetail.scoreData = scoreData;
|
||||
this.el.emit('leaderboardscoreadded', this.addEventDetail, false);
|
||||
},
|
||||
|
||||
fetchScores: function (challengeId) {
|
||||
const state = this.el.sceneEl.systems.state.state;
|
||||
const query = this.db
|
||||
.where('challengeId', '==', challengeId)
|
||||
.where(
|
||||
'difficulty', '==',
|
||||
state.menuSelectedChallenge.id
|
||||
? state.menuSelectedChallenge.difficulty
|
||||
: state.challenge.difficulty)
|
||||
.orderBy('score', 'desc')
|
||||
.orderBy('time', 'asc')
|
||||
.limit(10);
|
||||
query.get().then(snapshot => {
|
||||
this.eventDetail.challengeId = challengeId;
|
||||
this.scores.length = 0;
|
||||
if (!snapshot.empty) {
|
||||
snapshot.forEach(score => this.scores.push(score.data()));
|
||||
}
|
||||
this.el.sceneEl.emit('leaderboard', this.eventDetail, false);
|
||||
}).catch(e => {
|
||||
console.error('[firestore]', e);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Is high score?
|
||||
*/
|
||||
checkLeaderboardQualify: function () {
|
||||
const state = this.el.sceneEl.systems.state.state;
|
||||
const score = state.score.score;
|
||||
|
||||
// If less than 10, then automatic high score.
|
||||
if (this.scores.length < NUM_SCORES_DISPLAYED) {
|
||||
this.qualifyingIndex = this.scores.length;
|
||||
this.el.sceneEl.emit('leaderboardqualify', this.scores.length, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if overtook any existing high score.
|
||||
for (let i = 0; i < this.scores.length; i++) {
|
||||
if (score > this.scores[i].score) {
|
||||
this.qualifyingIndex = i;
|
||||
this.el.sceneEl.emit('leaderboardqualify', i, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AFRAME.registerComponent('leaderboard-title', {
|
||||
schema: {
|
||||
leaderboardQualified: {default: false}
|
||||
},
|
||||
|
||||
update: function () {
|
||||
const value = this.data.leaderboardQualified
|
||||
? 'NEW HIGH SCORE'
|
||||
: 'LEADERBOARD';
|
||||
this.el.setAttribute('text', 'value', value);
|
||||
}
|
||||
});
|
||||
@@ -24,6 +24,7 @@ AFRAME.registerComponent('search', {
|
||||
},
|
||||
|
||||
superkeyboardchange: bindEvent(function (evt) {
|
||||
if (evt.target !== this.el) { return; }
|
||||
this.search(evt.detail.value);
|
||||
}),
|
||||
|
||||
|
||||
17
src/components/visible-raycastable.js
Normal file
17
src/components/visible-raycastable.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Couple visibility and raycastability.
|
||||
*/
|
||||
AFRAME.registerComponent('visible-raycastable', {
|
||||
schema: {
|
||||
default: true
|
||||
},
|
||||
|
||||
update: function () {
|
||||
this.el.object3D.visible = this.data;
|
||||
if (this.data) {
|
||||
this.el.setAttribute('raycastable', '');
|
||||
} else {
|
||||
this.el.removeAttribute('raycastable', '');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -21,6 +21,7 @@
|
||||
bind__beat-loader="challengeId: challenge.id; difficulty: menuSelectedChallenge.difficulty; isPlaying: isPlaying; menuSelectedChallengeId: menuSelectedChallenge.id"
|
||||
bind__gameover="isGameOver: isGameOver"
|
||||
bind__intro-song="isPlaying: menuActive && !menuSelectedChallenge.id; isSearching: isSearching"
|
||||
bind__leaderboard="isVictory: isVictory; menuSelectedChallengeId: menuSelectedChallenge.id; challengeId: challenge.id"
|
||||
bind__overlay="enabled: !isPlaying"
|
||||
bind__song="challengeId: challenge.id; isPlaying: isPlaying; isBeatsPreloaded: challenge.isBeatsPreloaded; isGameOver: isGameOver; isVictory: isVictory"
|
||||
bind__song-preview-system="challengeId: challenge.id; isSearching: isSearching; isSongLoading: isSongLoading; selectedChallengeId: menuSelectedChallenge.id"
|
||||
@@ -33,6 +34,7 @@
|
||||
debug-state
|
||||
effect-bloom="strength: 1"
|
||||
gpu-preloader
|
||||
leaderboard="apiKey: AIzaSyBCnpzND3eN37CBSu1bSYfaKQoe6yD3SnY; authDomain: supersaberrr.firebaseapp.com; databaseURL: https://supersaberrr.firebaseio.com; projectId: supersaberrr; storageBucket: supersaberrr.appspot.com; messagingSenderId: 172125624222"
|
||||
loading-screen="backgroundColor: #000;"
|
||||
overlay="objects: .overlay"
|
||||
pool__beat-arrow-blue="mixin: arrowBlueBeat; size: 10; container: #beatContainer"
|
||||
@@ -74,6 +76,7 @@
|
||||
{% include './templates/tutorial.html' %}
|
||||
{% include './templates/gameMenu.html' %}
|
||||
{% include './templates/victory.html' %}
|
||||
{% include './templates/leaderboard.html' %}
|
||||
|
||||
<!-- Wrong + miss beat visual indicators. -->
|
||||
<a-entity id="badContainer" bind__visible="isPlaying">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
var utils = require('../utils');
|
||||
|
||||
const challengeDataStore = {};
|
||||
const NUM_LEADERBOARD_DISPLAY = 10;
|
||||
const SEARCH_PER_PAGE = 6;
|
||||
const SONG_NAME_TRUNCATE = 24;
|
||||
const SONG_SUB_NAME_TRUNCATE = 32;
|
||||
@@ -11,8 +12,8 @@ const DAMAGE_MAX = 10;
|
||||
|
||||
const DEBUG_CHALLENGE = {
|
||||
author: 'Superman',
|
||||
difficulty: 'Normal',
|
||||
id: '517',
|
||||
difficulty: 'Expert',
|
||||
id: '31',
|
||||
image: 'assets/img/molerat.jpg',
|
||||
songName: 'Friday',
|
||||
songLength: 100,
|
||||
@@ -57,6 +58,10 @@ AFRAME.registerState({
|
||||
isSongFetching: false, // Fetching stage.
|
||||
isSongLoading: false, // Either fetching or decoding.
|
||||
isVictory: false, // Victory screen.
|
||||
leaderboard: [],
|
||||
leaderboardFetched: false,
|
||||
leaderboardQualified: false,
|
||||
leaderboardText: '',
|
||||
menuActive: true, // Main menu active.
|
||||
menuDifficulties: [], // List of strings of available difficulties for selected.
|
||||
menuSelectedChallenge: { // Currently selected challenge in the main menu.
|
||||
@@ -207,6 +212,7 @@ AFRAME.registerState({
|
||||
Object.assign(state.menuSelectedChallenge, DEBUG_CHALLENGE);
|
||||
Object.assign(state.challenge, DEBUG_CHALLENGE);
|
||||
state.isVictory = true;
|
||||
state.leaderboardQualified = true;
|
||||
state.menuActive = false;
|
||||
state.score.accuracy = 74.99;
|
||||
state.score.beatsHit = 125;
|
||||
@@ -228,6 +234,7 @@ AFRAME.registerState({
|
||||
state.isPaused = false;
|
||||
state.isSongLoading = true;
|
||||
state.isVictory = false;
|
||||
state.leaderboardQualified = false;
|
||||
},
|
||||
|
||||
gamemenuexit: (state) => {
|
||||
@@ -238,6 +245,7 @@ AFRAME.registerState({
|
||||
state.isVictory = false;
|
||||
state.menuActive = true;
|
||||
state.challenge.id = '';
|
||||
state.leaderboardQualified = false;
|
||||
},
|
||||
|
||||
genreclear: (state) => {
|
||||
@@ -261,6 +269,40 @@ AFRAME.registerState({
|
||||
state.menuSelectedChallenge.id = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* High scores.
|
||||
*/
|
||||
leaderboard: (state, payload) => {
|
||||
state.leaderboard.length = 0;
|
||||
state.leaderboardFetched = true;
|
||||
state.leaderboardText = '';
|
||||
for (let i = 0; i < payload.scores.length; i++) {
|
||||
let score = payload.scores[i];
|
||||
state.leaderboard.push(score);
|
||||
state.leaderboardText += `${score.username}\t${score.score}\n`;
|
||||
}
|
||||
},
|
||||
|
||||
leaderboardqualify: state => {
|
||||
state.leaderboardQualified = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert new score into leaderboard locally.
|
||||
*/
|
||||
leaderboardscoreadded: (state, payload) => {
|
||||
state.leaderboard.splice(payload.index, 0, payload.scoreData);
|
||||
state.leaderboardText = '';
|
||||
for (let i = 0; i < NUM_LEADERBOARD_DISPLAY; i++) {
|
||||
let score = state.leaderboard[i];
|
||||
state.leaderboardText += `${score.username}\t${score.score}\n`;
|
||||
}
|
||||
},
|
||||
|
||||
leaderboardsubmit: state => {
|
||||
state.leaderboardQualified = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Song clicked from menu.
|
||||
*/
|
||||
@@ -284,14 +326,18 @@ AFRAME.registerState({
|
||||
|
||||
computeMenuSelectedChallengeIndex(state);
|
||||
state.isSearching = false;
|
||||
|
||||
clearLeaderboard(state);
|
||||
},
|
||||
|
||||
menuchallengeunselect: state => {
|
||||
state.menuSelectedChallenge.id = '';
|
||||
clearLeaderboard(state);
|
||||
},
|
||||
|
||||
menudifficultyselect: (state, difficulty) => {
|
||||
state.menuSelectedChallenge.difficulty = difficulty;
|
||||
clearLeaderboard(state);
|
||||
},
|
||||
|
||||
minehit: state => {
|
||||
@@ -551,3 +597,10 @@ function computeBeatsText (state) {
|
||||
state.score.beatsText =
|
||||
`${state.score.beatsHit} / ${state.score.beatsMissed + state.score.beatsHit} BEATS`;
|
||||
}
|
||||
|
||||
function clearLeaderboard (state) {
|
||||
state.leaderboard.length = 0;
|
||||
state.leaderboard.__dirty = true;
|
||||
state.leaderboardText = '';
|
||||
state.leaderboardFetched = false;
|
||||
}
|
||||
|
||||
62
src/templates/leaderboard.html
Normal file
62
src/templates/leaderboard.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<a-entity
|
||||
id="leaderboard"
|
||||
class="overlay"
|
||||
position="2.44 1.1 -1.24"
|
||||
rotation="0 -30 0"
|
||||
bind__visible="leaderboardFetched && menuActive || isPaused || isVictory">
|
||||
|
||||
<a-entity
|
||||
mixin="slice"
|
||||
id="leaderboardBackground"
|
||||
position="0 -0.07 0"
|
||||
slice9="left: 70; width: 1.5; height: 1.79; opacity: 0.9"></a-entity>
|
||||
|
||||
<a-entity
|
||||
id="leaderboardTitle"
|
||||
mixin="font"
|
||||
bind__leaderboard-title="leaderboardQualified: leaderboardQualified"
|
||||
text="align: center; width: 2.5; value: LEADERBOARD"
|
||||
position="0 0.540 0.001"></a-entity>
|
||||
|
||||
<a-entity
|
||||
id="leaderboardText"
|
||||
mixin="font"
|
||||
bind__text="value: leaderboardText"
|
||||
bind__visible="!leaderboardQualified && !leaderboardEmpty"
|
||||
text="baseline: top; width: 1; anchor: left; color: #00acfc; wrapCount: 20"
|
||||
position="-0.6 0.35 0.001"></a-entity>
|
||||
|
||||
<a-entity
|
||||
id="leaderboardScores"
|
||||
mixin="font"
|
||||
bind__text="value: leaderboardText"
|
||||
bind__visible="!leaderboardQualified && !leaderboardEmpty"
|
||||
text="baseline: top; align: right; width: 1; anchor: right; color: #fff; wrapCount: 20"
|
||||
position="0.6 0.35 0.001"></a-entity>
|
||||
|
||||
|
||||
<a-entity
|
||||
id="leaderboardEmpty"
|
||||
mixin="font"
|
||||
bind__visible="leaderboard.length === 0"
|
||||
text="align: center; baseline: top; width: 1.6; value: No high scores yet, be the first!"
|
||||
position="0 0.47 0.001"></a-entity>
|
||||
|
||||
<a-entity
|
||||
id="leaderboardKeyboard"
|
||||
bind__super-keyboard="{% if not DEBUG_KEYBOARD %}hand: activeHand === 'left' && '#leftHand' || '#rightHand'; {% endif %}show: leaderboardQualified"
|
||||
super-keyboard="label: Enter yourself to the leaderboard!; inputColor: #00acfc; labelColor: #FFF; width: 1.36; hand: {{ DEBUG_KEYBOARD and '#mouseCursor' or '#rightHand' }}; imagePath: assets/img/keyboard/; font: assets/fonts/Teko-Bold.json; align: center; model: supersaber; keyColor: #fff; injectToRaycasterObjects: false; filters: allupper; keyHoverColor: #fff; maxLength: 15"
|
||||
position="0 -0.160 0.001"
|
||||
proxy-event__change="event: superkeyboardchange; to: a-scene; as: leaderboardusername"
|
||||
keyboard-raycastable="condition: leaderboardQualified">
|
||||
</a-entity>
|
||||
|
||||
<a-entity id="leaderboardSubmit"
|
||||
position="0 -0.730 0.002"
|
||||
mixin="bigMenuButton"
|
||||
bind__visible-raycastable="leaderboardQualified"
|
||||
slice9="width: 1"
|
||||
proxy-event="event: click; to: a-scene; as: leaderboardsubmit">
|
||||
<a-entity mixin="font" text="align: center; color: #AAA; wrapCount: 20; value: SUBMIT HIGH SCORE" position="0 -0.07 0.01"></a-entity>
|
||||
</a-entity>
|
||||
</a-entity>
|
||||
@@ -303,7 +303,7 @@
|
||||
bind__super-keyboard="{% if not DEBUG_KEYBOARD %}hand: activeHand === 'left' && '#leftHand' || '#rightHand'; {% endif %}show: isSearching"
|
||||
super-keyboard="label: Search from over 6000 songs!; inputColor: #fff; labelColor: #FFF; width: 1.2; hand: {{ DEBUG_KEYBOARD and '#mouseCursor' or '#rightHand' }}; imagePath: assets/img/keyboard/; font: assets/fonts/Teko-Bold.json; align: center; model: supersaber; keyColor: #fff; injectToRaycasterObjects: false; filters: allupper; keyHoverColor: #fff"
|
||||
position="0.6 1.1 -1.999"
|
||||
keyboard-raycastable
|
||||
keyboard-raycastable="condition: isSearching"
|
||||
search
|
||||
proxy-event__dismiss="event: superkeyboarddismiss; to: a-scene; as: keyboardclose"
|
||||
proxy-event__accept="event: superkeyboardinput; to: a-scene; as: keyboardclose">
|
||||
|
||||
Reference in New Issue
Block a user