鸿蒙开发案例:HarmonyOS NEXT 语法实现 2048
作者:zhongcx
- 2024-10-12 广东
本文字数:4985 字
阅读完需:约 16 分钟
【实现的功能】
• 游戏逻辑:实现了 2048 游戏的核心逻辑,包括初始化游戏盘面、添加随机方块、处理四个方向的滑动操作等。
• UI 展示:构建了游戏的用户界面,显示得分、游戏盘面,并提供了重新开始按钮。
• 用户交互:支持触摸屏上的手势识别,通过滑动手势控制游戏盘面上方块的移动。
【待实现功能】
• 方块移动动画:暂未实现 原理应该是在 UI 的 Text 上设置.translate({ x:, y: })并添加.animation({duration: 200}),然后在逻辑里通过修改 x 或 y 来实现位移动画。研究了一下操作时方块的移动动画,但效果不尽如人意(T_T)。继续努力学习如何实现动画效果。
【完整代码】
// 使用装饰器标记Cell类,可能表示该类具有可观测性
@ObservedV2
class Cell {
// 使用Trace装饰器标记value属性,可能表示该属性的变化会被追踪
@Trace value: number;
// 构造函数初始化单元格的值为0
constructor() {
this.value = 0;
}
}
// 使用Entry和Component装饰器标记Game2048结构体,可能表示这是程序的入口点,并且该结构体定义了一个组件
@Entry
@Component
// 定义Game2048结构体
struct Game2048 {
// 使用State装饰器标记状态变量,可能表示这些变量是组件的状态
@State board: Cell[][] = []; // 游戏盘面
@State score: number = 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); // 填充空位
}
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); // 填充空位
}
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); // 填充空位
}
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); // 填充空位
}
// 将处理后的数组元素放回到棋盘的对应列中
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 || ''}`)
.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%')
.height('100%')
.onTouch((e) => {
if (e.type === TouchType.Down && e.touches.length > 0) { // 触摸开始,记录初始位置
this.screenStartX = e.touches[0].x;
this.screenStartY = e.touches[0].y;
} else if (e.type === TouchType.Up && e.changedTouches.length > 0) { // 当手指抬起时,更新最后的位置
this.lastScreenX = e.changedTouches[0].x;
this.lastScreenY = e.changedTouches[0].y;
}
})
.gesture(
SwipeGesture({ direction: SwipeDirection.All }) // 支持方向中 all可以是上下左右
.onAction((_event: GestureEvent) => {
const swipeX = this.lastScreenX - this.screenStartX;
const swipeY = this.lastScreenY - this.screenStartY;
// 清除开始位置记录,准备下一次滑动判断
this.screenStartX = 0;
this.screenStartY = 0;
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); // 添加一个随机方块
})
)
}
}
复制代码
划线
评论
复制
发布于: 刚刚阅读数: 5
zhongcx
关注
还未添加个人签名 2024-09-27 加入
还未添加个人简介
评论