当前位置:网站首页>分享一个好玩的JS小游戏
分享一个好玩的JS小游戏
2022-07-20 03:33:00 【老电影故事】
前言
一个js的忍者小游戏
话不多说
如图所示
简单好玩 打发时间
代码
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>忍者大闯关</title>
<link rel="stylesheet" href="./css.css">
</head>
<body>
<div class="container">
<div id="score"></div>
<canvas id="game" width="375" height="375"></canvas>
<div id="introduction">按住鼠标伸出一根棍子</div>
<div id="perfect">双倍 得分</div>
<button id="restart">再玩一次</button>
</div>
<script src="./js.js"></script>
</body>
</html>
CSS
html, body {
height: 100%;
margin: 0;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
cursor: pointer;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
#score {
position: absolute;
top: 30px;
right: 30px;
font-size: 2em;
font-weight: 900;
}
#introduction {
width: 200px;
height: 150px;
position: absolute;
font-weight: 600;
font-size: 0.8em;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
text-align: center;
transition: opacity 2s;
}
#restart {
width: 120px;
height: 120px;
position: absolute;
border-radius: 50%;
color: white;
background-color: red;
border: none;
font-weight: 700;
font-size: 1.2em;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
display: none;
cursor: pointer;
}
#perfect {
position: absolute;
opacity: 0;
transition: opacity 2s;
}
#youtube, #youtube-card {
display: none;
}
@media (min-height: 425px) {
/** Youtube logo by https://codepen.io/alvaromontoro */
#youtube {
z-index: 2;
display: block;
width: 100px;
height: 70px;
position: absolute;
bottom: 20px;
left: 20px;
background: red;
border-radius: 50% / 11%;
transform: scale(0.8);
transition: transform 0.5s;
}
#youtube:hover, #youtube:focus {
transform: scale(0.9);
}
#youtube::before {
content: "";
display: block;
position: absolute;
top: 7.5%;
left: -6%;
width: 112%;
height: 85%;
background: red;
border-radius: 9% / 50%;
}
#youtube::after {
content: "";
display: block;
position: absolute;
top: 20px;
left: 40px;
width: 45px;
height: 30px;
border: 15px solid transparent;
box-sizing: border-box;
border-left: 30px solid white;
}
#youtube span {
font-size: 0;
position: absolute;
width: 0;
height: 0;
overflow: hidden;
}
#youtube:hover + #youtube-card {
display: block;
position: absolute;
bottom: 12px;
left: 10px;
padding: 25px 25px 25px 130px;
width: 300px;
background-color: white;
}
}
// Extend the base functionality of JavaScript
Array.prototype.last = function () {
return this[this.length - 1];
};
// A sinus function that acceps degrees instead of radians
Math.sinus = function (degree) {
return Math.sin((degree / 180) * Math.PI);
};
// Game data
let phase = "waiting"; // waiting | stretching | turning | walking | transitioning | falling
let lastTimestamp; // The timestamp of the previous requestAnimationFrame cycle
let heroX; // Changes when moving forward
let heroY; // Only changes when falling
let sceneOffset; // Moves the whole game
let platforms = [];
let sticks = [];
let trees = [];
// Todo: Save high score to localStorage (?)
let score = 0;
// Configuration
const canvasWidth = 375;
const canvasHeight = 375;
const platformHeight = 100;
const heroDistanceFromEdge = 10; // While waiting
const paddingX = 100; // The waiting position of the hero in from the original canvas size
const perfectAreaSize = 10;
// The background moves slower than the hero
const backgroundSpeedMultiplier = 0.2;
const hill1BaseHeight = 100;
const hill1Amplitude = 10;
const hill1Stretch = 1;
const hill2BaseHeight = 70;
const hill2Amplitude = 20;
const hill2Stretch = 0.5;
const stretchingSpeed = 4; // Milliseconds it takes to draw a pixel
const turningSpeed = 4; // Milliseconds it takes to turn a degree
const walkingSpeed = 4;
const transitioningSpeed = 2;
const fallingSpeed = 2;
const heroWidth = 17; // 24
const heroHeight = 30; // 40
const canvas = document.getElementById("game");
canvas.width = window.innerWidth; // Make the Canvas full screen
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
const introductionElement = document.getElementById("introduction");
const perfectElement = document.getElementById("perfect");
const restartButton = document.getElementById("restart");
const scoreElement = document.getElementById("score");
// Initialize layout
resetGame();
// Resets game variables and layouts but does not start the game (game starts on keypress)
function resetGame() {
// Reset game progress
phase = "waiting";
lastTimestamp = undefined;
sceneOffset = 0;
score = 0;
introductionElement.style.opacity = 1;
perfectElement.style.opacity = 0;
restartButton.style.display = "none";
scoreElement.innerText = score;
// The first platform is always the same
// x + w has to match paddingX
platforms = [{
x: 50, w: 50 }];
generatePlatform();
generatePlatform();
generatePlatform();
generatePlatform();
sticks = [{
x: platforms[0].x + platforms[0].w, length: 0, rotation: 0 }];
trees = [];
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
generateTree();
heroX = platforms[0].x + platforms[0].w - heroDistanceFromEdge;
heroY = 0;
draw();
}
function generateTree() {
const minimumGap = 30;
const maximumGap = 150;
// X coordinate of the right edge of the furthest tree
const lastTree = trees[trees.length - 1];
let furthestX = lastTree ? lastTree.x : 0;
const x =
furthestX +
minimumGap +
Math.floor(Math.random() * (maximumGap - minimumGap));
const treeColors = ["#6D8821", "#8FAC34", "#98B333"];
const color = treeColors[Math.floor(Math.random() * 3)];
trees.push({
x, color });
}
function generatePlatform() {
const minimumGap = 40;
const maximumGap = 200;
const minimumWidth = 20;
const maximumWidth = 100;
// X coordinate of the right edge of the furthest platform
const lastPlatform = platforms[platforms.length - 1];
let furthestX = lastPlatform.x + lastPlatform.w;
const x =
furthestX +
minimumGap +
Math.floor(Math.random() * (maximumGap - minimumGap));
const w =
minimumWidth + Math.floor(Math.random() * (maximumWidth - minimumWidth));
platforms.push({
x, w });
}
resetGame();
// If space was pressed restart the game
window.addEventListener("keydown", function (event) {
if (event.key == " ") {
event.preventDefault();
resetGame();
return;
}
});
window.addEventListener("mousedown", function (event) {
if (phase == "waiting") {
lastTimestamp = undefined;
introductionElement.style.opacity = 0;
phase = "stretching";
window.requestAnimationFrame(animate);
}
});
window.addEventListener("mouseup", function (event) {
if (phase == "stretching") {
phase = "turning";
}
});
window.addEventListener("resize", function (event) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
draw();
});
window.requestAnimationFrame(animate);
// The main game loop
function animate(timestamp) {
if (!lastTimestamp) {
lastTimestamp = timestamp;
window.requestAnimationFrame(animate);
return;
}
switch (phase) {
case "waiting":
return; // Stop the loop
case "stretching": {
sticks.last().length += (timestamp - lastTimestamp) / stretchingSpeed;
break;
}
case "turning": {
sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
if (sticks.last().rotation > 90) {
sticks.last().rotation = 90;
const [nextPlatform, perfectHit] = thePlatformTheStickHits();
if (nextPlatform) {
// Increase score
score += perfectHit ? 2 : 1;
scoreElement.innerText = score;
if (perfectHit) {
perfectElement.style.opacity = 1;
setTimeout(() => (perfectElement.style.opacity = 0), 1000);
}
generatePlatform();
generateTree();
generateTree();
}
phase = "walking";
}
break;
}
case "walking": {
heroX += (timestamp - lastTimestamp) / walkingSpeed;
const [nextPlatform] = thePlatformTheStickHits();
if (nextPlatform) {
// If hero will reach another platform then limit it's position at it's edge
const maxHeroX = nextPlatform.x + nextPlatform.w - heroDistanceFromEdge;
if (heroX > maxHeroX) {
heroX = maxHeroX;
phase = "transitioning";
}
} else {
// If hero won't reach another platform then limit it's position at the end of the pole
const maxHeroX = sticks.last().x + sticks.last().length + heroWidth;
if (heroX > maxHeroX) {
heroX = maxHeroX;
phase = "falling";
}
}
break;
}
case "transitioning": {
sceneOffset += (timestamp - lastTimestamp) / transitioningSpeed;
const [nextPlatform] = thePlatformTheStickHits();
if (sceneOffset > nextPlatform.x + nextPlatform.w - paddingX) {
// Add the next step
sticks.push({
x: nextPlatform.x + nextPlatform.w,
length: 0,
rotation: 0
});
phase = "waiting";
}
break;
}
case "falling": {
if (sticks.last().rotation < 180)
sticks.last().rotation += (timestamp - lastTimestamp) / turningSpeed;
heroY += (timestamp - lastTimestamp) / fallingSpeed;
const maxHeroY =
platformHeight + 100 + (window.innerHeight - canvasHeight) / 2;
if (heroY > maxHeroY) {
restartButton.style.display = "block";
return;
}
break;
}
default:
throw Error("Wrong phase");
}
draw();
window.requestAnimationFrame(animate);
lastTimestamp = timestamp;
}
// Returns the platform the stick hit (if it didn't hit any stick then return undefined)
function thePlatformTheStickHits() {
if (sticks.last().rotation != 90)
throw Error(`Stick is ${
sticks.last().rotation}°`);
const stickFarX = sticks.last().x + sticks.last().length;
const platformTheStickHits = platforms.find(
(platform) => platform.x < stickFarX && stickFarX < platform.x + platform.w
);
// If the stick hits the perfect area
if (
platformTheStickHits &&
platformTheStickHits.x + platformTheStickHits.w / 2 - perfectAreaSize / 2 <
stickFarX &&
stickFarX <
platformTheStickHits.x + platformTheStickHits.w / 2 + perfectAreaSize / 2
)
return [platformTheStickHits, true];
return [platformTheStickHits, false];
}
function draw() {
ctx.save();
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
drawBackground();
// Center main canvas area to the middle of the screen
ctx.translate(
(window.innerWidth - canvasWidth) / 2 - sceneOffset,
(window.innerHeight - canvasHeight) / 2
);
// Draw scene
drawPlatforms();
drawHero();
drawSticks();
// Restore transformation
ctx.restore();
}
restartButton.addEventListener("click", function (event) {
event.preventDefault();
resetGame();
restartButton.style.display = "none";
});
function drawPlatforms() {
platforms.forEach(({
x, w }) => {
// Draw platform
ctx.fillStyle = "black";
ctx.fillRect(
x,
canvasHeight - platformHeight,
w,
platformHeight + (window.innerHeight - canvasHeight) / 2
);
// Draw perfect area only if hero did not yet reach the platform
if (sticks.last().x < x) {
ctx.fillStyle = "red";
ctx.fillRect(
x + w / 2 - perfectAreaSize / 2,
canvasHeight - platformHeight,
perfectAreaSize,
perfectAreaSize
);
}
});
}
function drawHero() {
ctx.save();
ctx.fillStyle = "black";
ctx.translate(
heroX - heroWidth / 2,
heroY + canvasHeight - platformHeight - heroHeight / 2
);
// Body
drawRoundedRect(
-heroWidth / 2,
-heroHeight / 2,
heroWidth,
heroHeight - 4,
5
);
// Legs
const legDistance = 5;
ctx.beginPath();
ctx.arc(legDistance, 11.5, 3, 0, Math.PI * 2, false);
ctx.fill();
ctx.beginPath();
ctx.arc(-legDistance, 11.5, 3, 0, Math.PI * 2, false);
ctx.fill();
// Eye
ctx.beginPath();
ctx.fillStyle = "white";
ctx.arc(5, -7, 3, 0, Math.PI * 2, false);
ctx.fill();
// Band
ctx.fillStyle = "red";
ctx.fillRect(-heroWidth / 2 - 1, -12, heroWidth + 2, 4.5);
ctx.beginPath();
ctx.moveTo(-9, -14.5);
ctx.lineTo(-17, -18.5);
ctx.lineTo(-14, -8.5);
ctx.fill();
ctx.beginPath();
ctx.moveTo(-10, -10.5);
ctx.lineTo(-15, -3.5);
ctx.lineTo(-5, -7);
ctx.fill();
ctx.restore();
}
function drawRoundedRect(x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.arcTo(x, y + height, x + radius, y + height, radius);
ctx.lineTo(x + width - radius, y + height);
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
ctx.lineTo(x + width, y + radius);
ctx.arcTo(x + width, y, x + width - radius, y, radius);
ctx.lineTo(x + radius, y);
ctx.arcTo(x, y, x, y + radius, radius);
ctx.fill();
}
function drawSticks() {
sticks.forEach((stick) => {
ctx.save();
// Move the anchor point to the start of the stick and rotate
ctx.translate(stick.x, canvasHeight - platformHeight);
ctx.rotate((Math.PI / 180) * stick.rotation);
// Draw stick
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(0, 0);
ctx.lineTo(0, -stick.length);
ctx.stroke();
// Restore transformations
ctx.restore();
});
}
function drawBackground() {
// Draw sky
var gradient = ctx.createLinearGradient(0, 0, 0, window.innerHeight);
gradient.addColorStop(0, "#BBD691");
gradient.addColorStop(1, "#FEF1E1");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);
// Draw hills
drawHill(hill1BaseHeight, hill1Amplitude, hill1Stretch, "#95C629");
drawHill(hill2BaseHeight, hill2Amplitude, hill2Stretch, "#659F1C");
// Draw trees
trees.forEach((tree) => drawTree(tree.x, tree.color));
}
// A hill is a shape under a stretched out sinus wave
function drawHill(baseHeight, amplitude, stretch, color) {
ctx.beginPath();
ctx.moveTo(0, window.innerHeight);
ctx.lineTo(0, getHillY(0, baseHeight, amplitude, stretch));
for (let i = 0; i < window.innerWidth; i++) {
ctx.lineTo(i, getHillY(i, baseHeight, amplitude, stretch));
}
ctx.lineTo(window.innerWidth, window.innerHeight);
ctx.fillStyle = color;
ctx.fill();
}
function drawTree(x, color) {
ctx.save();
ctx.translate(
(-sceneOffset * backgroundSpeedMultiplier + x) * hill1Stretch,
getTreeY(x, hill1BaseHeight, hill1Amplitude)
);
const treeTrunkHeight = 5;
const treeTrunkWidth = 2;
const treeCrownHeight = 25;
const treeCrownWidth = 10;
// Draw trunk
ctx.fillStyle = "#7D833C";
ctx.fillRect(
-treeTrunkWidth / 2,
-treeTrunkHeight,
treeTrunkWidth,
treeTrunkHeight
);
// Draw crown
ctx.beginPath();
ctx.moveTo(-treeCrownWidth / 2, -treeTrunkHeight);
ctx.lineTo(0, -(treeTrunkHeight + treeCrownHeight));
ctx.lineTo(treeCrownWidth / 2, -treeTrunkHeight);
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
}
function getHillY(windowX, baseHeight, amplitude, stretch) {
const sineBaseY = window.innerHeight - baseHeight;
return (
Math.sinus((sceneOffset * backgroundSpeedMultiplier + windowX) * stretch) *
amplitude +
sineBaseY
);
}
function getTreeY(x, baseHeight, amplitude) {
const sineBaseY = window.innerHeight - baseHeight;
return Math.sinus(x) * amplitude + sineBaseY;
}
赶紧跑起来试试吧!!!!
边栏推荐
- 亮点抢先看!2022开放原子全球开源峰会定于7月25-29日在北京举办
- 网络原理之协议详解
- Exch2010:重建整个 DAG
- C语言二叉树+队列实现层次遍历
- ROS(sub,pub)测试 Plotjuggler
- Grouping convolution and deep separable convolution
- Mail push platform - Foreign Trade Promotion
- 【培训课程专用】Trustzone--TZC400设置安全内存
- leetcode 剑指 Offer 32 - II. 从上到下打印二叉树 II
- U.S. lawmakers advocate cracking down on encrypted mining, ringing the alarm of encryption? Only by reducing the carbon footprint can we achieve real value
猜你喜欢
随机推荐
AIOps 还是 APM,企业用户应如何作出选择?
C language realizes binary tree cueing
Unity shader 实现图片带圆角和边线border
【upload靶场12-16】截断、图片马
归并排序针对性刷题
正则表达式
Fruit loops studio music host software Daw fruit software 20.9 Chinese version
Win: use Netsh command to configure port forwarding
[upload range 1-11] basic level: characteristics, analysis and utilization
C语言力扣第35题之搜索插入位置。经典二分法
Calculate the value of any root n
[leetcode] sword finger offer 52 The first common node of two linked lists
An interesting example to illustrate the difference of emplace_back() and push_back()
四层、七层负载均衡的区别(转)
[OBS] text description of QT UI
371 pages of 200000 words 2021 smart city informatization comprehensive construction plan
leetcode 剑指 Offer 50. 第一个只出现一次的字符
DTOs' 3D engine will replace the game engine monster and realize localization
JASMINER X4 became popular overseas and was praised by many well-known bloggers
网站引用百度地图,乱码及无图标