回复
鸿蒙NEXT开发案例:2048
zhongcx
发布于 2024-12-1 08:53
浏览
0收藏
【引言】
本文将介绍如何使用鸿蒙NEXT框架开发经典的2048游戏。我们将通过代码示例展示游戏的核心逻辑,包括游戏初始化、滑动操作、合并单元格以及界面构建。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
- 类与装饰器
在代码中,我们定义了两个主要的类:Cell和Game2048。Cell类表示游戏中的单元格,使用了@ObservedV2和@Trace装饰器,表明该类具有可观测性,并且其value属性的变化会被追踪。
@ObservedV2
class Cell {
@Trace value: number;
constructor() {
this.value = 0;
}
}
Game2048结构体则是游戏的核心,使用了@Entry和@Component装饰器,表示这是程序的入口点,并且定义了一个组件。
2. 游戏状态管理
Game2048类中定义了多个状态变量,包括游戏盘面board、分数score、单元格大小cellSize等。通过@State装饰器标记这些变量,使其能够在界面中自动更新。
@State board: Cell[][] = [];
@State score: number = 0;
- 游戏逻辑
3.1 初始化游戏
aboutToAppear方法重置分数并初始化游戏盘面,调用initBoard方法创建4x4的单元格矩阵,并在随机位置添加两个初始方块。
aboutToAppear() {
this.score = 0;
this.initBoard();
this.addRandomTiles(2);
}
3.2 添加随机方块
addRandomTiles方法负责在空单元格中随机添加2或4。通过遍历盘面,记录空单元格的位置,并随机选择位置进行填充。
addRandomTiles(count: number) {
// 逻辑代码
}
3.3 滑动与合并
游戏的核心操作是滑动,分别实现了slideLeft、slideRight、slideUp和slideDown方法。这些方法通过临时数组存储非零值,并处理合并逻辑,更新分数。
slideLeft() {
// 逻辑代码
}
- 界面构建
游戏界面通过build方法构建,使用Column和Flex布局显示得分和单元格。每个单元格的颜色和字体大小根据其值动态调整。
build() {
Column({ space: 10 }) {
// 显示得分和单元格
}
}
- 触摸与手势处理
通过onTouch和gesture方法处理用户的触摸事件,判断滑动方向并调用相应的滑动方法。
.onTouch((e) => {
// 触摸事件处理
})
.gesture(
SwipeGesture({ direction: SwipeDirection.All })
);
【完整代码】
// 使用装饰器标记Cell类,可能表示该类具有可观测性
@ObservedV2
class Cell {
// 使用Trace装饰器标记value属性,可能表示该属性的变化会被追踪
@Trace value: number;
// 构造函数初始化单元格的值为0
constructor() {
this.value = 0; // 设置单元格初始值为0
}
}
// 使用Entry和Component装饰器标记Game2048结构体,可能表示这是程序的入口点,并且该结构体定义了一个组件
@Entry
@Component
// 定义Game2048结构体
struct Game2048 {
// 使用State装饰器标记状态变量,可能表示这些变量是组件的状态
@State board: Cell[][] = []; // 游戏盘面,存储单元格
@State score: number = 0; // 分数,初始化为0
@State cellSize: number = 200; // 单元格大小
@State cellMargin: number = 5; // 单元格之间的边距
@State screenStartX: number = 0; // 触摸开始时的屏幕X坐标
@State screenStartY: number = 0; // 触摸开始时的屏幕Y坐标
@State lastScreenX: number = 0; // 触摸结束时的屏幕X坐标
@State lastScreenY: number = 0; // 触摸结束时的屏幕Y坐标
// 定义颜色数组,表示不同数字对应的颜色
colors: string[] = [
'#CCCCCC', // 0 - 灰色
'#FFC107', // 2 - 黄色
'#FF9800', // 4 - 橙色
'#FF5722', // 8 - 深橙色
'#F44336', // 16 - 红色
'#9C27B0', // 32 - 紫色
'#3F51B5', // 64 - 蓝紫色
'#00BCD4', // 128 - 蓝色
'#009688', // 256 - 深青色
'#4CAF50', // 512 - 绿色
'#8BC34A', // 1024 - 浅绿色
'#CDDC39', // 2048 - 柠檬黄
'#FFEB3B', // 4096 - 淡黄色
'#795548', // 8192 - 棕色
'#607D8B', // 16384 - 深灰色
'#9E9E9E', // 32768 - 灰色
'#000000' // 以上 - 黑色
];
// 游戏即将出现时执行的方法
aboutToAppear() {
this.score = 0; // 重置分数
this.initBoard(); // 重新初始化游戏板
this.addRandomTiles(2); // 添加两个随机方块
}
// 初始化游戏盘面
initBoard() {
if (this.board.length == 0) { // 如果盘面为空
for (let i = 0; i < 4; i++) { // 遍历行
let cellArr: Cell[] = []; // 创建新行
for (let j = 0; j < 4; j++) { // 遍历列
cellArr.push(new Cell()); // 创建新单元格并添加到行中
}
this.board.push(cellArr); // 将行添加到盘面
}
} else { // 如果盘面不为空
for (let i = 0; i < this.board.length; i++) { // 遍历已有行
for (let j = 0; j < this.board[i].length; j++) { // 遍历列
this.board[i][j].value = 0; // 清空单元格值
}
}
}
}
// 在盘面上添加指定数量的随机方块
addRandomTiles(count: number) {
let emptyCells: object[] = []; // 存储空单元格位置
for (let row = 0; row < 4; row++) { // 遍历行
for (let col = 0; col < 4; col++) { // 遍历列
if (this.board[row][col].value === 0) { // 如果单元格为空
emptyCells.push(Object({ row: row, col: col })); // 记录空单元格位置
}
}
}
for (let i = 0; i < count; i++) { // 添加指定数量的随机方块
if (emptyCells.length > 0) { // 如果还有空单元格
let randomIndex = Math.floor(Math.random() * emptyCells.length); // 随机选择一个空单元格
let obj = emptyCells[randomIndex]; // 获取该空单元格的位置
this.board[obj['row']][obj['col']].value = Math.random() < 0.9 ? 2 : 4; // 随机生成2或4
emptyCells.splice(randomIndex, 1); // 移除已使用的空单元格位置
}
}
}
// 向左滑动
slideLeft() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow: number[] = []; // 临时存储行数据
let merged: boolean[] = new Array(4).fill(false); // 标记是否已经合并过
for (let col = 0; col < 4; col++) { // 遍历列
if (this.board[row][col].value !== 0) { // 如果单元格非零
tempRow.push(this.board[row][col].value); // 移动非零值到临时数组
}
}
let mergePos = 0; // 合并位置指针
while (mergePos < tempRow.length - 1) { // 遍历临时数组
if (tempRow[mergePos] === tempRow[mergePos + 1] && !merged[mergePos]) { // 如果可以合并
tempRow[mergePos] *= 2; // 合并
this.score += tempRow[mergePos]; // 更新分数
merged[mergePos] = true; // 标记已合并
tempRow.splice(mergePos + 1, 1); // 移除合并过的值
} else {
mergePos++; // 移动到下一个位置
}
}
while (tempRow.length < 4) { // 填充空位
tempRow.push(0); // 添加0到临时数组
}
for (let col = 0; col < 4; col++) { // 更新盘面
this.board[row][col].value = tempRow[col]; // 将临时数组的值放回盘面
}
}
}
// 向右滑动
slideRight() {
for (let row = 0; row < 4; row++) { // 遍历每一行
let tempRow: number[] = []; // 临时存储行数据
let merged: boolean[] = new Array(4).fill(false); // 标记是否已经合并过
for (let col = 3; col >= 0; col--) { // 从右向左遍历列
if (this.board[row][col].value !== 0) { // 如果单元格非零
tempRow.unshift(this.board[row][col].value); // 移动非零值到临时数组
}
}
let mergePos = tempRow.length - 1; // 合并位置指针
while (mergePos > 0) { // 遍历临时数组
if (tempRow[mergePos] === tempRow[mergePos - 1] && !merged[mergePos - 1]) { // 如果可以合并
tempRow[mergePos] *= 2; // 合并
this.score += tempRow[mergePos]; // 更新分数
merged[mergePos - 1] = true; // 标记已合并
tempRow.splice(mergePos - 1, 1); // 移除合并过的值
} else {
mergePos--; // 移动到上一个位置
}
}
while (tempRow.length < 4) { // 填充空位
tempRow.unshift(0); // 添加0到临时数组
}
for (let col = 0; col < 4; col++) { // 更新盘面
this.board[row][col].value = tempRow[col]; // 将临时数组的值放回盘面
}
}
}
// 向上滑动
slideUp() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol: number[] = [];
let merged: boolean[] = new Array(4).fill(false); // 标记是否已经合并过
for (let row = 0; row < 4; row++) { // 遍历行
if (this.board[row][col].value !== 0) { // 如果单元格非零
tempCol.push(this.board[row][col].value); // 移动非零值到临时数组
}
}
let mergePos = 0; // 合并位置指针
while (mergePos < tempCol.length - 1) { // 遍历临时数组
if (tempCol[mergePos] === tempCol[mergePos + 1] && !merged[mergePos]) { // 如果可以合并
tempCol[mergePos] *= 2; // 合并
this.score += tempCol[mergePos]; // 更新分数
merged[mergePos] = true; // 标记已合并
tempCol.splice(mergePos + 1, 1); // 移除合并过的值
} else {
mergePos++; // 移动到下一个位置
}
}
while (tempCol.length < 4) { // 填充空位
tempCol.push(0); // 添加0到临时数组
}
for (let newRow = 0; newRow < 4; newRow++) { // 更新盘面
this.board[newRow][col].value = tempCol[newRow]; // 将临时数组的值放回盘面
}
}
}
// 向下滑动
slideDown() {
for (let col = 0; col < 4; col++) { // 遍历每一列
let tempCol: number[] = []; // 临时存储列数据
let merged: boolean[] = new Array(4).fill(false); // 标记是否已经合并过
// 从下往上遍历列
for (let row = 3; row >= 0; row--) { // 从底部开始遍历
if (this.board[row][col].value !== 0) { // 如果单元格非零
tempCol.unshift(this.board[row][col].value); // 移动非零值到临时数组
}
}
let mergePos = tempCol.length - 1; // 合并位置指针
while (mergePos > 0) { // 遍历临时数组
if (tempCol[mergePos] === tempCol[mergePos - 1] && !merged[mergePos - 1]) { // 如果可以合并
tempCol[mergePos] *= 2; // 合并
this.score += tempCol[mergePos]; // 更新分数
merged[mergePos - 1] = true; // 标记已合并
tempCol.splice(mergePos - 1, 1); // 移除合并过的值
} else {
mergePos--; // 移动到上一个位置
}
}
// 如果数组长度小于4,用0填充
while (tempCol.length < 4) {
tempCol.unshift(0); // 添加0到临时数组
}
// 将处理后的数组元素放回到棋盘的对应列中
for (let row = 0; row < 4; row++) {
this.board[3 - row][col].value = tempCol[3 - row]; // 注意反转顺序
}
}
}
// 构建游戏界面
build() {
// 布局容器
Column({ space: 10 }) {
// 显示得分
Text(`得分: ${this.score}`)
.fontSize(24) // 设置字体大小
.margin({ top: 20 }) // 设置上边距
// 底层背景布局
Flex({ wrap: FlexWrap.Wrap, direction: FlexDirection.Row }) {
// 遍历每个单元格
ForEach(this.board.flat(), (cell: Cell, index: number) => {
// 显示单元格上的数字
Text(`${cell.value || ''}`) // 显示单元格值,值为0时显示空
.width(`${this.cellSize}px`) // 设置单元格宽度
.height(`${this.cellSize}px`) // 设置单元格高度
.margin(`${this.cellMargin}px`) // 设置单元格边距
.fontSize(`${cell.value >= 100 ? this.cellSize / 3 : this.cellSize / 2}px`) // 根据数字大小调整字体大小
.textAlign(TextAlign.Center) // 设置文本居中
.backgroundColor(this.colors[cell.value == 0 ? 0 : Math.floor(Math.log2(cell.value))]) // 设置背景颜色
.fontColor(cell.value === 0 ? '#000' : '#fff') // 设置字体颜色
.borderRadius(5) // 设置圆角
})
}
.width(`${(this.cellSize + this.cellMargin * 2) * 4}px`) // 设置容器宽度
// 重新开始按钮
Button('重新开始').onClick(() => {
this.aboutToAppear(); // 重新开始游戏
})
}
.width('100%') // 设置容器宽度为100%
.height('100%') // 设置容器高度为100%
.onTouch((e) => { // 处理触摸事件
if (e.type === TouchType.Down && e.touches.length > 0) { // 触摸开始,记录初始位置
this.screenStartX = e.touches[0].x; // 记录X坐标
this.screenStartY = e.touches[0].y; // 记录Y坐标
} else if (e.type === TouchType.Up && e.changedTouches.length > 0) { // 当手指抬起时,更新最后的位置
this.lastScreenX = e.changedTouches[0].x; // 记录最后X坐标
this.lastScreenY = e.changedTouches[0].y; // 记录最后Y坐标
}
})
.gesture(
SwipeGesture({ direction: SwipeDirection.All }) // 支持所有方向的滑动手势
.onAction((_event: GestureEvent) => { // 处理滑动事件
const swipeX = this.lastScreenX - this.screenStartX; // 计算X轴滑动距离
const swipeY = this.lastScreenY - this.screenStartY; // 计算Y轴滑动距离
// 清除开始位置记录,准备下一次滑动判断
this.screenStartX = 0; // 重置初始X坐标
this.screenStartY = 0; // 重置初始Y坐标
if (Math.abs(swipeX) > Math.abs(swipeY)) { // 判断滑动方向
if (swipeX > 0) {
this.slideRight(); // 向右滑动
} else {
this.slideLeft(); // 向左滑动
}
} else {
if (swipeY > 0) {
this.slideDown(); // 向下滑动
} else {
this.slideUp(); // 向上滑动
}
}
this.addRandomTiles(1); // 添加一个随机方块
})
)
}
}
分类
标签
赞
收藏
回复
相关推荐