当前位置:  首页>> 技术小册>> TypeScript和Vue从入门到精通(三)

7.4 实战二:弹球游戏

在前面的章节中,我们已经深入学习了TypeScript和Vue.js的基础知识,包括组件化开发、状态管理、路由配置以及Vuex等高级特性。为了将理论知识转化为实践能力,本章节将引导你通过开发一个简单的弹球游戏来巩固所学,并探索Vue与TypeScript结合在项目中的实际应用。

7.4.1 项目概述

弹球游戏(Ball Bounce Game)是一款经典的休闲游戏,玩家通过控制一个挡板来反弹不断下落的球,以防止其落地。游戏界面简单,但背后涉及到了动画处理、碰撞检测、得分计算等编程概念。使用Vue.js结合TypeScript开发此游戏,不仅可以锻炼我们的前端编程能力,还能加深对Vue响应式系统、组件通信以及TypeScript类型安全的理解。

7.4.2 项目搭建

首先,我们需要使用Vue CLI创建一个新的Vue项目,并配置TypeScript支持。如果你还没有安装Vue CLI,可以通过npm或yarn来安装:

  1. npm install -g @vue/cli
  2. # 或者
  3. yarn global add @vue/cli

然后,创建一个新的Vue项目,并添加TypeScript支持:

  1. vue create ball-bounce-game
  2. # 在提示时选择配置TypeScript

7.4.3 游戏组件设计

为了保持代码的清晰和可维护性,我们将游戏拆分为几个关键组件:

  1. GameContainer:游戏容器组件,负责整个游戏的布局和初始化。
  2. Ball:球组件,控制球的移动、速度以及碰撞检测。
  3. Paddle:挡板组件,用户可以通过键盘控制挡板的左右移动。
  4. ScoreBoard:计分板组件,显示当前得分。

7.4.4 实现细节

7.4.4.1 GameContainer 组件

GameContainer作为游戏的主容器,负责初始化游戏状态、渲染子组件,并处理游戏逻辑。

  1. <template>
  2. <div class="game-container">
  3. <Paddle ref="paddle" />
  4. <Ball ref="ball" />
  5. <ScoreBoard :score="score" />
  6. </div>
  7. </template>
  8. <script lang="ts">
  9. import { defineComponent, ref, onMounted, watch } from 'vue';
  10. import Paddle from './Paddle.vue';
  11. import Ball from './Ball.vue';
  12. import ScoreBoard from './ScoreBoard.vue';
  13. export default defineComponent({
  14. components: { Paddle, Ball, ScoreBoard },
  15. setup() {
  16. const ball = ref(null) as any; // TypeScript需要类型断言
  17. const paddle = ref(null) as any;
  18. const score = ref(0);
  19. onMounted(() => {
  20. // 初始化游戏逻辑,如启动球的运动
  21. if (ball.value && paddle.value) {
  22. ball.value.startGame();
  23. }
  24. });
  25. // 监听球的得分事件
  26. watch(() => ball.value?.score, (newVal) => {
  27. score.value = newVal;
  28. });
  29. return { ball, paddle, score };
  30. }
  31. });
  32. </script>
  33. <style scoped>
  34. .game-container {
  35. position: relative;
  36. width: 100%;
  37. height: 500px;
  38. background-color: #000;
  39. overflow: hidden;
  40. }
  41. </style>
7.4.4.2 Ball 组件

Ball组件负责球的移动、碰撞检测以及得分逻辑。

  1. <template>
  2. <div ref="ballElement" class="ball"></div>
  3. </template>
  4. <script lang="ts">
  5. import { defineComponent, ref, onMounted, watch, computed, nextTick } from 'vue';
  6. interface BallProps {
  7. x: number;
  8. y: number;
  9. speedX: number;
  10. speedY: number;
  11. paddleWidth: number;
  12. paddlePosition: number;
  13. }
  14. export default defineComponent({
  15. props: {
  16. paddleWidth: {
  17. type: Number,
  18. required: true
  19. },
  20. paddlePosition: {
  21. type: Number,
  22. required: true
  23. }
  24. },
  25. setup(props, { emit }) {
  26. const ballElement = ref(null) as any;
  27. const { x, y, speedX, speedY } = ref({
  28. x: 50,
  29. y: 100,
  30. speedX: 3,
  31. speedY: 3
  32. });
  33. const moveBall = () => {
  34. // 更新球的位置
  35. x.value += speedX.value;
  36. y.value += speedY.value;
  37. // 边界碰撞检测
  38. if (y.value > 500 || y.value < 0) {
  39. speedY.value = -speedY.value; // 垂直反弹
  40. }
  41. // 与挡板碰撞检测
  42. if (x.value + 20 > props.paddlePosition && x.value < props.paddlePosition + props.paddleWidth) {
  43. if (y.value + 20 > 470) { // 假设挡板在底部
  44. speedY.value = -speedY.value;
  45. if (Math.abs(x.value - (props.paddlePosition + props.paddleWidth / 2)) < 10) {
  46. // 如果球击中挡板中心,增加速度
  47. speedX *= 1.1;
  48. }
  49. }
  50. }
  51. // 更新DOM
  52. nextTick(() => {
  53. ballElement.value.style.transform = `translate(${x.value}px, ${y.value}px)`;
  54. });
  55. requestAnimationFrame(moveBall);
  56. };
  57. onMounted(() => {
  58. moveBall();
  59. });
  60. return { ballElement, x, y };
  61. }
  62. });
  63. </script>
  64. <style scoped>
  65. .ball {
  66. width: 20px;
  67. height: 20px;
  68. background-color: red;
  69. position: absolute;
  70. border-radius: 50%;
  71. }
  72. </style>

注意:这里的代码简化了部分逻辑,如得分和挡板控制,这些将在Paddle组件中处理。

7.4.4.3 Paddle 组件

Paddle组件负责接收键盘事件,控制挡板的左右移动。

  1. <template>
  2. <div ref="paddleElement" class="paddle" :style="{ left: paddlePosition + 'px' }"></div>
  3. </template>
  4. <script lang="ts">
  5. import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
  6. export default defineComponent({
  7. setup() {
  8. const paddleElement = ref(null) as any;
  9. const paddleWidth = 100;
  10. const paddlePosition = ref(400); // 初始位置
  11. const movePaddle = (event: KeyboardEvent) => {
  12. if (event.key === 'ArrowLeft' && paddlePosition.value > 0) {
  13. paddlePosition.value -= 10;
  14. } else if (event.key === 'ArrowRight' && paddlePosition.value < window.innerWidth - paddleWidth) {
  15. paddlePosition.value += 10;
  16. }
  17. };
  18. onMounted(() => {
  19. window.addEventListener('keydown', movePaddle);
  20. });
  21. onUnmounted(() => {
  22. window.removeEventListener('keydown', movePaddle);
  23. });
  24. return { paddleElement, paddleWidth, paddlePosition };
  25. }
  26. });
  27. </script>
  28. <style scoped>
  29. .paddle {
  30. width: 100px;
  31. height: 20px;
  32. background-color: blue;
  33. position: absolute;
  34. bottom: 0;
  35. left: 0;
  36. }
  37. </style>
7.4.4.4 ScoreBoard 组件

ScoreBoard组件负责显示当前得分。

  1. <template>
  2. <div class="score-board">{{ score }}</div>
  3. </template>
  4. <script lang="ts">
  5. import { defineComponent, PropType } from 'vue';
  6. export default defineComponent({
  7. props: {
  8. score: {
  9. type: Number,
  10. required: true
  11. }
  12. }
  13. });
  14. </script>
  15. <style scoped>
  16. .score-board {
  17. position: absolute;
  18. top: 20px;
  19. left: 50%;
  20. transform: translateX(-50%);
  21. color: white;
  22. font-size: 24px;
  23. font-weight: bold;
  24. }
  25. </style>

7.4.5 总结

通过本章节的


该分类下的相关小册推荐: