Day 06 · UI 不卡帧Canvas·Widget·Layout 响应式界面完全实战学习目标掌握 Cocos UI 系统全套组件实现自适应布局、常用控件和界面动效预计时间3 小时难度⭐⭐⭐☆☆Cocos UI 系统架构Canvas画布 └── 所有 UI 元素的根节点 ├── Widget自适应组件—— 相对布局 ├── Layout布局组件—— 自动排列 └── 常用 UI 组件 ├── Label文字 ├── RichText富文本 ├── Sprite图片 ├── Button按钮 ├── Toggle / ToggleContainer单选框/复选框 ├── Slider滑动条 ├── ProgressBar进度条 ├── EditBox输入框 ├── ScrollView滚动容器 └── PageView翻页容器1. Canvas 与屏幕适配1.1 Canvas 组件Canvas 是 UI 根节点负责屏幕适配将设计分辨率映射到实际屏幕。推荐配置手机竖屏游戏设计分辨率750 × 1334iPhone 8 标准 适配模式FitHeight高度优先适配1.2 多分辨率适配策略适配模式说明适用场景FitWidth以宽度为基准横版游戏FitHeight以高度为基准竖版游戏ShowAll全部显示可能有黑边固定比例游戏ExactFit拉伸填满可能变形背景图NoBorder填满无黑边可能裁剪全屏背景2. Widget自适应布局Widget 组件让 UI 元素根据父节点/屏幕边界自动定位和缩放。import{_decorator,Component,Widget}fromcc;const{ccclass}_decorator;ccclass(WidgetDemo)exportclassWidgetDemoextendsComponent{start(){constwidgetthis.getComponent(Widget)!;// 方式1在编辑器中设置推荐// 方式2代码设置动态调整widget.isAlignLefttrue;widget.left20;// 距左边 20pxwidget.isAlignRighttrue;widget.right20;// 距右边 20pxwidget.isAlignToptrue;widget.top50;// 距顶部 50px// 使用百分比相对于父节点宽高的百分比widget.isAlignLeftPercentagefalse;// 使用像素值widget.isAlignRightPercentagefalse;// 更新 Widget代码修改后需要手动更新widget.updateAlignment();}}2.1 常用 Widget 场景全屏背景铺满整个父节点Left0, Right0, Top0, Bottom0勾选 AlignLeft, AlignRight, AlignTop, AlignBottom顶部状态栏吸顶Top0, Left0, Right0固定 Height例如 100px底部操作栏吸底Bottom0, Left0, Right0固定 Height右下角按钮固定右下角Right20, Bottom203. Layout自动布局Layout 让子节点按照规则自动排列免去手动调整位置。import{_decorator,Component,Layout}fromcc;const{ccclass}_decorator;ccclass(LayoutDemo)exportclassLayoutDemoextendsComponent{start(){constlayoutthis.getComponent(Layout)!;// 水平排列从左到右layout.typeLayout.Type.HORIZONTAL;layout.spacingX10;// 子节点横向间距layout.paddingLeft20;// 内边距layout.paddingRight20;// 垂直排列从上到下layout.typeLayout.Type.VERTICAL;layout.spacingY10;// 网格排列layout.typeLayout.Type.GRID;layout.startAxisLayout.AxisDirection.HORIZONTAL;// 先横向排列layout.constraintLayout.Constraint.FIXED_COL;// 固定列数layout.constraintNum3;// 每行3个layout.spacingX10;layout.spacingY10;// 调整大小模式layout.resizeModeLayout.ResizeMode.CONTAINER;// 自动调整容器大小以适应子节点// ResizeMode.CHILDREN: 自动调整子节点大小以填满容器// 更新布局添加/删除子节点后手动触发layout.updateLayout();}}4. 常用 UI 组件4.1 Label文字import{_decorator,Component,Label,Color}fromcc;const{ccclass,property}_decorator;ccclass(LabelDemo)exportclassLabelDemoextendsComponent{property(Label)scoreLabel:Labelnull!;updateScore(score:number){this.scoreLabel.string分数${score};}start(){constlabelthis.getComponent(Label)!;// 基础属性label.stringHello Cocos!;label.fontSize36;label.lineHeight40;// 颜色label.colornewColor(255,200,0,255);// 金色// 溢出处理label.overflowLabel.Overflow.CLAMP;// 截断超出部分label.overflowLabel.Overflow.RESIZE_HEIGHT;// 自动扩展高度label.overflowLabel.Overflow.SHRINK;// 缩小字体以适应// 对齐方式label.horizontalAlignLabel.HorizontalAlign.CENTER;label.verticalAlignLabel.VerticalAlign.CENTER;// 是否启用富文本支持 b, color 等标签// 注意富文本用 RichText 组件更好// 字体文件// label.font myBitmapFont; // 使用位图字体}}4.2 Button按钮import{_decorator,Component,Button,EventHandler}fromcc;const{ccclass,property}_decorator;ccclass(ButtonDemo)exportclassButtonDemoextendsComponent{property(Button)startBtn:Buttonnull!;start(){// 方式1在编辑器中绑定点击事件推荐// Button 组件 → Click Events → → 拖入节点 → 选择方法// 方式2代码绑定this.startBtn.node.on(Button.EventType.CLICK,this.onStartClick,this);// 禁用按钮this.startBtn.interactablefalse;// 启用按钮this.startBtn.interactabletrue;// 修改按钮状态的 SpriteNormal/Hover/Pressed/Disabled// 在编辑器中 Button 组件的 Transition 属性中设置}onStartClick(event:Event,data:string){console.log(点击了开始按钮);// data 是编辑器中配置的 Custom Event Data 字符串}onDestroy(){if(this.startBtn){this.startBtn.node.off(Button.EventType.CLICK,this.onStartClick,this);}}}4.3 ScrollView滚动视图ScrollView 是游戏中最常用的 UI 组件之一排行榜、技能列表、商店等import{_decorator,Component,ScrollView,Node,Label,instantiate,Prefab}fromcc;const{ccclass,property}_decorator;ccclass(RankList)exportclassRankListextendsComponent{property(ScrollView)scrollView:ScrollViewnull!;property(Prefab)itemPrefab:Prefabnull!;// 填充排行榜数据fillData(rankData:Array{name:string;score:number}){constcontentthis.scrollView.content!;// 清空旧数据content.removeAllChildren();// 添加新条目rankData.forEach((data,index){constiteminstantiate(this.itemPrefab);content.addChild(item);// 设置条目数据constrankLabelitem.getChildByName(Rank)?.getComponent(Label)!;constnameLabelitem.getChildByName(Name)?.getComponent(Label)!;constscoreLabelitem.getChildByName(Score)?.getComponent(Label)!;if(rankLabel)rankLabel.string#${index1};if(nameLabel)nameLabel.stringdata.name;if(scoreLabel)scoreLabel.string${data.score};});// 滚动到顶部this.scrollView.scrollToTop(0.3);}// 平滑滚动到指定位置scrollToBottom(){this.scrollView.scrollToBottom(0.5);// 0.5秒滚到底部}}4.4 ProgressBar进度条import{_decorator,Component,ProgressBar}fromcc;const{ccclass,property}_decorator;ccclass(HealthBar)exportclassHealthBarextendsComponent{property(ProgressBar)hpBar:ProgressBarnull!;private_maxHP:number100;private_currentHP:number100;setHP(current:number,max:number){this._currentHPMath.max(0,current);this._maxHPmax;this.hpBar.progressthis._currentHP/this._maxHP;// 0~1}takeDamage(damage:number){this.setHP(this._currentHP-damage,this._maxHP);}}5. 完整实战游戏主界面综合运用今天所学构建一个完整的游戏主界面import{_decorator,Component,Button,Label,Node,tween,Vec3,UIOpacity}fromcc;const{ccclass,property}_decorator;ccclass(MainMenuUI)exportclassMainMenuUIextendsComponent{property(Button)startBtn:Buttonnull!;property(Button)settingsBtn:Buttonnull!;property(Label)titleLabel:Labelnull!;property(Node)settingsPanel:Nodenull!;property(Node)logoNode:Nodenull!;onLoad(){// 初始状态设置面板隐藏this.settingsPanel.activefalse;}start(){// 注册按钮事件this.startBtn.node.on(Button.EventType.CLICK,this.onStart,this);this.settingsBtn.node.on(Button.EventType.CLICK,this.onSettings,this);// 播放 Logo 入场动画this.playLogoEntrance();}playLogoEntrance(){// Logo 从上方飞入this.logoNode.setPosition(0,600,0);this.logoNode.setScale(0.5,0.5,1);tween(this.logoNode).to(0.8,{position:newVec3(0,150,0),scale:newVec3(1,1,1)},{easing:backOut}).call((){// 入场完成后开始悬浮动画this.playLogoHover();}).start();}playLogoHover(){tween(this.logoNode).to(1.5,{position:newVec3(0,165,0)},{easing:sineInOut}).to(1.5,{position:newVec3(0,135,0)},{easing:sineInOut}).union().repeatForever().start();}onStart(){console.log(开始游戏);// TODO: 加载游戏场景// director.loadScene(Game);}onSettings(){this.showPanel(this.settingsPanel);}showPanel(panel:Node){panel.activetrue;panel.setScale(0.8,0.8,1);constopacitypanel.getComponent(UIOpacity)!;opacity.opacity0;tween(panel).to(0.25,{scale:newVec3(1,1,1)},{easing:backOut}).start();tween(opacity).to(0.2,{opacity:255}).start();}hidePanel(panel:Node){tween(panel).to(0.2,{scale:newVec3(0.8,0.8,1)}).call((){panel.activefalse;}).start();}onDestroy(){this.startBtn.node.off(Button.EventType.CLICK,this.onStart,this);this.settingsBtn.node.off(Button.EventType.CLICK,this.onSettings,this);}}6. 今日总结✅ 掌握 Canvas 设计分辨率与屏幕适配策略✅ 掌握 Widget 组件的锚点布局✅ 掌握 Layout 的三种排列模式✅ 熟练使用 Label、Button、ScrollView、ProgressBar✅ 实战构建带动画效果的游戏主界面⚠️ UI 常见坑问题解决方案Widget 不生效父节点需要有 ContentSize检查 Alignment 是否勾选ScrollView 滚动不流畅子节点不要用 Widget改用 Layout 统一管理按钮区域不准检查节点的 UITransform ContentSize 是否正确不要用 Scale 缩放代替 SizeLabel 文字溢出设置合适的 overflow 模式或固定 ContentSize← Day 05 | 系列目录 | Day 07 →