游戏应用实战
游戏应用是移动端最受欢迎的应用类型之一,本文将介绍如何使用 uni-app 开发简单而有趣的游戏应用。
游戏应用概述
uni-app 虽然不是专门的游戏开发框架,但对于一些轻量级的休闲游戏,完全可以胜任。使用 uni-app 开发游戏有以下优势:
- 跨平台:一次开发,多端运行
- 开发效率高:利用 Vue 的响应式特性
- 丰富的 API:可以调用设备的各种能力
- 社区支持:有大量的插件和组件可供使用
技术选型
前端技术栈
- uni-app:跨平台开发框架
- Vue.js:响应式数据绑定
- Canvas:游戏渲染
- Animation:动画效果
后端技术栈(如需要)
- 云函数:处理游戏逻辑
- 云数据库:存储游戏数据和排行榜
- WebSocket:实时多人游戏
案例:贪吃蛇游戏
下面我们将实现一个经典的贪吃蛇游戏,包括游戏界面、控制逻辑和得分系统。
项目结构
├── components // 自定义组件
│ ├── game-controller // 游戏控制器组件
│ └── score-board // 得分板组件
├── pages // 页面文件夹
│ ├── index // 首页(游戏主界面)
│ ├── rank // 排行榜
│ └── settings // 游戏设置
├── static // 静态资源
│ ├── images // 游戏图片资源
│ └── sounds // 游戏音效
├── utils // 工具函数
│ ├── game.js // 游戏核心逻辑
│ └── storage.js // 本地存储工具
├── App.vue // 应用入口
├── main.js // 主入口
├── manifest.json // 配置文件
└── pages.json // 页面配置
核心功能实现
1. 游戏主界面
游戏主界面包含游戏画布、控制按钮和得分显示。
<template>
<view class="game-container">
<!-- 游戏状态提示 -->
<view class="game-status" v-if="!isPlaying">
<view class="status-content">
<text class="status-title">{{ gameOver ? '游戏结束' : '贪吃蛇' }}</text>
<text class="status-score" v-if="gameOver">得分: {{ score }}</text>
<button class="start-btn" @tap="startGame">{{ gameOver ? '再来一局' : '开始游戏' }}</button>
</view>
</view>
<!-- 游戏画布 -->
<canvas
canvas-id="gameCanvas"
id="gameCanvas"
class="game-canvas"
@touchstart="handleTouch"
@touchmove="handleTouch"
></canvas>
<!-- 游戏信息 -->
<view class="game-info">
<view class="score-box">
<text class="score-label">得分</text>
<text class="score-value">{{ score }}</text>
</view>
<view class="high-score-box">
<text class="score-label">最高分</text>
<text class="score-value">{{ highScore }}</text>
</view>
</view>
<!-- 游戏控制器 -->
<game-controller @direction-change="changeDirection"></game-controller>
<!-- 功能按钮 -->
<view class="function-btns">
<view class="function-btn" @tap="pauseGame">
<text class="iconfont" :class="isPaused ? 'icon-play' : 'icon-pause'"></text>
</view>
<view class="function-btn" @tap="goToRank">
<text class="iconfont icon-rank"></text>
</view>
<view class="function-btn" @tap="goToSettings">
<text class="iconfont icon-settings"></text>
</view>
</view>
</view>
</template>
<script>
import gameController from '@/components/game-controller/game-controller.vue';
import Game from '@/utils/game.js';
import storage from '@/utils/storage.js';
export default {
components: {
gameController
},
data() {
return {
game: null,
ctx: null,
canvasWidth: 300,
canvasHeight: 300,
gridSize: 15,
isPlaying: false,
isPaused: false,
gameOver: false,
score: 0,
highScore: 0,
gameLoop: null,
settings: {
speed: 150,
soundEnabled: true,
vibrationEnabled: true
}
}
},
onLoad() {
// 加载设置
this.loadSettings();
// 加载最高分
this.highScore = storage.getHighScore() || 0;
},
onReady() {
// 获取画布上下文
this.initCanvas();
},
onUnload() {
// 清除游戏循环
this.stopGame();
},
methods: {
// 初始化画布
initCanvas() {
const query = uni.createSelectorQuery().in(this);
query.select('#gameCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
// 设置画布大小
const dpr = uni.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
ctx.scale(dpr, dpr);
this.ctx = ctx;
this.canvasWidth = res[0].width;
this.canvasHeight = res[0].height;
// 初始化游戏
this.game = new Game({
ctx,
width: this.canvasWidth,
height: this.canvasHeight,
gridSize: this.gridSize,
speed: this.settings.speed,
onScore: this.updateScore,
onGameOver: this.handleGameOver
});
// 绘制初始界面
this.game.drawBackground();
});
},
// 开始游戏
startGame() {
if (this.game) {
this.game.init();
this.isPlaying = true;
this.isPaused = false;
this.gameOver = false;
this.score = 0;
// 启动游戏循环
this.runGameLoop();
// 播放开始音效
if (this.settings.soundEnabled) {
this.playSound('start');
}
}
},
// 暂停游戏
pauseGame() {
if (!this.isPlaying || this.gameOver) return;
this.isPaused = !this.isPaused;
if (this.isPaused) {
clearInterval(this.gameLoop);
} else {
this.runGameLoop();
}
},
// 停止游戏
stopGame() {
clearInterval(this.gameLoop);
this.isPlaying = false;
},
// 运行游戏循环
runGameLoop() {
clearInterval(this.gameLoop);
this.gameLoop = setInterval(() => {
if (!this.isPaused && this.isPlaying) {
this.game.update();
this.game.draw();
}
}, this.settings.speed);
},
// 更新分数
updateScore(newScore) {
this.score = newScore;
// 播放得分音效
if (this.settings.soundEnabled) {
this.playSound('score');
}
// 震动反馈
if (this.settings.vibrationEnabled) {
uni.vibrateShort();
}
},
// 处理游戏结束
handleGameOver() {
this.gameOver = true;
this.isPlaying = false;
clearInterval(this.gameLoop);
// 更新最高分
if (this.score > this.highScore) {
this.highScore = this.score;
storage.setHighScore(this.highScore);
}
// 保存分数到排行榜
storage.addScoreToRank({
score: this.score,
date: new Date().toISOString(),
duration: this.game.getDuration()
});
// 播放结束音效
if (this.settings.soundEnabled) {
this.playSound('gameover');
}
// 震动反馈
if (this.settings.vibrationEnabled) {
uni.vibrateLong();
}
},
// 改变蛇的方向
changeDirection(direction) {
if (this.game && this.isPlaying && !this.isPaused) {
this.game.changeDirection(direction);
}
},
// 处理触摸事件
handleTouch(e) {
if (!this.isPlaying || this.isPaused) return;
const touch = e.touches[0];
const lastTouch = this.lastTouch;
if (lastTouch) {
const deltaX = touch.clientX - lastTouch.clientX;
const deltaY = touch.clientY - lastTouch.clientY;
// 判断滑动方向
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动
if (deltaX > 0) {
this.changeDirection('right');
} else {
this.changeDirection('left');
}
} else {
// 垂直滑动
if (deltaY > 0) {
this.changeDirection('down');
} else {
this.changeDirection('up');
}
}
}
this.lastTouch = touch;
},
// 播放音效
playSound(type) {
const soundMap = {
start: '/static/sounds/start.mp3',
score: '/static/sounds/score.mp3',
gameover: '/static/sounds/gameover.mp3'
};
const soundUrl = soundMap[type];
if (soundUrl) {
const innerAudioContext = uni.createInnerAudioContext();
innerAudioContext.src = soundUrl;
innerAudioContext.play();
}
},
// 加载设置
loadSettings() {
const settings = storage.getSettings();
if (settings) {
this.settings = { ...this.settings, ...settings };
}
},
// 跳转到排行榜
goToRank() {
uni.navigateTo({
url: '/pages/rank/rank'
});
},
// 跳转到设置页面
goToSettings() {
uni.navigateTo({
url: '/pages/settings/settings'
});
}
}
}
</script>
<style lang="scss">
.game-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx;
.game-status {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
.status-content {
background-color: #fff;
border-radius: 20rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
.status-title {
font-size: 40rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.status-score {
font-size: 32rpx;
margin-bottom: 30rpx;
}
.start-btn {
background-color: #1aad19;
color: #fff;
border-radius: 40rpx;
padding: 0 60rpx;
}
}
}
.game-canvas {
width: 100%;
height: 750rpx;
background-color: #f5f5f5;
border: 2rpx solid #ddd;
border-radius: 10rpx;
margin-bottom: 30rpx;
}
.game-info {
display: flex;
width: 100%;
margin-bottom: 30rpx;
.score-box, .high-score-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
background-color: #f9f9f9;
border-radius: 10rpx;
&:first-child {
margin-right: 20rpx;
}
.score-label {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.score-value {
font-size: 40rpx;
font-weight: bold;
color: #333;
}
}
}
.function-btns {
display: flex;
justify-content: center;
margin-top: 30rpx;
.function-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
margin: 0 20rpx;
.iconfont {
font-size: 40rpx;
color: #333;
}
&:active {
background-color: #e0e0e0;
}
}
}
}
</style>
2. 游戏控制器组件
游戏控制器组件提供方向控制按钮,让用户可以控制蛇的移动方向。
<template>
<view class="game-controller">
<view class="direction-pad">
<view class="direction-btn up" @tap="changeDirection('up')">
<text class="iconfont icon-up"></text>
</view>
<view class="direction-row">
<view class="direction-btn left" @tap="changeDirection('left')">
<text class="iconfont icon-left"></text>
</view>
<view class="direction-btn center"></view>
<view class="direction-btn right" @tap="changeDirection('right')">
<text class="iconfont icon-right"></text>
</view>
</view>
<view class="direction-btn down" @tap="changeDirection('down')">
<text class="iconfont icon-down"></text>
</view>
</view>
</view>
</template>
<script>
export default {
methods: {
changeDirection(direction) {
this.$emit('direction-change', direction);
}
}
}
</script>
<style lang="scss">
.game-controller {
width: 100%;
.direction-pad {
display: flex;
flex-direction: column;
align-items: center;
.direction-row {
display: flex;
align-items: center;
}
.direction-btn {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
margin: 10rpx;
.iconfont {
font-size: 40rpx;
color: #333;
}
&:active {
background-color: #e0e0e0;
}
&.center {
background-color: transparent;
}
}
}
}
</style>
3. 游戏核心逻辑
游戏核心逻辑封装在 game.js
中,包括蛇的移动、食物生成、碰撞检测等功能。
// utils/game.js
export default class Game {
constructor(options) {
this.ctx = options.ctx;
this.width = options.width;
this.height = options.height;
this.gridSize = options.gridSize || 15;
this.speed = options.speed || 150;
this.onScore = options.onScore || function() {};
this.onGameOver = options.onGameOver || function() {};
this.snake = [];
this.food = null;
this.direction = 'right';
this.nextDirection = 'right';
this.score = 0;
this.startTime = 0;
// 计算网格数量
this.gridWidth = Math.floor(this.width / this.gridSize);
this.gridHeight = Math.floor(this.height / this.gridSize);
}
// 初始化游戏
init() {
// 初始化蛇
this.snake = [
{ x: 3, y: 1 },
{ x: 2, y: 1 },
{ x: 1, y: 1 }
];
// 初始化方向
this.direction = 'right';
this.nextDirection = 'right';
// 初始化分数
this.score = 0;
// 生成食物
this.generateFood();
// 记录开始时间
this.startTime = Date.now();
}
// 更新游戏状态
update() {
// 更新方向
this.direction = this.nextDirection;
// 获取蛇头
const head = { ...this.snake[0] };
// 根据方向移动蛇头
switch (this.direction) {
case 'up':
head.y -= 1;
break;
case 'down':
head.y += 1;
break;
case 'left':
head.x -= 1;
break;
case 'right':
head.x += 1;
break;
}
// 检查是否撞墙
if (head.x < 0 || head.x >= this.gridWidth || head.y < 0 || head.y >= this.gridHeight) {
this.onGameOver();
return;
}
// 检查是否撞到自己
for (let i = 0; i < this.snake.length; i++) {
if (this.snake[i].x === head.x && this.snake[i].y === head.y) {
this.onGameOver();
return;
}
}
// 将新蛇头添加到蛇身前面
this.snake.unshift(head);
// 检查是否吃到食物
if (head.x === this.food.x && head.y === this.food.y) {
// 增加分数
this.score += 10;
this.onScore(this.score);
// 生成新食物
this.generateFood();
} else {
// 如果没有吃到食物,移除蛇尾
this.snake.pop();
}
}
// 绘制游戏
draw() {
// 清空画布
this.ctx.clearRect(0, 0, this.width, this.height);
// 绘制背景
this.drawBackground();
// 绘制食物
this.drawFood();
// 绘制蛇
this.drawSnake();
}
// 绘制背景
drawBackground() {
this.ctx.fillStyle = '#f5f5f5';
this.ctx.fillRect(0, 0, this.width, this.height);
// 绘制网格
this.ctx.strokeStyle = '#e0e0e0';
this.ctx.lineWidth = 0.5;
// 绘制垂直线
for (let x = 0; x <= this.width; x += this.gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(x, 0);
this.ctx.lineTo(x, this.height);
this.ctx.stroke();
}
// 绘制水平线
for (let y = 0; y <= this.height; y += this.gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(this.width, y);
this.ctx.stroke();
}
}
// 绘制食物
drawFood() {
if (!this.food) return;
this.ctx.fillStyle = '#ff0000';
this.ctx.beginPath();
const centerX = this.food.x * this.gridSize + this.gridSize / 2;
const centerY = this.food.y * this.gridSize + this.gridSize / 2;
const radius = this.gridSize / 2 * 0.8;
this.ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
this.ctx.fill();
}
// 绘制蛇
drawSnake() {
// 绘制蛇身
for (let i = 1; i < this.snake.length; i++) {
const segment = this.snake[i];
this.ctx.fillStyle = '#4caf50';
this.ctx.fillRect(
segment.x * this.gridSize,
segment.y * this.gridSize,
this.gridSize,
this.gridSize
);
}
// 绘制蛇头
const head = this.snake[0];
this.ctx.fillStyle = '#388e3c';
this.ctx.fillRect(
head.x * this.gridSize,
head.y * this.gridSize,
this.gridSize,
this.gridSize
);
// 绘制蛇眼
this.ctx.fillStyle = '#ffffff';
const eyeSize = this.gridSize / 5;
let leftEyeX, leftEyeY, rightEyeX, rightEyeY;
switch (this.direction) {
case 'up':
leftEyeX = head.x * this.gridSize + this.gridSize / 4;
leftEyeY = head.y * this.gridSize + this.gridSize / 4;
rightEyeX = head.x * this.gridSize + this.gridSize * 3 / 4;
rightEyeY = head.y * this.gridSize + this.gridSize / 4;
break;
case 'down':
leftEyeX = head.x * this.gridSize + this.gridSize / 4;
leftEyeY = head.y * this.gridSize + this.gridSize * 3 / 4;
rightEyeX = head.x * this.gridSize + this.gridSize * 3 / 4;
rightEyeY = head.y * this.gridSize + this.gridSize * 3 / 4;
break;
case 'left':
leftEyeX = head.x * this.gridSize + this.gridSize / 4;
leftEyeY = head.y * this.gridSize + this.gridSize / 4;
rightEyeX = head.x * this.gridSize + this.gridSize / 4;
rightEyeY = head.y * this.gridSize + this.gridSize * 3 / 4;
break;
case 'right':
leftEyeX = head.x * this.gridSize + this.gridSize * 3 / 4;
leftEyeY = head.y * this.gridSize + this.gridSize / 4;
rightEyeX = head.x * this.gridSize + this.gridSize * 3 / 4;
rightEyeY = head.y * this.gridSize + this.gridSize * 3 / 4;
break;
}
this.ctx.beginPath();
this.ctx.arc(leftEyeX, leftEyeY, eyeSize, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.beginPath();
this.ctx.arc(rightEyeX, rightEyeY, eyeSize, 0, Math.PI * 2);
this.ctx.fill();
}
// 生成食物
generateFood() {
// 创建一个包含所有可能位置的数组
const availablePositions = [];
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
// 检查该位置是否被蛇占用
let isOccupied = false;
for (let i = 0; i < this.snake.length; i++) {
if (this.snake[i].x === x && this.snake[i].y === y) {
isOccupied = true;
break;
}
}
if (!isOccupied) {
availablePositions.push({ x, y });
}
}
}
// 随机选择一个可用位置
if (availablePositions.length > 0) {
const randomIndex = Math.floor(Math.random() * availablePositions.length);
this.food = availablePositions[randomIndex];
}
}
// 改变方向
changeDirection(newDirection) {
// 防止180度转弯
if (
(this.direction === 'up' && newDirection === 'down') ||
(this.direction === 'down' && newDirection === 'up') ||
(this.direction === 'left' && newDirection === 'right') ||
(this.direction === 'right' && newDirection === 'left')
) {
return;
}
this.nextDirection = newDirection;
}
// 获取游戏持续时间(秒)
getDuration() {
return Math.floor((Date.now() - this.startTime) / 1000);
}
}
4. 排行榜页面
排行榜页面展示玩家的历史最高分。
<template>
<view class="rank-page">
<view class="header">
<text class="title">排行榜</text>
</view>
<view class="empty-tip" v-if="rankList.length === 0">
<text class="iconfont icon-empty"></text>
<text>暂无排行数据</text>
</view>
<scroll-view scroll-y class="rank-list" v-else>
<view class="rank-header">
<text class="rank-column rank">排名</text>
<text class="rank-column score">分数</text>
<text class="rank-column date">日期</text>
<text class="rank-column duration">用时</text>
</view>
<view
class="rank-item"
v-for="(item, index) in rankList"
:key="index"
>
<text class="rank-column rank">{{ index + 1 }}</text>
<text class="rank-column score">{{ item.score }}</text>
<text class="rank-column date">{{ formatDate(item.date) }}</text>
<text class="rank-column duration">{{ formatDuration(item.duration) }}</text>
</view>
</scroll-view>
</view>
</template>
<script>
import storage from '@/utils/storage.js';
export default {
data() {
return {
rankList: []
}
},
onLoad() {
this.loadRankList();
},
methods: {
// 加载排行榜数据
loadRankList() {
const rankList = storage.getRankList() || [];
// 按分数降序排序
this.rankList = rankList.sort((a, b) => b.score - a.score);
},
// 格式化日期
formatDate(dateString) {
const date = new Date(dateString);
return `${date.getMonth() + 1}/${date.getDate()}`;
},
// 格式化时长
formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${String(remainingSeconds).padStart(2, '0')}`;
}
}
}
</script>
<style lang="scss">
.rank-page {
display: flex;
flex-direction: column;
height: 100vh;
.header {
padding: 30rpx;
background-color: #f5f5f5;
border-bottom: 1rpx solid #eee;
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.empty-tip {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
.iconfont {
font-size: 80rpx;
margin-bottom: 20rpx;
}
}
.rank-list {
flex: 1;
.rank-header {
display: flex;
padding: 20rpx;
background-color: #f9f9f9;
border-bottom: 1rpx solid #eee;
font-weight: bold;
}
.rank-item {
display: flex;
padding: 20rpx;
border-bottom: 1rpx solid #f5f5f5;
&:nth-child(odd) {
background-color: #fafafa;
}
}
.rank-column {
&.rank {
width: 15%;
text-align: center;
}
&.score {
width: 25%;
text-align: center;
font-weight: bold;
}
&.date {
width: 30%;
text-align: center;
}
&.duration {
width: 30%;
text-align: center;
}
}
}
}
</style>
4. 应用优化与最佳实践
4.1 性能优化
游戏应用对性能要求较高,以下是一些优化建议:
Canvas 渲染优化
// 优化 Canvas 渲染
function optimizeCanvasRendering(canvas, ctx) {
// 使用 requestAnimationFrame 代替 setInterval
let animationId;
function gameLoop(timestamp) {
// 更新游戏状态
game.update();
// 渲染游戏
game.draw();
// 继续循环
animationId = requestAnimationFrame(gameLoop);
}
// 启动游戏循环
function startGameLoop() {
animationId = requestAnimationFrame(gameLoop);
}
// 停止游戏循环
function stopGameLoop() {
cancelAnimationFrame(animationId);
}
// 减少重绘区域
function drawPartial(x, y, width, height) {
// 只重绘需要更新的部分
ctx.clearRect(x, y, width, height);
// 绘制更新的内容
}
return {
startGameLoop,
stopGameLoop,
drawPartial
};
}
内存管理
// 内存管理优化
function optimizeMemoryUsage() {
// 对象池模式,减少对象创建和垃圾回收
const particlePool = [];
const maxParticles = 100;
// 初始化对象池
for (let i = 0; i < maxParticles; i++) {
particlePool.push({
x: 0,
y: 0,
active: false,
// 其他属性
});
}
// 获取一个可用的粒子对象
function getParticle() {
for (let i = 0; i < maxParticles; i++) {
if (!particlePool[i].active) {
particlePool[i].active = true;
return particlePool[i];
}
}
return null; // 池已满
}
// 释放粒子对象
function releaseParticle(particle) {
particle.active = false;
// 重置其他属性
}
return {
getParticle,
releaseParticle
};
}
4.2 游戏音效与震动
游戏音效和震动可以提升游戏体验,以下是实现方式:
// 音效管理器
const SoundManager = {
sounds: {},
// 预加载音效
preload(soundMap) {
Object.keys(soundMap).forEach(key => {
const sound = uni.createInnerAudioContext();
sound.src = soundMap[key];
sound.autoplay = false;
this.sounds[key] = sound;
});
},
// 播放音效
play(name) {
if (this.sounds[name]) {
// 重新播放前先停止并重置
this.sounds[name].stop();
this.sounds[name].seek(0);
this.sounds[name].play();
}
},
// 停止音效
stop(name) {
if (this.sounds[name]) {
this.sounds[name].stop();
}
},
// 释放资源
release() {
Object.values(this.sounds).forEach(sound => {
sound.destroy();
});
this.sounds = {};
}
};
// 震动管理器
const VibrationManager = {
// 短震动
short() {
uni.vibrateShort({
success: () => {
console.log('短震动成功');
},
fail: (err) => {
console.error('短震动失败', err);
}
});
},
// 长震动
long() {
uni.vibrateLong({
success: () => {
console.log('长震动成功');
},
fail: (err) => {
console.error('长震动失败', err);
}
});
}
};
4.3 本地存储工具
本地存储工具用于保存游戏设置、最高分和排行榜数据。
// utils/storage.js
export default {
// 保存最高分
setHighScore(score) {
uni.setStorageSync('snake_high_score', score);
},
// 获取最高分
getHighScore() {
return uni.getStorageSync('snake_high_score');
},
// 保存游戏设置
setSettings(settings) {
uni.setStorageSync('snake_settings', settings);
},
// 获取游戏设置
getSettings() {
return uni.getStorageSync('snake_settings');
},
// 添加分数到排行榜
addScoreToRank(scoreData) {
let rankList = this.getRankList() || [];
// 添加新分数
rankList.push(scoreData);
// 按分数降序排序
rankList.sort((a, b) => b.score - a.score);
// 只保留前20名
if (rankList.length > 20) {
rankList = rankList.slice(0, 20);
}
uni.setStorageSync('snake_rank_list', rankList);
},
// 获取排行榜
getRankList() {
return uni.getStorageSync('snake_rank_list');
},
// 清除排行榜
clearRankList() {
uni.removeStorageSync('snake_rank_list');
}
};
5. 总结与拓展
5.1 开发要点总结
Canvas 性能优化:游戏开发中,Canvas 渲染性能至关重要,应当合理控制重绘区域,使用 requestAnimationFrame 代替 setInterval。
状态管理:游戏状态管理需要清晰,包括游戏初始化、运行、暂停、结束等状态的切换。
用户体验:游戏控制需要简单直观,音效和震动反馈可以提升游戏体验。
数据持久化:使用本地存储保存游戏设置、最高分和排行榜数据,提升用户粘性。
跨端适配:使用 uni-app 开发游戏时,需要考虑不同平台的差异,如触控方式、屏幕尺寸等。
5.2 功能拓展方向
多人对战:通过 WebSocket 实现实时多人对战功能,增加游戏的社交性和竞争性。
关卡设计:设计不同难度的关卡,增加游戏的挑战性和可玩性。
成就系统:设计游戏成就系统,激励用户完成各种挑战。
皮肤系统:提供多种游戏皮肤,增加游戏的个性化和收益点。
社交分享:集成社交分享功能,让用户可以分享游戏成绩,扩大游戏影响力。
5.3 商业化思路
内购道具:提供游戏内购买,如特殊能力、皮肤、关卡等。
广告变现:在游戏中适当位置插入广告,如游戏结束页面、暂停页面等。
会员订阅:提供会员订阅服务,会员可以获得更多游戏特权。
游戏联盟:与其他游戏开发者合作,形成游戏联盟,互相推广。
赛事活动:举办游戏比赛,吸引更多用户参与,提高用户活跃度。