hytopia
Version:
The HYTOPIA SDK makes it easy for developers to create massively multiplayer games using JavaScript or TypeScript.
249 lines (211 loc) • 5.94 kB
HTML
<!-- Game Start Animation-->
<script>
function formatTime(ms) {
const totalSeconds = Math.floor(ms / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const paddedMinutes = minutes.toString().padStart(2, '0');
const paddedSeconds = seconds.toString().padStart(2, '0');
return `${paddedMinutes}:${paddedSeconds}`;
}
function showGameOver(scoreTime, lastTopScoreTime) {
const gameOver = document.getElementById('game-over');
const scoreValue = document.getElementById('score-value');
const highScoreText = document.getElementById('high-score-text');
scoreValue.textContent = formatTime(scoreTime);
highScoreText.style.display = scoreTime > lastTopScoreTime ? 'block' : 'none';
gameOver.style.opacity = 1;
setTimeout(() => {
gameOver.style.opacity = 0;
}, 3000);
}
function showGameCountdown() {
const el = document.getElementById('countdown');
const show = (text, color) => {
el.style.opacity = 0;
setTimeout(() => {
el.textContent = text;
el.style.color = color;
el.style.opacity = 1;
}, 300);
};
[3, 2, 1].forEach((num, i) => {
setTimeout(() => show(num, '#ff0000'), i * 1000);
});
setTimeout(() => {
show('GO!', '#00ff00');
setTimeout(() => el.style.opacity = 0, 1000);
}, 3000);
}
function updateLeaderboard(scores) {
const entriesDiv = document.getElementById('leaderboard-entries');
entriesDiv.innerHTML = '';
if (!scores.length) {
const noScoresRow = document.createElement('div');
noScoresRow.className = 'leaderboard-row';
noScoresRow.textContent = 'No Top Scores';
noScoresRow.style.display = 'flex';
noScoresRow.style.justifyContent = 'center';
entriesDiv.appendChild(noScoresRow);
return;
}
scores.forEach(({name, score}) => {
const row = document.createElement('div');
row.className = 'leaderboard-row';
const username = document.createElement('span');
username.className = 'username';
username.textContent = name;
const time = document.createElement('span');
time.className = 'time';
time.textContent = formatTime(score);
row.appendChild(username);
row.appendChild(time);
entriesDiv.appendChild(row);
});
}
// Server to client UI data handlers
hytopia.onData(data => {
if (data.type === 'game-end') {
showGameOver(data.scoreTime, data.lastTopScoreTime);
}
if (data.type === 'game-start') {
showGameCountdown();
}
if (data.type === 'leaderboard') {
updateLeaderboard(data.scores);
}
});
// Register in-game scene UI templates, so server can
// instantiate instance with new SceneUI({ templateId: 'join-npc-message', ...etc });
hytopia.registerSceneUITemplate('join-npc-message', () => {
const template = document.getElementById('join-npc-message');
const clone = template.content.cloneNode(true);
return clone;
});
</script>
<!-- Game Start Countdown -->
<div id="countdown"></div>
<!-- Game End Animation -->
<div id="game-over">
<div class="main-text">Game Over!</div>
<div class="score-text">Score: <span id="score-value"></span></div>
<div id="high-score-text">New personal high score!</div>
</div>
<!-- Leaderboard -->
<div class="leaderboard">
<h2>Top Survivors</h2>
<div id="leaderboard-entries" class="leaderboard-entries">
</div>
</div>
<!-- Template for Join NPC Scene UI-->
<template id="join-npc-message">
<div class="join-npc-message">
<h1>Join the game</h1>
<p>Jump on my platform to join the game</p>
<p style="margin-top: 5px;">(WASD to move, Spacebar to jump)</p>
</div>
</template>
<!-- Styles -->
<style>
* {
font-family: Arial, sans-serif;
user-select: none;
}
.join-npc-message {
background: rgba(0, 0, 0, 0.8);
border-radius: 12px;
padding: 12px 20px;
color: white;
text-align: center;
position: relative;
margin-bottom: 15px;
}
.join-npc-message:after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid rgba(0, 0, 0, 0.8);
}
.join-npc-message h1 {
font-size: 18px;
margin: 0 0 8px 0;
}
.join-npc-message p {
font-size: 14px;
margin: 0;
}
#countdown {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 120px;
font-weight: bold;
opacity: 0;
transition: opacity 0.3s;
text-shadow: 2px 2px 8px rgba(0,0,0,0.8);
}
#game-over {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
opacity: 0;
transition: opacity 0.5s;
}
#game-over .main-text {
font-size: 120px;
font-weight: bold;
color: #ff0000;
text-shadow: 2px 2px 8px rgba(0,0,0,0.8);
}
#game-over .score-text {
font-size: 48px;
margin-top: 20px;
color: white;
text-shadow: 2px 2px 8px rgba(0,0,0,0.8);
}
#game-over #high-score-text {
font-size: 36px;
margin-top: 15px;
color: #ffd700;
text-shadow: 2px 2px 8px rgba(0,0,0,0.8);
display: none;
}
.leaderboard {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
border-radius: 12px;
padding: 15px;
color: white;
min-width: 200px;
}
.leaderboard h2 {
font-size: 18px;
margin: 0 0 12px 0;
text-align: center;
}
.leaderboard-entries {
display: flex;
flex-direction: column;
gap: 8px;
}
.leaderboard-row {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.username {
color: #fff;
}
.time {
color: #ffd700;
}
</style>