js实现重力感应迷宫球滚动小游戏代码

代码语言:html

所属分类:游戏

代码描述:js实现重力感应迷宫球滚动小游戏代码

代码标签: 感应 迷宫 滚动 小游戏

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开


<!DOCTYPE html>
<html lang="en" >

<head>

  <meta charset="UTF-8">

  
  
<style>
body {
  /* https://coolors.co/f06449-ede6e3-7d82b8-36382e-613f75  */
  --background-color: #ede6e3;
  --wall-color: #36382e;
  --joystick-color: #210124;
  --joystick-head-color: #f06449;
  --ball-color: #f06449;
  --end-color: #7d82b8;
  --text-color: #210124;

  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  background-color: var(--background-color);
}

html,
body {
  height: 100%;
  margin: 0;
}

#center {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

#game {
  display: grid;
  grid-template-columns: auto 150px;
  grid-template-rows: 1fr auto 1fr;
  gap: 30px;
  perspective: 600px;
}

#maze {
  position: relative;
  grid-row: 1 / -1;
  grid-column: 1;
  width: 350px;
  height: 315px;
  display: flex;
  justify-content: center;
  align-items: center;
}

#end {
  width: 65px;
  height: 65px;
  border: 5px dashed var(--end-color);
  border-radius: 50%;
}

#joystick {
  position: relative;
  background-color: var(--joystick-color);
  border-radius: 50%;
  width: 50px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 10px 50px;
  grid-row: 2;
}

#joystick-head {
  position: relative;
  background-color: var(--joystick-head-color);
  border-radius: 50%;
  width: 20px;
  height: 20px;
  cursor: grab;

  animation-name: glow;
  animation-duration: 0.6s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  animation-timing-function: ease-in-out;
  animation-delay: 4s;
}

@keyframes glow {
  0% {
    transform: scale(1);
  }
  100% {
    transform: scale(1.2);
  }
}

.joystick-arrow:nth-of-type(1) {
  position: absolute;
  bottom: 55px;

  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;

  border-bottom: 10px solid var(--joystick-color);
}

.joystick-arrow:nth-of-type(2) {
  position: absolute;
  top: 55px;

  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;

  border-top: 10px solid var(--joystick-color);
}

.joystick-arrow:nth-of-type(3) {
  position: absolute;
  left: 55px;

  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;

  border-left: 10px solid var(--joystick-color);
}

.joystick-arrow:nth-of-type(4) {
  position: absolute;
  right: 55px;

  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;

  border-right: 10px solid var(--joystick-color);
}

#note {
  grid-row: 3;
  grid-column: 2;
  text-align: center;
  font-size: 0.8em;
  color: var(--text-color);
  transition: opacity 2s;
}

a:visited {
  color: inherit;
}

.ball {
  position: absolute;
  margin-top: -5px;
  margin-left: -5px;
  border-radius: 50%;
  background-color: var(--ball-color);
  width: 10px;
  height: 10px;
}

.wall {
  position: absolute;
  background-color: var(--wall-color);
  transform-origin: top center;
  margin-left: -5px;
}

.wall::before,
.wall::after {
  display: block;
  content: "";
  width: 10px;
  height: 10px;
  background-color: inherit;
  border-radius: 50%;
  position: absolute;
}

.wall::before {
  top: -5px;
}

.wall::after {
  bottom: -5px;
}

.black-hole {
  position: absolute;
  margin-top: -9px;
  margin-left: -9px;
  border-radius: 50%;
  background-color: black;
  width: 18px;
  height: 18px;
}

#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;
    right: 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;
    right: 10px;
    padding: 25px 130px 25px 25px;
    width: 300px;
    background-color: white;
  }
}
</style>



</head>

<body translate="no" >
  <div id="center">
  <div id="game">
    <div id="maze">
      <div id="end"></div>
    </div>
    <div id="joystick">
      <div class="joystick-arrow"></div>
      <div class="joystick-arrow"></div>
      <div class="joystick-arrow"></div>
      <div class="joystick-arrow"></div>
      <div id="joystick-head"></div>
    </div>
    <div id="note">
      Click the joystick to start!
      <p>Move every ball to the center. Ready for hard mode? Press H</p>
    </div>
  </div>
</div>


  
  
      <script>
/*

If you want to know how this game works, you can find a source code walkthrough video here: https://youtu.be/bTk6dcAckuI

Follow me on twitter for more: https://twitter.com/HunorBorbely

*/

Math.minmax = (value, limit) => {
  return Math.max(Math.min(value, limit), -limit);
};

const distance2D = (p1, p2) => {
  return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2);
};

// Angle between the two points
const getAngle = (p1, p2) => {
  let angle = Math.atan((p2.y - p1.y) / (p2.x - p1.x));
  if (p2.x - p1.x < 0) angle += Math.PI;
  return angle;
};

// The closest a ball and a wall cap can be
const closestItCanBe = (cap, ball) => {
  let angle = getAngle(cap, ball);

  const deltaX = Math.cos(angle) * (wallW / 2 + ballSize / 2);
  const deltaY = Math.sin(angle) * (wallW / 2 + ballSize / 2);

  return { x: cap.x + deltaX, y: cap.y + deltaY };
};

// Roll the ball around the wall cap
const rollAroundCap = (cap, ball) => {
  // The direction the ball can't move any further because the wall holds it back
  let impactAngle = getAngle(ball, cap);

  // The direction the ball wants to move based on it's velocity
  let heading = getAngle(
  { x: 0, y: 0 },
  { x: ball.velocityX, y: ball.velocityY });


  // The angle between the impact direction and the ball's desired direction
  // The smaller this angle is, the bigger the impact
  // The closer it is to 90 degrees the smoother it gets (at 90 there would be no collision)
  let impactHeadingAngle = impactAngle - heading;

  // Velocity distance if not hit would have occurred
  const velocityMagnitude = distance2D(
  { x: 0, y: 0 },
  { x: ball.velocityX, y: ball.velocityY });

  // Velocity component diagonal to the impact
  const velocityMagnitudeDiagonalToTheImpact =
  Math.sin(impactHeadingAngle) * velocityMagnitude;

  // How far should the ball be from the wall cap
  const closestDistance = wallW / 2 + ballSize / 2;

  const rotationAngle = Math.atan(
  velocityMagnitudeDiagonalToTheImpact / closestDistance);


  const deltaFromCap = {
    x: Math.cos(impactAngle + Math.PI - rotationAngle) * closestDistance,
    y: Math.sin(impactAngle + Math.PI - rotationAngle) * closestDistance };


  const x = ball.x;
  const y = ball.y;
  const velocityX = ball.x - (cap.x + deltaFromCap.x);
  const velocityY = ball.y - (cap.y + deltaFromCap.y);
  const nextX = x + velocityX;
  const nextY = y + velocityY;

  return { x, y, velocityX, velocityY, nextX, nextY };
};

// Decreases the absolute value of a number but keeps it's sign, doesn't go below abs 0
const slow = (number, difference) => {
  if (Math.abs(number) <= difference) return 0;
  if (number > difference) return number - difference;
  return number + difference;
};

const mazeElement = document.getElementById("maze");
const joystickHeadElement = document.getElementById("joystick-head");
const noteElement = document.getElementById("note"); // Note element for instructions and game won, game failed texts

let hardMode = false;
let previousTimestamp;
let gameInProgress;
let mouseStartX;
let mouseStartY;
let accelerationX;
let accelerationY;
let frictionX;
let frictionY;

const pathW = 25; // Path width
const wallW = 10; // Wall width
const ballSize = 10; // Width and height of the ball
const holeSize = 18;

const debugMode = false;

let balls = [];
let ballElements = [];
let holeElements = [];

resetGame();

// Draw balls for the first time
balls.forEach(({ x, y }) => {
  const ball = document.createElement("div");
  ball.setAttribute("class", "ball");
  ball.style.cssText = `left: ${x}px; top: ${y}px; `;

  mazeElement.appendChild(ball);
  ballElements.push(ball);
});

// Wall metadata
const walls = [
// Border
{ column: 0, row: 0, horizontal: true, length: 10 },
{ column: 0, row: 0, horizontal: false, length: 9 },
{ column: 0, row: 9, horizontal: true, length: 10 },
{ column: 10, row: 0, horizontal: false, length: 9 },

// Horizontal lines starting in 1st column
{ column: 0, row: 6, horizontal: true, length: 1 },
{ column: 0, row: 8, horizontal: true, length: 1 },

// Horizontal lines starting in 2nd column
{ column: 1, row: 1, horizontal: true, length: 2 },
{ column: 1, row: 7, horizontal: true, length: 1 },

// Horizontal lines starting in 3rd column
{ column: 2, row: 2, horizontal: true, length: 2 },
{ column: 2, row: 4, horizontal: true, length: 1 },
{ column: 2, row: 5, horizontal: true, length: 1 },
{ column: 2, row: 6, horizontal: true, length: 1 },

// Horizontal lines starting in 4th column
{ column: 3, row: 3, horizontal: true, length: 1 },
{ column: 3, row: 8, horizontal: true, length: 3 },

// Horizontal lines starting in 5th column
{ column: 4, row: 6, horizontal: true, length: 1 },

// Horizontal lines starting in 6th column
{ column: 5, row: 2, horizontal: true, length: 2 },
{ column: 5, row: 7, horizontal: true, length: 1 },

// Horizontal lines starting in 7th column
{ column: 6, row: 1, horizontal: true, length: 1 },
{ column: 6, row: 6, horizontal: true, length: 2 },

// Horizontal lines starting in 8th column
{ column: 7, row: 3, horizontal: true, length: 2 },
{ column: 7, row: 7, horizontal: true, length: 2 },

// Horizontal lines starting in 9th column
{ column: 8, row: 1, horizontal: true, length: 1 },
{ column: 8, row: 2, horizontal: true, length: 1 },
{ column: 8, row: 3, horizontal: true, length: 1 },
{ column: 8, row: 4, horizontal: true, length: 2 },
{ column: 8, row: 8, horizontal: true, length: 2 },

// Vertical lines after the 1st column
{ column: 1, row: 1, horizontal: false, length: 2 },
{ column: 1, row: 4, horizontal: false, length: 2 },

// Vertical lines after the 2nd column
{ column: 2, row: 2, horizontal: false, length: 2 },
{ column: 2, row: 5, horizontal: false, length: 1 },
{ column: 2, row: 7, horizontal: false, length: 2 },

// Vertical lines after the 3rd column
{ column: 3, row: 0, horizontal: false, length: 1 },
{ column: 3, row: 4, horizontal: false, length: 1 },
{ column: 3, row: 6, horizontal: false, length: 2 },

// Vertical lines after the 4th column
{ column: 4, row: 1, horizontal: false, length: 2 },
{ column: 4, row: 6, horizontal: false, length: 1 },

// Vertical lines after the 5th column
{ column: 5, row: 0, horizontal: false, length: 2 },
{ column: 5, row: 6, horizontal: false, length: 1 },
{ column: 5, row: 8, horizontal: false, length: 1 },

// Vertical lines after the 6th column
{ column: 6, row: 4, horizontal: false, length: 1 },
{ column: 6, row: 6, horizontal: false, length: 1 },

// Vertical lines after the 7th column
{ column: 7, row: 1, horizontal: false, length: 4 },
{ column: 7, row: 7, horizontal: false, length: 2 },

// Vertical lines after the 8th column
{ column: 8, row: 2, horizontal: false, length: 1 },
{ column: 8, row: 4, horizontal: false, length: 2 },

// Vertical lines after the 9th column
{ column: 9, row: 1, horizontal: false, length: 1 },
{ column: 9, row: 5, horizontal: false, length: 2 }].
map(wall => ({
  x: wall.column * (pathW + wallW),
  y: wall.row * (pathW + wallW),
  horizontal: wall.horizontal,
  length: wall.length * (pathW + wallW) }));


// Draw walls
walls.forEach(({ x, y, horizontal, length }) => {
  const wall = document.createElement("div");
  wall.setAttribute("class", "wall");
  wall.style.cssText = `
      left: ${x}px;
      top: ${y}px;
      width: ${wallW}px;
      height: ${length}px;
      transform: rotate(${horizontal ? -90 : 0}deg);
    `;

  mazeElement.appendChild(wall);
});

const holes = [
{ column: 0, row: 5 },
{ column: 2, row: 0 },
{ column: 2, row: 4 },
{ column: 4, row: 6 },
{ column: 6, row: 2 },
{ column: 6, row: 8 },
{ column: 8, row: 1 },
{ column: 8, row: 2 }].
map(hole => ({
  x: hole.column * (wallW + pathW) + (wallW / 2 + pathW / 2),
  y: hole.row * (wallW + pathW) + (wallW / 2 + pathW / 2) }));


joystickHeadElement.addEventListener("mousedown", function (event) {
  if (!gameInProgress) {
    mouseStartX = event.clientX;
    mouseStartY = event.clientY;
    gameInProgress = true;
    window.requestAnimationFrame(main);
    noteElement.style.opacity = 0;
    joystickHeadElement.style.cssText = `
        animation: none;
        cursor: grabbing;
      `;
  }
});

window.addEventListener("mousemove", function (event) {
  if (gameInProgres.........完整代码请登录后点击上方下载按钮下载查看

网友评论0