Compare commits
No commits in common. "master" and "898998e48aa40e6f184adb6b9521aacd3cb183a0" have entirely different histories.
master
...
898998e48a
516
index.html
516
index.html
@ -40,11 +40,6 @@
|
||||
animation: cycle_sprites 300ms steps(1) infinite;
|
||||
}
|
||||
|
||||
.opacity-transition {
|
||||
transition-property: opacity;
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes cycle_sprites {
|
||||
0% {
|
||||
background-position: -330px -1371px;
|
||||
@ -78,86 +73,8 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="viewport">
|
||||
<div id="flash" style="z-index:11;
|
||||
background-color: white;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
opacity: 0%;"></div>
|
||||
|
||||
<div id="background" style="
|
||||
left:0px;
|
||||
top:0px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -355px;
|
||||
width: 800px;
|
||||
height: 480px;
|
||||
z-index: -2;
|
||||
">
|
||||
</div>
|
||||
<div id="background2" style="
|
||||
left:800px;
|
||||
top:0px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -355px;
|
||||
width: 800px;
|
||||
height: 480px;
|
||||
z-index: -2;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="plane" style="
|
||||
z-index: 0;
|
||||
">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="game_over" style="
|
||||
left: calc(50vw - 206px);
|
||||
top: 40vh;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -835px;
|
||||
width: 412px;
|
||||
height: 78px;
|
||||
z-index: 10;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="score" style="
|
||||
left:50px;
|
||||
top: 50px;
|
||||
z-index: 10;
|
||||
"></div>
|
||||
|
||||
<div id="get_ready" style="
|
||||
left: calc(50vw - 200px);
|
||||
top: 40vh;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -913px;
|
||||
width: 400px;
|
||||
height: 73px;
|
||||
z-index: 10;
|
||||
">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// POTENTIAL IMPROVEMENTS:
|
||||
// - Use a fixed pool of level objects instead of removing and creating them all the time
|
||||
// - Use a better class hierarchy - there's some code duplication, especially between Spikes and Ground
|
||||
|
||||
// // Setup
|
||||
|
||||
// Scale viewport to fit device width
|
||||
function apply_scale() {
|
||||
var scale = window.innerHeight / 480.0;
|
||||
$("#viewport").css({ "transform": "scale(" + scale + ")", "transform-origin": "0 0" });
|
||||
camera_x = window.innerWidth / 10.0;
|
||||
}
|
||||
|
||||
window.onresize = apply_scale;
|
||||
apply_scale();
|
||||
|
||||
// Game Loop
|
||||
// Setup
|
||||
function timestamp() {
|
||||
return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
|
||||
}
|
||||
@ -172,6 +89,10 @@
|
||||
STARTSCREEN: 1
|
||||
};
|
||||
|
||||
var current_gamestate = GameState.STARTSCREEN;
|
||||
var has_died = false;
|
||||
|
||||
|
||||
function frame() {
|
||||
now = timestamp();
|
||||
dt = dt + Math.min(1, (now - last) / 1000);
|
||||
@ -187,298 +108,35 @@
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
// // Game
|
||||
// Game
|
||||
const GRAVITY = 200
|
||||
const JUMP_VEL = 200
|
||||
const SPIKE_DISTANCE = 250
|
||||
const GROUND_DISTANCE = 800;
|
||||
const INITIAL_SPIKE_DISTANCE = 2000;
|
||||
const FIRST_SPIKE_POSITION = 350;
|
||||
const FIRST_GROUND_POSITION = -1000;
|
||||
const SPIKE_DISTANCE = 400
|
||||
|
||||
// Should be considered a const too, but can't be because it needs to react to viewport resizing
|
||||
var camera_x = window.innerWidth / 10.0;
|
||||
|
||||
// Score
|
||||
var score = 0;
|
||||
var under_spikes_in_previous_frame = false;
|
||||
|
||||
// Level placement utilities
|
||||
var next_spike_location;
|
||||
var next_ground_location;
|
||||
|
||||
var current_gamestate = GameState.STARTSCREEN;
|
||||
|
||||
var has_died = false; // true if the player has ever died during this session
|
||||
var just_died = false; // true from player death to one frame after play death
|
||||
var frame_passed_after_death = false; // true in the frame after the player has died
|
||||
|
||||
// Classes
|
||||
class Bird {
|
||||
constructor() {
|
||||
this.position_x = 100;
|
||||
this.position_y = 150;
|
||||
this.position_y = 100;
|
||||
|
||||
this.width = 88;
|
||||
this.height = 73;
|
||||
|
||||
this.velocity_y = 0;
|
||||
}
|
||||
|
||||
get_center_x() {
|
||||
return this.position_x + this.width / 2.0;
|
||||
}
|
||||
get_center_y() {
|
||||
return this.position_y + this.height / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
class Spikes {
|
||||
constructor(x, y) {
|
||||
this.position_x = x;
|
||||
this.position_y = y;
|
||||
|
||||
this.width = 108;
|
||||
|
||||
this.collision_begin_x = 25;
|
||||
this.collision_end_x = 75;
|
||||
|
||||
this.collision_begin_y = 170;
|
||||
this.collision_end_y = 280;
|
||||
|
||||
this.id = Math.floor(Math.random() * 1000000);
|
||||
|
||||
this.element1 = document.createElement("div");
|
||||
this.element1.id = this.id;
|
||||
|
||||
this.element2 = document.createElement("div");
|
||||
this.element2.id = this.id + 1;
|
||||
|
||||
document.getElementById("viewport").appendChild(this.element1);
|
||||
document.getElementById("viewport").appendChild(this.element2);
|
||||
|
||||
$("#" + this.id).css({
|
||||
"top": (y + 300) + "px",
|
||||
"background-image": "url(sheet.png)",
|
||||
"background-position": "-0px -1757px",
|
||||
"width": "108px",
|
||||
"height": "239px",
|
||||
"z-index": "1"
|
||||
})
|
||||
|
||||
$("#" + (this.id + 1)).css({
|
||||
"top": (y - 100) + "px",
|
||||
"background-image": "url(sheet.png)",
|
||||
"background-position": "-264px -986px",
|
||||
"width": "108px",
|
||||
"height": "239px",
|
||||
"z-index": "1"
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
set_camera_position(x) {
|
||||
$("#" + this.id).css({
|
||||
"left": (this.position_x + x) + "px"
|
||||
})
|
||||
$("#" + (this.id + 1)).css({
|
||||
"left": (this.position_x + x) + "px"
|
||||
})
|
||||
}
|
||||
|
||||
remove() {
|
||||
document.getElementById(this.id).remove();
|
||||
document.getElementById(this.id + 1).remove();
|
||||
constructor() {
|
||||
this.position_x = 350;
|
||||
this.position_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Ground {
|
||||
constructor(x) {
|
||||
this.position_x = x;
|
||||
|
||||
this.width = 800;
|
||||
|
||||
this.id = Math.floor(Math.random() * 1000000);
|
||||
|
||||
this.element1 = document.createElement("div");
|
||||
this.element1.id = this.id;
|
||||
|
||||
document.getElementById("viewport").appendChild(this.element1);
|
||||
|
||||
$("#" + this.id).css({
|
||||
"left": (this.position_x) + "px",
|
||||
"top": "409px",
|
||||
"background-image": "url(sheet.png)",
|
||||
"background-position": "-0px -142px",
|
||||
"width": "800px",
|
||||
"height": "71px",
|
||||
"z-index": "2"
|
||||
});
|
||||
}
|
||||
|
||||
set_camera_position(x) {
|
||||
$("#" + this.id).css({
|
||||
"left": (this.position_x + x) + "px"
|
||||
})
|
||||
}
|
||||
|
||||
remove() {
|
||||
document.getElementById(this.id).remove();
|
||||
}
|
||||
}
|
||||
|
||||
class Digit {
|
||||
constructor(x, y, image_x, image_y, width, height) {
|
||||
this.position_x = x;
|
||||
this.position_y = y;
|
||||
|
||||
this.width = width;
|
||||
this.height = height
|
||||
|
||||
this.id = Math.floor(Math.random() * 1000000);
|
||||
|
||||
this.element1 = document.createElement("div");
|
||||
this.element1.id = this.id;
|
||||
|
||||
document.getElementById("score").appendChild(this.element1);
|
||||
|
||||
$("#" + this.id).css({
|
||||
"left": (this.position_x) + "px",
|
||||
"top": (this.position_y) + "px",
|
||||
"background-image": "url(sheet.png)",
|
||||
"background-position": "-" + image_x + "px -" + image_y + "px",
|
||||
"width": width + "px",
|
||||
"height": height + "px",
|
||||
"z-index": "10"
|
||||
});
|
||||
}
|
||||
|
||||
remove() {
|
||||
document.getElementById(this.id).remove();
|
||||
}
|
||||
}
|
||||
|
||||
function get_digit(val, x, y) {
|
||||
if (val == 0) {
|
||||
return new Digit(x, y, 432, 1743, 53, 78);
|
||||
} else if (val == 1) {
|
||||
return new Digit(x, y, 512, 1093, 37, 76);
|
||||
} else if (val == 2) {
|
||||
return new Digit(x, y, 477, 1350, 51, 77);
|
||||
} else if (val == 3) {
|
||||
return new Digit(x, y, 485, 1679, 51, 78);
|
||||
} else if (val == 4) {
|
||||
return new Digit(x, y, 432, 1537, 55, 76);
|
||||
} else if (val == 5) {
|
||||
return new Digit(x, y, 485, 1823, 50, 76);
|
||||
} else if (val == 6) {
|
||||
return new Digit(x, y, 432, 1885, 53, 77);
|
||||
} else if (val == 7) {
|
||||
return new Digit(x, y, 478, 1173, 51, 76);
|
||||
} else if (val == 8) {
|
||||
return new Digit(x, y, 461, 899, 51, 78);
|
||||
} else if (val == 9) {
|
||||
return new Digit(x, y, 458, 1962, 53, 77);
|
||||
}
|
||||
}
|
||||
|
||||
// Objects
|
||||
let player = new Bird()
|
||||
var spikes = []
|
||||
var grounds = []
|
||||
var score_digits = []
|
||||
|
||||
// Utility functions
|
||||
function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function spawn_spikes() {
|
||||
spikes.push(new Spikes(next_spike_location, randomInt(-100, 100)));
|
||||
next_spike_location += SPIKE_DISTANCE;
|
||||
}
|
||||
|
||||
function spawn_ground() {
|
||||
grounds.push(new Ground(next_ground_location));
|
||||
next_ground_location += GROUND_DISTANCE;
|
||||
}
|
||||
|
||||
function update_score() {
|
||||
score_digits.forEach(function (digit) {
|
||||
digit.remove();
|
||||
})
|
||||
score_digits.length = 0;
|
||||
|
||||
// Could be done with modulo and division but I like this stupid approach:
|
||||
// Turn the score into a string and iterate over its characters
|
||||
var working_x = 0;
|
||||
String(score).split('').map(function (v, i) {
|
||||
var digit = get_digit(v, working_x, 0);
|
||||
score_digits.push(digit);
|
||||
working_x += digit.width;
|
||||
});
|
||||
}
|
||||
|
||||
// Setup the game world by removing old objects and spawning new ones
|
||||
function setupGame() {
|
||||
next_spike_location = FIRST_SPIKE_POSITION;
|
||||
next_ground_location = FIRST_GROUND_POSITION;
|
||||
|
||||
spikes.forEach(function (spike) {
|
||||
spike.remove();
|
||||
})
|
||||
grounds.forEach(function (ground) {
|
||||
ground.remove();
|
||||
})
|
||||
|
||||
spikes.length = 0;
|
||||
grounds.length = 0;
|
||||
|
||||
player.position_x = 100;
|
||||
player.position_y = 150;
|
||||
|
||||
player.velocity_y = 0;
|
||||
|
||||
for (let i = next_spike_location; i < INITIAL_SPIKE_DISTANCE; i += SPIKE_DISTANCE) {
|
||||
spawn_spikes();
|
||||
}
|
||||
for (let i = next_ground_location; i < INITIAL_SPIKE_DISTANCE; i += GROUND_DISTANCE) {
|
||||
spawn_ground();
|
||||
}
|
||||
|
||||
under_spikes_in_previous_frame = false;
|
||||
score = 0;
|
||||
update_score();
|
||||
}
|
||||
|
||||
// Fixed time loop
|
||||
function update(dt) {
|
||||
if (current_gamestate == GameState.PLAYING) {
|
||||
// Remove old spikes and spawn new ones
|
||||
var first_spike = spikes[0];
|
||||
if (first_spike.position_x < player.position_x - camera_x - first_spike.width) {
|
||||
// This spike has just left the view -> remove it
|
||||
first_spike.remove()
|
||||
spikes.shift();
|
||||
|
||||
// Spawn new spikes in front of the player
|
||||
spawn_spikes();
|
||||
}
|
||||
|
||||
// Same for ground
|
||||
var first_ground = grounds[0];
|
||||
if (first_ground.position_x < player.position_x - camera_x - first_ground.width) {
|
||||
// This spike has just left the view -> remove it
|
||||
first_ground.remove()
|
||||
grounds.shift();
|
||||
|
||||
// Spawn new spikes in front of the player
|
||||
spawn_ground();
|
||||
}
|
||||
|
||||
|
||||
// Move the player
|
||||
player.position_y += player.velocity_y * dt;
|
||||
player.position_x += 100 * dt;
|
||||
|
||||
@ -491,60 +149,22 @@
|
||||
// Gravity
|
||||
player.velocity_y += GRAVITY * dt;
|
||||
|
||||
// // Lose conditions
|
||||
// Player hits the ground
|
||||
// Lose condition
|
||||
if (player.position_y > 400) {
|
||||
die();
|
||||
}
|
||||
|
||||
// Player hits a spike
|
||||
var under_spikes_this_frame = false;
|
||||
|
||||
spikes.forEach(function (spike) {
|
||||
if (player.get_center_x() > spike.position_x + spike.collision_begin_x
|
||||
&& player.get_center_x() < spike.position_x + spike.collision_end_x) {
|
||||
under_spikes_this_frame = true;
|
||||
|
||||
// Player is inside the spikes - check the y axis
|
||||
if (!(player.get_center_y() > spike.position_y + spike.collision_begin_y
|
||||
&& player.get_center_y() < spike.position_y + spike.collision_end_y)) {
|
||||
die();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Increase the score if we just left spikes
|
||||
if (under_spikes_in_previous_frame && !under_spikes_this_frame) {
|
||||
score += 1;
|
||||
update_score();
|
||||
}
|
||||
under_spikes_in_previous_frame = under_spikes_this_frame;
|
||||
} else if (current_gamestate == GameState.STARTSCREEN) {
|
||||
// Drop the player to the ground if they're not there yet (after hitting a spike)
|
||||
if (player.position_y < 400 && has_died) {
|
||||
player.velocity_y += GRAVITY * dt;
|
||||
player.position_y += player.velocity_y * dt;
|
||||
}
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// Variable time render loop
|
||||
function render(dt) {
|
||||
// Place the player
|
||||
$("#plane").css({
|
||||
top: player.position_y + "px",
|
||||
left: camera_x + "px"
|
||||
left: player.position_x + "px"
|
||||
});
|
||||
|
||||
// Move the camera by moving everything in the level by the inverse of the player position
|
||||
spikes.forEach(function (spike) {
|
||||
spike.set_camera_position(-player.position_x + camera_x);
|
||||
})
|
||||
grounds.forEach(function (ground) {
|
||||
ground.set_camera_position(-player.position_x + camera_x);
|
||||
})
|
||||
|
||||
// Visibility of start / game over texts
|
||||
if (current_gamestate == GameState.STARTSCREEN) {
|
||||
if (has_died == false) {
|
||||
$("#get_ready").css({ visibility: "visible" });
|
||||
@ -557,48 +177,23 @@
|
||||
$("#get_ready").css({ visibility: "hidden" });
|
||||
$("#game_over").css({ visibility: "hidden" });
|
||||
}
|
||||
|
||||
if (just_died) {
|
||||
// We need to wait for one frame in order to make sure that the flash has been set to full opacity by die().
|
||||
// After waiting for this frame to pass, we can enable the smooth transition to 0% opacity.
|
||||
if (frame_passed_after_death == true) {
|
||||
$("#flash").addClass("opacity-transition");
|
||||
$("#flash").css({
|
||||
"opacity": "0%",
|
||||
});
|
||||
|
||||
frame_passed_after_death = false;
|
||||
just_died = false;
|
||||
} else {
|
||||
frame_passed_after_death = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lose state
|
||||
|
||||
function die() {
|
||||
current_gamestate = GameState.STARTSCREEN;
|
||||
has_died = true;
|
||||
just_died = true;
|
||||
|
||||
// Remove the flash from the opacity-transition class to set it to white immediately
|
||||
$("#flash").removeClass("opacity-transition")
|
||||
$("#flash").css({
|
||||
"opacity": "100%",
|
||||
});
|
||||
|
||||
// Drop the player down with some speed
|
||||
player.velocity_y = 100;
|
||||
}
|
||||
|
||||
// Sets up and starts a new game
|
||||
function restart() {
|
||||
setupGame();
|
||||
player.position_x = 100;
|
||||
player.position_y = 100;
|
||||
|
||||
player.velocity_y = 0;
|
||||
|
||||
current_gamestate = GameState.PLAYING;
|
||||
}
|
||||
|
||||
// Input
|
||||
document.onmousedown = function (evt) {
|
||||
if (current_gamestate == GameState.PLAYING) {
|
||||
// Jump
|
||||
@ -608,10 +203,81 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Start the game loop
|
||||
requestAnimationFrame(frame);
|
||||
</script>
|
||||
|
||||
|
||||
<div id="viewport">
|
||||
<div id="floor" style="
|
||||
left:0px;
|
||||
top:409px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -142px;
|
||||
width: 800px;
|
||||
height: 71px;
|
||||
z-index: -1;
|
||||
">
|
||||
</div>
|
||||
<div id="background" style="
|
||||
left:0px;
|
||||
top:0px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -355px;
|
||||
width: 800px;
|
||||
height: 480px;
|
||||
z-index: -2;
|
||||
">
|
||||
</div>
|
||||
<div id="plane" style="
|
||||
z-index: 0;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="spike_bottom" style="
|
||||
left:300px;
|
||||
top:300px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -1757px;
|
||||
width: 108px;
|
||||
height: 239px;
|
||||
z-index: 1;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="spike_top" style="
|
||||
left:300px;
|
||||
top:-100px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -264px -986px;
|
||||
width: 108px;
|
||||
height: 239px;
|
||||
z-index: 1;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="game_over" style="
|
||||
left:200px;
|
||||
top:200px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -835px;
|
||||
width: 412px;
|
||||
height: 78px;
|
||||
z-index: 1;
|
||||
">
|
||||
</div>
|
||||
|
||||
<div id="get_ready" style="
|
||||
left:200px;
|
||||
top:200px;
|
||||
background-image:url(sheet.png);
|
||||
background-position: -0px -913px;
|
||||
width: 400px;
|
||||
height: 73px;
|
||||
z-index: 1;
|
||||
">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user