游戏结束时的仪式感——结果弹窗的层叠设计
游戏截图:
Stack层叠布局
build(){Stack({alignContent:Alignment.TopStart}){// 第一层:游戏界面Column(){// 导航栏、状态栏、棋盘、按钮...}.width('100%').height('100%').backgroundColor('#F5F0E8')// 第二层:结果弹窗(条件渲染)if(this.showResultDialog){Column(){// 弹窗内容...}.width('70%').padding(32).backgroundColor('#FFFFFF').borderRadius(16)}}.width('100%').height('100%')}Stack的层叠原理
Stack ├── Column (游戏界面) ← 底层 └── Column (结果弹窗) ← 上层,覆盖游戏界面Stack中的子组件按声明顺序层叠——后声明的在上层。弹窗声明在后面,所以覆盖游戏界面。
条件渲染
if(this.showResultDialog){Column(){/* 弹窗内容 */}}showResultDialog为true时弹窗渲染,为false时不渲染。ArkUI的条件渲染会完全创建/销毁组件。
弹窗触发时机
privateonCellClick(row:number,col:number):void{if(this.result!==GameResult.PLAYING)return;constsuccess=this.engine.placePiece(row,col,this.currentPlayer);if(success){this.refreshBoardData();if(this.result!==GameResult.PLAYING){this.showResultDialog=true;// 游戏结束时显示弹窗}}}落子后如果result不再是PLAYING(有人获胜或平局),设置showResultDialog = true。
弹窗内容设计
Column(){Text(this.result===GameResult.DRAW?'平局!':'游戏结束').fontSize(24).fontWeight(FontWeight.Bold).fontColor('#333333').margin({bottom:8})Text(this.getStatusText()).fontSize(18).fontColor('#E63946').margin({bottom:4})Text(`共${this.moveCount}手`).fontSize(14).fontColor('#888888').margin({bottom:24})Row({space:16}){Button('返回首页').fontSize(16).fontColor('#555555').backgroundColor('#E8E0D5').borderRadius(24).height(44).onClick(()=>{router.back();})Button('再来一局').fontSize(16).fontColor('#FFFFFF').backgroundColor('#4A90D9').borderRadius(24).height(44).onClick(()=>{this.restart();})}}.width('70%').padding(32).backgroundColor('#FFFFFF').borderRadius(16).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)弹窗信息层级
┌─────────────────────────┐ │ │ │ 游戏结束 │ ← 24px Bold 主标题 │ 黑方获胜! │ ← 18px 红色 结果 │ 共 47 手 │ ← 14px 灰色 统计 │ │ │ [返回首页] [再来一局] │ ← 44px 按钮 │ │ └─────────────────────────┘弹窗按钮逻辑
返回首页
Button('返回首页').onClick(()=>{router.back();})调用router.back()返回Index首页。当前页面的组件和引擎实例会被销毁。
再来一局
Button('再来一局').onClick(()=>{this.restart();})privaterestart():void{this.engine.reset();// 重置引擎this.showResultDialog=false;// 关闭弹窗this.refreshBoardData();// 刷新UI}restart关闭弹窗并重置游戏——弹窗通过showResultDialog = false消失,棋盘清空。
弹窗样式
.width('70%')// 宽度70%,两侧留边.padding(32)// 内边距32.backgroundColor('#FFFFFF')// 白色背景.borderRadius(16)// 圆角.justifyContent(FlexAlign.Center)// 垂直居中.alignItems(HorizontalAlign.Center)// 水平居中Stack的alignContent
Stack({alignContent:Alignment.TopStart})Alignment.TopStart让未指定对齐方式的子组件(弹窗)从左上角开始排列。但弹窗设置了justifyContent(Center)和alignItems(Center),所以内容在弹窗内部居中。
注意:Stack默认居中对齐子组件。如果想让弹窗覆盖全屏并居中,可以:
Stack({alignContent:Alignment.Center}){Column(){/* 游戏界面 */}if(this.showResultDialog){Column(){/* 弹窗 */}// 自动居中}}本项目TwoPlayerPage使用TopStart让游戏界面从顶部开始,弹窗通过Stack默认行为居中显示。
对比AIBattlePage的弹窗
AIBattlePage的弹窗更复杂——有半透明遮罩层:
@BuilderresultDialog(){Stack({alignContent:Alignment.Center}){// 半透明遮罩Column().width('100%').height('100%').backgroundColor('#00000066')// 40%透明黑// 弹窗内容Column(){...}}}TwoPlayerPage没有遮罩层,AIBattlePage有——设计上的差异。
总结
结果弹窗的设计要点:
- Stack层叠:弹窗覆盖在游戏界面上方
- 条件渲染:
if (showResultDialog)控制显示 - 信息层级:主标题→结果→统计→按钮
- 弹窗样式:70%宽度、32内边距、16圆角
- 按钮逻辑:返回首页或再来一局
弹窗是游戏结束的"仪式感"——它让用户感受到对局的完整闭环。