在前面的章节中,我们已经深入学习了TypeScript和Vue.js的基础知识,包括组件化开发、状态管理、路由配置以及Vuex等高级特性。为了将理论知识转化为实践能力,本章节将引导你通过开发一个简单的弹球游戏来巩固所学,并探索Vue与TypeScript结合在项目中的实际应用。
弹球游戏(Ball Bounce Game)是一款经典的休闲游戏,玩家通过控制一个挡板来反弹不断下落的球,以防止其落地。游戏界面简单,但背后涉及到了动画处理、碰撞检测、得分计算等编程概念。使用Vue.js结合TypeScript开发此游戏,不仅可以锻炼我们的前端编程能力,还能加深对Vue响应式系统、组件通信以及TypeScript类型安全的理解。
首先,我们需要使用Vue CLI创建一个新的Vue项目,并配置TypeScript支持。如果你还没有安装Vue CLI,可以通过npm或yarn来安装:
npm install -g @vue/cli
# 或者
yarn global add @vue/cli
然后,创建一个新的Vue项目,并添加TypeScript支持:
vue create ball-bounce-game
# 在提示时选择配置TypeScript
为了保持代码的清晰和可维护性,我们将游戏拆分为几个关键组件:
GameContainer作为游戏的主容器,负责初始化游戏状态、渲染子组件,并处理游戏逻辑。
<template>
<div class="game-container">
<Paddle ref="paddle" />
<Ball ref="ball" />
<ScoreBoard :score="score" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, watch } from 'vue';
import Paddle from './Paddle.vue';
import Ball from './Ball.vue';
import ScoreBoard from './ScoreBoard.vue';
export default defineComponent({
components: { Paddle, Ball, ScoreBoard },
setup() {
const ball = ref(null) as any; // TypeScript需要类型断言
const paddle = ref(null) as any;
const score = ref(0);
onMounted(() => {
// 初始化游戏逻辑,如启动球的运动
if (ball.value && paddle.value) {
ball.value.startGame();
}
});
// 监听球的得分事件
watch(() => ball.value?.score, (newVal) => {
score.value = newVal;
});
return { ball, paddle, score };
}
});
</script>
<style scoped>
.game-container {
position: relative;
width: 100%;
height: 500px;
background-color: #000;
overflow: hidden;
}
</style>
Ball组件负责球的移动、碰撞检测以及得分逻辑。
<template>
<div ref="ballElement" class="ball"></div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, watch, computed, nextTick } from 'vue';
interface BallProps {
x: number;
y: number;
speedX: number;
speedY: number;
paddleWidth: number;
paddlePosition: number;
}
export default defineComponent({
props: {
paddleWidth: {
type: Number,
required: true
},
paddlePosition: {
type: Number,
required: true
}
},
setup(props, { emit }) {
const ballElement = ref(null) as any;
const { x, y, speedX, speedY } = ref({
x: 50,
y: 100,
speedX: 3,
speedY: 3
});
const moveBall = () => {
// 更新球的位置
x.value += speedX.value;
y.value += speedY.value;
// 边界碰撞检测
if (y.value > 500 || y.value < 0) {
speedY.value = -speedY.value; // 垂直反弹
}
// 与挡板碰撞检测
if (x.value + 20 > props.paddlePosition && x.value < props.paddlePosition + props.paddleWidth) {
if (y.value + 20 > 470) { // 假设挡板在底部
speedY.value = -speedY.value;
if (Math.abs(x.value - (props.paddlePosition + props.paddleWidth / 2)) < 10) {
// 如果球击中挡板中心,增加速度
speedX *= 1.1;
}
}
}
// 更新DOM
nextTick(() => {
ballElement.value.style.transform = `translate(${x.value}px, ${y.value}px)`;
});
requestAnimationFrame(moveBall);
};
onMounted(() => {
moveBall();
});
return { ballElement, x, y };
}
});
</script>
<style scoped>
.ball {
width: 20px;
height: 20px;
background-color: red;
position: absolute;
border-radius: 50%;
}
</style>
注意:这里的代码简化了部分逻辑,如得分和挡板控制,这些将在Paddle组件中处理。
Paddle组件负责接收键盘事件,控制挡板的左右移动。
<template>
<div ref="paddleElement" class="paddle" :style="{ left: paddlePosition + 'px' }"></div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
export default defineComponent({
setup() {
const paddleElement = ref(null) as any;
const paddleWidth = 100;
const paddlePosition = ref(400); // 初始位置
const movePaddle = (event: KeyboardEvent) => {
if (event.key === 'ArrowLeft' && paddlePosition.value > 0) {
paddlePosition.value -= 10;
} else if (event.key === 'ArrowRight' && paddlePosition.value < window.innerWidth - paddleWidth) {
paddlePosition.value += 10;
}
};
onMounted(() => {
window.addEventListener('keydown', movePaddle);
});
onUnmounted(() => {
window.removeEventListener('keydown', movePaddle);
});
return { paddleElement, paddleWidth, paddlePosition };
}
});
</script>
<style scoped>
.paddle {
width: 100px;
height: 20px;
background-color: blue;
position: absolute;
bottom: 0;
left: 0;
}
</style>
ScoreBoard组件负责显示当前得分。
<template>
<div class="score-board">{{ score }}</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
export default defineComponent({
props: {
score: {
type: Number,
required: true
}
}
});
</script>
<style scoped>
.score-board {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 24px;
font-weight: bold;
}
</style>
通过本章节的