vue3实现一个接球小游戏

使用 Vue3 + ts + canvas 实现一个web端接球小游戏,主要交互包括:操作键盘的【<】【>】来滑动手柄去接球、游戏开始、游戏暂停、游戏继续、游戏重新开始、游戏失败,用到的知识包括:ts、canvas绘图、事件监听器的添加与移除,定时器的使用与移除。效果如下图所示:

接球小游戏

详细代码:
模版部分

<template>
  <div class="game">
    <canvas ref="canvasRef" id="breakout-canvas" width="500" height="500"></canvas>
    <div class="btn-box">
      <el-button color="#529b2e" :disabled="['processing', 'pausing'].includes(status)" :icon="Sunrise"
        @click="handleStartGame">{{ status === 'end' ? 'Restart' : 'Start' }} game</el-button>
      <el-button color="#c45656" :disabled="['default', 'end'].includes(status)" :icon="MostlyCloudy"
        @click="handlePauseOrContinueGame">{{ status === 'pausing' ? 'continue' : 'Pause' }} game</el-button>
    </div>
  </div>
</template>

TS部分

<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, toValue } from 'vue'
import { ElMessage } from 'element-plus'
import {
  Sunrise,
  MostlyCloudy,
} from '@element-plus/icons-vue'
const paddleHeight = 10
const paddleWidth = 75
const BallRadius = 10

const canvasRef = ref<HTMLCanvasElement>()
const intervalRef = ref<Number>()
const status = ref<'default' | 'processing' | 'pausing' | 'end'>('default')

// 球的坐标
const ballPosition = reactive<{ x: number; y: number }>({ x: 0, y: 0 })
// 球每次移动的距离
const step = reactive<{ dx: number; dy: number }>({ dx: 2, dy: -2 })

// 手柄的坐标 x 
const paddleX = ref<number>(0)
const rightPress = ref<boolean>(false)
const leftPress = ref<boolean>(false)

const init = () => {
  const { width, height } = toValue(canvasRef)
  paddleX.value = (width - paddleWidth) / 2
  ballPosition.x = width / 2
  ballPosition.y = height - 30
  draw()
}

// 绘制球
const drawBall = () => {
  const { x, y } = toValue(ballPosition)
  const { width, height } = toValue(canvasRef)
  const canvas = canvasRef.value
  const ctx = canvas.getContext('2d')
  // 清除上一个阶段的球,clearRect:指定矩形区域,让清除部分完全透明
  ctx.clearRect(0, 0, width, height)
  ctx.beginPath()
  ctx.arc(x, y, BallRadius, 0, Math.PI * 2, true)
  ctx.fillStyle = '#0095DD'
  ctx.fill()
  ctx.closePath()
}

// 绘制手柄
const drawPaddle = () => {
  const { width, height } = toValue(canvasRef)
  if (toValue(rightPress)) {
    paddleX.value = Math.min(toValue(paddleX) + 7, width - paddleWidth)
  } else if (toValue(leftPress)) {
    paddleX.value = Math.max(toValue(paddleX) - 7, 0)
  }
  const canvas = canvasRef.value
  const ctx = canvas.getContext('2d')
  ctx.rect(paddleX.value, height - paddleHeight, paddleWidth, paddleHeight)
  ctx.fillStyle = "#0095DD"
  ctx.fill()
  ctx.closePath()
}

const draw = () => {
  drawBall()
  drawPaddle()

  const { width, height } = toValue(canvasRef)
  const { x, y } = toValue(ballPosition)
  const { dx, dy } = toValue(step)
  // 判断球的 x 坐标 位置是否到达画布边缘,如果是,则调整step
  if (x + dx > width - BallRadius || x + dx < BallRadius) {
    step.dx = -step.dx
  }
  // 判断球的 y 坐标 位置是否到达画布边缘,如果是,则调整step
  if (y + dy < BallRadius) {
    step.dy = -step.dy
  } else if (y + dy > height - BallRadius) {
    // 球在手柄上,则也继续游戏
    if(x > toValue(paddleX) && x < toValue(paddleX) + paddleWidth){
      step.dy = -step.dy
    } else {
      // 球在手柄外面,游戏结束
      handleGameOver()
      return
    }
  }
  ballPosition.x += step.dx
  ballPosition.y += step.dy
}

const handleStartGame = () => {
  if (toValue(status) === 'end') {
    init()
    step.dx = 2
    step.dy = -2
    status.value = 'default'
    return
  }
  ElMessage.success('游戏开始!')
  status.value = 'processing'
  intervalRef.value = setInterval(() => draw(), 10)
}

const handlePauseOrContinueGame = () => {
  const msg = status.value === 'pausing' ? '继续' : '暂停'
  ElMessage.info(`游戏${msg}`)
  if (status.value === 'pausing') {
    status.value = 'processing'
    intervalRef.value = setInterval(() => draw(), 10)
  } else {
    status.value = 'pausing'
    clearInterval(Number(intervalRef.value))
  }
}

const handleGameOver = () => {
  ElMessage.error('游戏失败!')
  status.value = 'end'
  clearInterval(Number(intervalRef.value))
}

const keyDownHandler = (e: KeyboardEvent) => {
  if (['Right', 'ArrowRight'].includes(e.key)) {
    rightPress.value = true
  } else if (['Left', 'ArrowLeft'].includes(e.key)) {
    leftPress.value = true
  }
}

const keyUpHandler = (e: KeyboardEvent) => {
  if (['Right', 'ArrowRight'].includes(e.key)) {
    rightPress.value = false
  } else if (['Left', 'ArrowLeft'].includes(e.key)) {
    leftPress.value = false
  }
}

onMounted(() => {
  init()
  window.addEventListener('keydown', keyDownHandler, false)
  window.addEventListener('keyup', keyUpHandler, false)
})

onUnmounted(() => {
  clearInterval(Number(intervalRef.value))
  window.removeEventListener('keydown', keyDownHandler, false)
  window.removeEventListener('keyup', keyUpHandler, false)
})
</script>

Scss部分

<style lang="scss" scoped>
.game {
  min-height: 675px;
  height: 100vh;
  width: 100vw;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background-image: url(@/assets/ground.png);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
}

canvas {
  box-shadow: -15px 15px 15px rgba(236, 245, 255, 1);
  background: linear-gradient(230deg,
      #337ecc 0%,
      #d9ecff 100%);
  display: block;
  border-radius: 10px;
}

.btn-box {
  width: 300px;
  margin: 50px auto;
  display: flex;
  justify-content: center;
  align-items: center;
  justify-content: space-between;
}
</style>

同志们,完整的项目地址为:完整代码,本人会不定时的在这个项目中更新一些页面开发,欢迎大家有问题随时咨询奥,一起学习!

相关推荐

  1. vue3实现一个接球游戏

    2024-07-15 14:30:02       18 阅读
  2. Python用Pygame实现一个五子棋游戏

    2024-07-15 14:30:02       50 阅读
  3. arduino ide编写的esp32和st773580*160的一个接球游戏

    2024-07-15 14:30:02       61 阅读
  4. 轻松一下游戏

    2024-07-15 14:30:02       59 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-15 14:30:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-15 14:30:02       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-15 14:30:02       58 阅读
  4. Python语言-面向对象

    2024-07-15 14:30:02       69 阅读

热门阅读

  1. 安装 MySQL与修改配置流程

    2024-07-15 14:30:02       18 阅读
  2. html dialog不显示边框

    2024-07-15 14:30:02       23 阅读
  3. conda

    2024-07-15 14:30:02       25 阅读
  4. 代码随想录算法训练营第三十二天

    2024-07-15 14:30:02       25 阅读
  5. 【并发编程】CPU & IO 密集型

    2024-07-15 14:30:02       17 阅读
  6. python中逻辑运算符and 和 or 的优先级问题。

    2024-07-15 14:30:02       21 阅读
  7. Android 中处理 RGB24 格式数据

    2024-07-15 14:30:02       24 阅读