react实现砸地鼠小游戏效果代码

代码语言:html

所属分类:游戏

代码描述:react实现砸地鼠小游戏效果代码

代码标签: 小游戏 效果

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


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

<head>

  <meta charset="UTF-8">

  <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap" rel="stylesheet">


  
  
<style>
:root {
  --controls: hsl(38, 96%, calc((55 + var(--lightness, 0)) * 1%));
  --controls-secondary: hsl(55, 100%, 50%);
  --controls-color: hsl(0, 0%, 100%);
  --sky: hsl(204, 80%, 80%);
  --grass: hsl(98, 40%, 50%);
  --dirt: hsl(35, 40%, 20%);
}

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin: 0;
  font-family: 'Fredoka One', cursive;
  background: var(--sky);
}

.moles {
  display: inline-grid;
  grid-template-rows: repeat(2, auto);
  grid-template-columns: repeat(3, auto);
  grid-gap: 0 2vmin;
  cursor: none;
}

.moles > *:nth-of-type(4),
.moles > *:nth-of-type(5) {
  transform: translate(50%, -25%);
}

main {
  height: 100vh;
  width: 100vw;
  display: grid;
  place-items: center;
  background: linear-gradient(var(--sky) 0 44%, var(--grass) 44%);
}

button {
  --controls: hsl(38, 96%, calc((55 + var(--lightness, 0)) * 1%));
  background: var(--controls);
  color: var(--controls-color);
  padding: 1rem 2rem;
  font-family: 'Fredoka One', cursive;
  font-weight: bold;
  font-size: 1.75rem;
  border-radius: 1rem;
  border: 4px var(--controls-color) solid;
  white-space: nowrap;
  cursor: pointer;
}

button:hover {
  --lightness: 5;
}
button:active {
  --lightness: -15;
}

.celebration {
  font-size: 4rem;
  line-height: 1;
  margin: 0;
  padding: 0;
  text-transform: uppercase;
  text-align: center;
}

.word {
  display: inline-block;
  white-space: nowrap;
}

.celebration .char {
  display: inline-block;
  color: hsl(calc((360 / var(--char-total)) * var(--char-index)), 70%, 65%);
  -webkit-animation: jump 0.35s calc(var(--char-index, 0) * -1s) infinite;
          animation: jump 0.35s calc(var(--char-index, 0) * -1s) infinite;
}

.countdown-number {
  font-size: 10rem;
  color: var(--dirt);
  -webkit-text-stroke: 0.25rem var(--controls-color);
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 12;
  margin: 0;
  padding: 0;
  transform: translate(-50%, -50%);
  display: 'none';
}

@-webkit-keyframes jump {
  50% {
    transform: translate(0, -25%);
  }
}

@keyframes jump {
  50% {
    transform: translate(0, -25%);
  }
}

.icon-button {
  height: 48px;
  width: 48px;
  outline: transparent;
  background: none;
  border: 0;
  display: grid;
  place-items: center;
  padding: 0;
  margin: 0;
}

.mute-button {
  position: fixed;
  bottom: 0;
  right: 0;
  z-index: 2;
}

.mute-button:hover ~ .mallet,
.end-button:hover ~ .mallet {
  display: none;
}

.end-button {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 2;
}

.game-info {
  position: fixed;
  top: 1rem;
  left: 1rem;
  display: grid;
  grid-template-columns: repeat(2, auto);
  grid-template-rows: repeat(2, auto);
  align-items: center;
  grid-gap: 0.5rem 1rem;
  z-index: 2;
  background: var(--controls-color);
  border: 4px solid var(--controls);
  border-radius: 1rem;
  padding: 1rem;
  width: 190px;
}

.info-screen {
  z-index: 2;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.results {
  background: var(--controls-color);
  padding: 2rem;
  border: 4px solid var(--controls);
  border-radius: 1rem;
}

.info-screen > * + * {
  margin-top: 1rem;
}

.icon {
  fill: hsl(35, 50%, 28%);
  stroke-width: 20px;
  overflow: visible;
  height: 24px;
  width: 24px;
}

@media(min-width: 768px) {
  .end-button {
    top: 1rem;
    right: 1rem;
  }
  .mute-button {
    bottom: 1rem;
    right: 1rem;
  }
  .icon {
    height: 48px;
    width: 48px;
  }
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

.info__text {
  font-size: clamp(1rem, 5vmin, 2rem);
  line-height: 1;
  color: var(--dirt);
  margin: 0;
}

.boring-text {
  font-size: 2rem;
  text-align: center;
}


.title {
  -webkit-text-stroke: 0.1vmin var(--controls-color);
  font-size: 6rem;
  font-weight: bold;
  color: transparent;
  background: linear-gradient(40deg, var(--controls), var(--controls-secondary));
  -webkit-background-clip: text;
  background-clip: text;
  text-align: center;
  display: inline-block;
  line-height: 0.75;
  margin: 0 0 4rem 0;
  padding: 0;
  transform: rotate(-15deg);
}

.title span {
  display: block;
}
.title span:nth-of-type(2) {
  transform: translate(0, -10%) rotate(15deg);
  color: var(--controls);
}
.hole {
  fill: hsl(0, 0%, 12%);
}

.hole__lip {
  fill: hsl(38, 20%, 50%);
}

.mole__feature {
  fill: hsl(0, 0%, 10%);
}

.mole__eyes--crossed {
  display: none;
}

.mole__mole {
  display: none;
}

.specs__lens {
  fill: hsla(198, 80%, calc((80 - (var(--shades, 0) * 75)) * 1%), calc(0.5 + (var(--shades, 0) * 0.5)));
  stroke: hsl(var(--accent), 25%, calc((30 - (var(--shades, 0) * 30)) * 1%));
}

.cap__accent {
  fill: hsl(var(--accent, 10), 80%, 50%);
}

.cap__body {
  fill: hsl(0, 0%, 5%);
}

.specs__glare {
  fill: hsla(0, 0%, 100%, calc(0.5 + (var(--shades, 0) * 0.25)));
}

.specs__bridge {
  stroke: hsl(var(--accent), 25%, calc((30 - (var(--shades, 0) * 30)) * 1%));
}

.mole__hole {
  width: 20vmin;
  height: 20vmin;
  position: relative;
  cursor: none;
}

.mole__hole * {
  cursor: none;
}

.mole__body {
  fill: hsl(var(--hue), calc((10 + (var(--golden, 0) * 40)) * 1%), calc(var(--lightness, 65) * 1%));
}

.mole__white {
  fill: hsl(40, 80%, calc((98 - (var(--golden, 0) * 15)) * 1%));
}

.mole__whiskers {
  stroke: hsl(40, calc((0 + (var(--golden, 0) * 35)) * 1%), calc((5 + (var(--golden, 0) * 40)) * 1%));
}

.mole__shadow {
  fill: hsl(var(--hue), 16%, 43%);
}

.mole__nose {
  fill: hsl(calc(10 + (var(--golden, 0) * 30)), 90%, calc((88 - (var(--golden, 0) * 35)) * 1%));
}

.mole {
  position: absolute;
  height: 100%;
  width: 100%;
}

.mole__whack {
  height: 100%;
  width: 100%;
  border: 0;
  opacity: 0;
  transform: translate(0, 0%);
  position: absolute;
  top: 0;
  left: 0;
}

.mole__points-holder {
  position: absolute;
  transform: rotate(calc(var(--angle, 0) * 1deg));
  transform-origin: 50% 200%;
  pointer-events: none;
  position: fixed;
  z-index: 10;
}

.mole__points {
  font-size: clamp(2rem, 8vmin, 18rem);
  pointer-events: none;
  font-weight: bold;
  color: hsl(var(--accent, 0), 90%, 75%);
  margin: 0;
  transform: translate(-50%, -200%);
  -webkit-text-stroke: 0.1vmin hsl(var(--accent), 50%, 35%);
}

.mallet {
  height: 0px;
  width: 0px;
  background: green;
  pointer-events: none;
  position: fixed;
  top: calc(var(--y) * 1px);
  left: calc(var(--x) * 1px);
  z-index: 10;
  transform: translate(-50%, -50%);
  display: none;
}

.mallet img {
  position: absolute;
  bottom: 0;
  height: 18vmin;
  transform-origin: 75% 85%;
  pointer-events: none;
}

@media (hover: none) {
  .mallet img {
    display: none;
  }
}

.hiscore {
  text-transform: uppercase;
  position: fixed;
  top: 1rem;
  left: 1rem;
  z-index: 2;
}
</style>



</head>

<body  >
  <div id="root"></div>

  
      <script  type="module">
import React, { Fragment, useCallback, useEffect, useState, useRef } from 'https://cdn.skypack.dev/react';
import ReactDOM from 'https://cdn.skypack.dev/react-dom';
import confetti from 'https://cdn.skypack.dev/canvas-confetti';
import Splitting from 'https://cdn.skypack.dev/splitting';
import gsap from 'https://cdn.skypack.dev/gsap';
import T from 'https://cdn.skypack.dev/prop-types';

const malletSrc = '//repo.bfw.wiki/bfwrepo/image/6080b789c7e9f.png';

// Constants
const constants = {
  TIME_LIMIT: 30000,
  MOLE_SCORE: 100,
  POINTS_MULTIPLIER: 0.9,
  TIME_MULTIPLIER: 1.2,
  MOLES: 5,
  REGULAR_SCORE: 100,
  GOLDEN_SCORE: 1000 };


// Custom Hooks
const useAudio = (src, volume = 1) => {
  const [audio, setAudio] = useState(null);
  useEffect(() => {
    const AUDIO = new Audio(src);
    AUDIO.volume = volume;
    setAudio(AUDIO);
  }, [src]);
  return {
    play: () => audio.play(),
    pause: () => audio.pause(),
    stop: () => {
      audio.pause();
      audio.currentTime = 0;
    } };

};

const usePersistentState = (key, initialValue) => {
  const [state, setState] = useState(
  window.localStorage.getItem(key) ?
  JSON.parse(window.localStorage.getItem(key)) :
  initialValue);

  useEffect(() => {
    window.localStorage.setItem(key, state);
  }, [key, state]);
  return [state, setState];
};

// Utils
const generateMoles = () =>
new Array(constants.MOLES).fill().map(() => ({
  speed: gsap.utils.random(0.5, 2),
  delay: gsap.utils.random(0.5, 5),
  points: constants.MOLE_SCORE }));



// Components

const CountDown = ({ fx, onComplete }) => {
  const count = useRef(null);
  const three = useRef(null);
  const two = useRef(null);
  const one = useRef(null);

  useEffect(() => {
    gsap.set([three.current, two.current, one.current], { display: 'none' });
    count.current = gsap.
    timeline({
      delay: 0.5,
      onComplete }).

    set(three.current, { display: 'block' }).
    fromTo(
    three.current,
    {
      scale: 1,
      rotate: gsap.utils.random(-30, 30) },

    {
      scale: 0,
      rotate: gsap.utils.random(-30, 30),
      duration: 1,
      onStart: () => fx() }).


    set(two.current, { display: 'block' }).
    fromTo(
    two.current,
    {
      scale: 1,
      rotate: gsap.utils.random(-30, 30) },

    {
      scale: 0,
      rotate: gsap.utils.random(-30, 30),
      duration: 1,
      onStart: () => fx() }).


    set(one.current, { display: 'block' }).
    fromTo(
    one.current,
    {
      scale: 1,
      rotate: gsap.utils.random(-30, 30) },

    {
      scale: 0,
      rotate: gsap.utils.random(-30, 30),
      duration: 1,
      onStart: () => fx() });



    return () => {
      if (count.current) count.current.kill();
    };
  }, []);
  return /*#__PURE__*/(
    React.createElement(Fragment, null, /*#__PURE__*/
    React.createElement("h2", { ref: three, className: "countdown-number", style: { display: 'none' } }, "3"), /*#__PURE__*/


    React.createElement("h2", { ref: two, className: "countdown-number", style: { display: 'none' } }, "2"), /*#__PURE__*/


    React.createElement("h2", { ref: one, className: "countdown-number", style: { display: 'none' } }, "1")));




};
CountDown.propTypes = {
  fx: T.func.isRequired,
  onComplete: T.func.isRequired };


const FinishScreen = ({ newHigh, onRestart, onReset, result }) => /*#__PURE__*/
React.createElement("div", { className: "info-screen" }, /*#__PURE__*/
React.createElement("div", { className: "results" },
newHigh && /*#__PURE__*/
React.createElement(Fragment, null, /*#__PURE__*/
React.createElement("h2", {
  className: "celebration",
  dangerouslySetInnerHTML: {
    __html: Splitting.html({ content: `New High Score!` }) } }), /*#__PURE__*/



React.createElement("h2", { className: "celebration" }, result)),


!newHigh && /*#__PURE__*/
React.createElement("h2", { className: "info__text boring-text" }, `You Scored ${result}`)), /*#__PURE__*/


React.createElement("button", { onClick: onRestart }, "Play Again"), /*#__PURE__*/
React.createElement("button", { onClick: onReset }, "Main Menu"));



FinishScreen.propTypes = {
  newHigh: T.bool.isRequired,
  onRestart: T.func.isRequired,
  onReset: T.func.isRequired,
  result: T.number.isRequired };


const StartScreen = ({ onStart }) => /*#__PURE__*/
React.createElement("div", { className: "info-screen" }, /*#__PURE__*/
React.createElement("h1", { className: "title" }, /*#__PURE__*/
React.createElement("span", null, "Whac"), /*#__PURE__*/
React.createElement("span", null, "a"), /*#__PURE__*/
React.createElement("span", null, "Mole")), /*#__PURE__*/

React.createElement("button", { onClick: onStart }, "Start Game"));



StartScreen.propTypes = {
  onStart: T.func.isRequired };


const Timer = ({ time, interval = 1000, onEnd }) => {
  const [internalTime, setInternalTime] = useState(time);
  const timerRef = useRef(time);
  const timeRef = useRef(time);
  useEffect(() => {
    if (internalTime === 0 && onEnd) {
      onEnd();
    }
  }, [internalTime, onEnd]);
  useEffect(() => {
    timerRef.current = setInterval(() => {
      setInternalTime(timeRef.current -= interval);
    }, interval);
    return () => {
      clearInterval(timerRef.current);
    };
  }, [interval]);
  return /*#__PURE__*/(
    React.createElement(Fragment, null, /*#__PURE__*/
    React.createElement("svg", { className: "icon", viewBox: "0 0 512 512", width: "100", title: "clock" }, /*#__PURE__*/
    React.createElement("path", { d: "M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z" })), /*#__PURE__*/

    React.createElement("span", { className: "info__text" }, `${internalTime / 1000}s`)));


};

Timer.defaultProps = {
  interval: 1000 };


Timer.propTypes = {
  time: T.number.isRequired,
  interval: T.number,
  onEnd: T.func.isRequired };


// This is the centerpiece of the game.
// It's the most complex component. But don't be scared of it!
const Mole = ({
  active = false,
  loading = false,
  onWhack,
  speed,
  delay,
  points,
  pointsMin = 10 }) =>
{
  const [whacked, setWhacked] = useState(false);
  const delayedRef = useRef(null);
  const pointsRef = useRef(points);
  const buttonRef = useRef(null);
  const capBody = useRef(null);
  const moleRef = useRef(null);
  const capPeak = useRef(null);
  const loadingRef = useRef(null);
  const noseRef = useRef(null);
  const moleContainerRef = useRef(null);
  const faceRef = useRef(null);
  const capRef = useRef(null);
  const specsRef = useRef(null);
  const bobRef = useRef(null);
  const eyesRef = useRef(null);
  const tummyRef = useRef(null);

  // Use a callback to cache the function and share it between effects.
  const setMole = useCallback(
  (
  override,
  accent = 45,
  shades = 1,
  golden = 1,
  hue = 45,
  lightness = 65) =>
  {
    // Give a 1% chance of getting the "Golden" Mole.
    if (Math.random() > 0.99 || override) {
      // Create the "Golden" Mole
      pointsRef.current = constants.GOLDEN_SCORE;
      // Set the specs and cap as displayed
      gsap.set([capRef.current, specsRef.current], {
        display: 'block' });

      // Set specific colors and that the shades/golden are active
      gsap.set(moleContainerRef.current, {
        '--accent': accent,
        '--shades': shades,
        '--golden': golden,
        '--hue': hue,
        '--lightness': lightness });

    } else {
      // Create a "Regular" Mole
      pointsRef.current = constants.REGULAR_SCORE;
      // Set whether Mole has a cap or specs
      gsap.set([capRef.current, specsRef.current], {
        display: () => Math.random() > 0.5 ? 'block' : 'none' });

      // Set random colors for Mole.
      gsap.set(moleContainerRef.current, {
        '--accent': gsap.utils.random(0, 359),
        '--shades': Math.random() > 0.65 ? 1 : 0,
        '--golden': 0,
        '--hue':
        Math.random() > 0.5 ?
        gsap.utils.random(185, 215) :
        gsap.utils.random(30, 50),
        '--lightness': gsap.utils.random(45, 75) });

    }
  },
  []);


  // Use an effect to get the Mole moving
  useEffect(() => {
    // Set the Mole position and overlay button to underground
    gsap.set([moleRef.current, buttonRef.current], {
      yPercent: 100 });

    // Show Mole
    gsap.set(moleRef.current, { display: 'block' });
    // Create the bobbing timeline and store a ref so we can kill it on unmount.
    // Timeline behavior defined by props
    if (active) {
      // Set characteristics for the Mole.
      setMole();
      bobRef.current = gsap.to([buttonRef.current, moleRef.current], {
        yPercent: 0,
        duration: speed,
        yoyo: true,
        repeat: -1,
        delay,
        repeatDelay: delay,
        onRepeat: () => {
          pointsRef.current = Math.floor(
          Math.max(pointsRef.current * constants.POINTS_MULTIPLIER, pointsMin));

        } });

    }
    // Cleanup the timeline on unmount
    return () => {
      if (bobRef.current) bobRef.current.kill();
    };
  }, [active, delay, pointsMin, speed, setMole]);

  // When a Mole is whacked, animate it underground
  // Swap out the Mole style, reset it, and speed up the bobbing timeline.
  useEffect(() => {
    if (whacked) {
      // Render something in the body
      bobRef.current.pause();
      gsap.to([moleRef.current, buttonRef.current], {
        yPercent: 100,
        duration: 0.1,
        onComplete: () => {
          delayedRef.current = gsap.delayedCall(gsap.utils.random(1, 3), () => {
            setMole();
            setWhacked(false);
            bobRef.current.
            restart().
            timeScale(bobRef.current.timeScale() * constants.TIME_MULTIPLIER);
          });
        } });

    }
    // If the delayed restart isn't started and we unmount, it will need cleaning up.
    return () => {
      if (delayedRef.current) delayedRef.current.kill();
    };
  }, [whacked, setMole]);

  // If a Mole is set to loading, play the loading animation version
  useEffect(() => {
    if (loading) {
      setMole(true, 10, 1, 0, 200, 70);
      loadingRef.current = gsap.
      timeline({
        repeat: -1,
        repeatDelay: 1 })

      // Shooting up!
      .to(moleRef.current, {
        yPercent: 5,
        ease: 'back.out(1)' }).

      to(
      capRef.current,
      {
        yPercent: -15,
        duration: 0.1,
        repeat: 1,
        yoyo: true },

      '>-0.2')

      // Side to side
      .to([capBody.current, faceRef.current], {
        xPercent: 10 }).

      to(
      capPeak.current,
      {
        xPercent: -10 },

      '<').

      to(
      [eyesRef.current, specsRef.current, tummyRef.current],
      {
        xPercent: 8 },

      '<').

      to(
      noseRef.current,
      {
        xPercent: 25 },

      '<').

      to([faceRef.current, capBody.current], {
        xPercent: -10,
        duration: 0.75 }).

      to(
      capPeak.current,
      {
        xPercent: 28,
        duration: 0.5 },

      '<').

      to(
      [eyesRef.current, specsRef.current, tummyRef.current],
      {
        xPercent: -8,
        duration: 0.75 },

      '<').

      to(
      noseRef.current,
      {
        xPercent: -25,
        duration: 0.75 },

      '<').

      to(moleRef.current, {
        yPercent: 100,
        delay: 0.2,
        ease: 'power4.in' }).

      to(
      capRef.current,
      {
        yPercent: -15,
        duration: 0.2,
        ease: 'power4.in' },

      '<+0.05');

    }
    return () => {
      gsap.set(
      [
      capRef.current,
      capPeak.current,
      capBody.current,
      faceRef.current,
      noseRef.current,
      eyesRef.current,
      specsRef.current,
      tummyRef.current],

      {
        xPercent: 0,
        yPercent: 0 });


      if (loadingRef.current) loadingRef.current.kill();
    };
  }, [loading]);

  // To render the score, we don't need React elements.
  // We can render them straight to the DOM and remove them once they've animated.
  // Alternatively, we could use a React DOM Portal. However, our element has
  // a short lifespan and doesn't update, etc.
  const renderScore = (x, y) => {
    const SCORE_HOLDER = document.createElement('div');
    SCORE_HOLDER.className = 'mole__points-holder';
    const SCORE = document.createElement('div');
    SCORE.className = 'mole__points';
    SCORE.innerText = pointsRef.current;
    SCORE_HOLDER.appendChild(SCORE);
    document.body.appendChild(SCORE_HOLDER);
    gsap.set(SCORE_HOLDER, {
      '--angle': gsap.utils.random(-35, 35),
      '--accent': gsap.utils.random(0, 359) });

    gsap.
    timeline({
      onComplete: () => SCORE_HOLDER.remove() }).........完整代码请登录后点击上方下载按钮下载查看

网友评论0