news 2026/4/16 13:50:50

Flutter 勇闯2D像素游戏之路(三):人物与地图元素的交互

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter 勇闯2D像素游戏之路(三):人物与地图元素的交互

Flutter 勇闯2D像素游戏之路(一):一个 Hero 的诞生
Flutter 勇闯2D像素游戏之路(二):绘制加载游戏地图
Flutter 勇闯2D像素游戏之路(三):人物与地图元素的交互

前言

在上篇文章中,我们初识了2D 地图编辑器 Tiled,完成了从地图绘制、资源加载基础墙体碰撞的搭建,为游戏世界奠定了最基本的空间规则。

但仅有能走、能挡的地图,还远不足以构成一个真正有生命力的游戏场景。
真正让地图起来的,是人物与地图元素之间的交互

在游戏中,地图并不仅仅是静态的背景,它往往承载着多种功能性的元素,例如:

  • 可被触发的宝箱、机关
  • 控制通行逻辑的钥匙与门
  • 带来风险与挑战的陷阱、伤害型障碍物

这些元素都需要与人物产生行为上的联动:
接触、判断、反馈、结果,共同构成完整的交互闭环。

因此,本篇文章将着重于构建人物与地图元素的交互,逐步介绍、实现几类常见的地图交互对象,构建一个更加丰富、可扩展的地图交互体系。

前情提示

1. 地图

在进行本章的内容实践之前,请先将之前地图中门、宝箱、钥匙、地刺元素先行清除。
本章将把他们从单纯装饰件,进化到真正的可交互件。

接下来所需要用到的门、宝箱、钥匙、地刺等图片素材,已在github仓库中添加。

2. 墙体

首先,先感谢兄弟的反馈 😊
接下来我们就用Object LayerTile Layer实现上一期的墙体碰撞区,看看区别。

Object Layer

Tile Layer

从图中可以看到:

  • Tile Layer的碰撞区完美和地图中wall图层匹配
  • Object Layer的碰撞区完美和Collisions对象层匹配

在本文素材的前提之下

  • 如果按Tile Layer,那么碰撞区范围就偏大了,需要你重新绘制墙体,但是你会发现素材中墙体与下面的地板是在一块的,不能分离,这就不太完美。
  • Object Layer可以让我们在不破坏改变墙体的前提下,更精细调整墙体的碰撞区,让它更合理。

因此,各有利弊,看大家的取舍

本文中水面的碰撞区就是Tile Layer方法,大家可以看一看。

MyHero

一. 本章目标



二. 人物碰撞矩形优化

1. 优化前

// hero_component.dart@overrideFuture<void>onLoad()async{...size=Vector2(100,100);position=Vector2(1000,1000);_hitbox=RectangleHitbox();add(_hitbox);}

默认情况下,人物的碰撞区域RectangleHitbox继承父组件的 size,也就是说碰撞矩形就是100×100,所以碰撞框很大,通常会比人物实际可视部分大。

这样就导致上图中问题,在一个狭窄的走道内,由于你的超大碰撞区域,而被卡住。

2. 优化后

// hero_component.dart@overrideFuture<void>onLoad()async{...size=Vector2(100,100);position=Vector2(1000,1000);_hitbox=RectangleHitbox.relative(Vector2(0.5,0.2),// 碰撞区域占组件宽高比例parentSize:size,position:Vector2(size.x*0.25,size.y*0.7),// 碰撞区域的偏移);add(_hitbox);}

优化后,我们使用RectangleHitbox.relative手动缩小碰撞矩形,并调整偏移,使碰撞区域只覆盖角色脚底,即可获取正常的游戏体验

  • 碰撞矩形与角色可视部分更贴合,避免卡墙。
  • 在狭窄走道中角色可以顺畅移动。
  • 保留足够的碰撞检测区域,保证游戏逻辑和物理交互仍然有效。

总结
通过简单缩小并下移碰撞矩形,实现了视觉与碰撞逻辑的分离,就可以极大提升可控性和玩家体验。

三. 统一碰撞管理

1. 必要性

在游戏开发中,墙体、门、障碍区域等碰撞对象往往分布在地图的不同层级或组件中。
这类对象的共同特点是:不一定需要渲染,但必须参与碰撞判断

如果每个组件各自实现碰撞逻辑,容易出现以下问题:

  1. 逻辑分散:碰撞判断分布在多个组件中,角色移动与阻挡逻辑难以统一管理。
  2. 重复实现:不同碰撞对象往往需要相同的尺寸、位置和碰撞检测代码,容易产生冗余。
  3. 扩展不便:新增一种可碰撞对象时,需要重复编写或修改现有碰撞相关逻辑。

因此,有必要建立一个统一的碰撞管理,将可参与碰撞的能力集中管理。

2. 实现

(1)新建碰撞基类

抽象出一个BlockerComponent作为统一的碰撞基类,后续所有需要参与碰撞的组件直接继承该类即可,避免在各个组件中重复编写碰撞逻辑。

// blocker_component.dartabstractclassBlockerComponentextendsSpriteComponentwithCollisionCallbacks{latefinalRectangleHitbox hitbox;BlockerComponent({super.position,requiredsuper.size,bool addHitbox=true,}){if(addHitbox){hitbox=RectangleHitbox();add(hitbox);}}@overrideFuture<void>onLoad()async{// 创建 1×1 的透明 sprite,占位以满足 SpriteComponent 要求finalrecorder=PictureRecorder();finalcanvas=Canvas(recorder);finalpaint=Paint()..color=constColor(0x00000000);canvas.drawRect(constRect.fromLTWH(0,0,1,1),paint);finalpicture=recorder.endRecording();finalimage=awaitpicture.toImage(1,1);sprite=Sprite(image);}boolcollidesWith(Rect heroRect){returnhitbox.toAbsoluteRect().overlaps(heroRect);}}
  • 作为可碰撞组件的基础类

    • 继承SpriteComponent,统一管理位置与尺寸
    • 混入CollisionCallbacks,接入 Flame 碰撞体系
    • 使用RectangleHitbox作为实际的碰撞区域
  • 解决 SpriteComponent 必须绑定 sprite 的限制

    • onLoad中创建一个1×1的透明 sprite
    • 组件本身不可见,但在引擎层面是合法的渲染组件
  • 提供手动碰撞检测能力

    • 通过collidesWith(Rect heroRect)判断是否发生重叠
    • 适用于角色移动过程中的阻挡判断与位移修正
(2)修改代码

首先,我们就可以将原来的wall_component.dart继承了BlockerComponent

// wall_component.dartclassWallComponentextendsBlockerComponent{WallComponent({Vector2?position,required Vector2 size}):super(position:position,size:size);}

其次,在my_game.dart中,我们将原来只用于收集墙体的列表修改:

// my_game.dartfinalList<WallComponent>walls--->finalList<BlockerComponent>blockers// 统一添加管理blockers.add();

最后,在hero_component.dart中修改:

  • 碰撞性能优化集合
finalSet<BlockerComponent>_nearbyBlockers={};late RectangleHitbox _hitbox;
  • 碰撞检测方法
bool_wouldCollideWithBlockers(){finalheroRect=_hitbox.toAbsoluteRect();// 优先使用游戏维护的 blockers 集合for(finalblockeringame.blockers){if(blocker.collidesWith(heroRect))returntrue;}...// 兼容附近 blockersfor(finalnearbyin_nearbyBlockers){if(nearby.collidesWith(heroRect))returntrue;}returnfalse;}
  • 碰撞回调
@overridevoidonCollisionStart(Set<Vector2>intersectionPoints,PositionComponent other,){super.onCollisionStart(intersectionPoints,other);if(otherisBlockerComponent){_nearbyBlockers.add(other);}}@overridevoidonCollisionEnd(PositionComponent other){super.onCollisionEnd(other);if(otherisBlockerComponent){_nearbyBlockers.remove(other);}}

四. Tile Layer 构建水面碰撞区

构建水面碰撞区,简单来说就是遍历Tiled地图中的water图层,将所有非空瓦片转换为对应的WaterComponent,并按瓦片位置和大小添加到游戏世界中,用于表示水面。

方式一:逐瓦片创建组件(仅展示)

finalwaterLayer=tiled.tileMap.getLayer<TileLayer>('water');if(waterLayer!=null&&waterLayer.data!=null){finalwidth=waterLayer.width;finalheight=waterLayer.height;finaldata=waterLayer.data!;for(vary=0;y<height;y++){for(varx=0;x<width;x++){finalindex=y*width+x;finalgid=data[index];if(gid!=0){finalwater=WaterComponent(position:Vector2(x*tileSize*mapScale,y*tileSize*mapScale,),size:Vector2(tileSize*mapScale,tileSize*mapScale,),);// 添加至统一碰撞列表blockers.add(water);awaitworld.add(water);}}}}

这里将地图中每一个8x8的水瓦片,都单独变成一个WaterComponent对象。

可以看见地图中水面数量巨大,产生了大量组件极大降低渲染和碰撞效率

方式二:批量合并创建组件(推荐)

// my_game.dartfinalwaterLayer=tiled.tileMap.getLayer<TileLayer>('water');if(waterLayer!=null&&waterLayer.data!=null){awaitaddMergedTileLayerV2(tileData:waterLayer.data!,width:waterLayer.width,height:waterLayer.height,tileSize:tileSize,scale:mapScale,createComponent:(position,size)async{finalwater=WaterComponent(position:position,size:size);// 添加至统一碰撞列表blockers.add(water);returnwater;},parent:world,);}

这种方式通过addMergedTileLayerV2将相邻的水瓦片批量合并为较少的组件:

  • 大幅减少Component数量,提高性能
  • 保持碰撞逻辑和渲染一致
  • 可以统一加入blockers列表,方便英雄碰撞预测

可以看见地图中,数量巨大的组件,经过addMergedTileLayerV2方法批量合并后,仅为3个了。

批量合并方法

// common.dart/// 扫描 TileLayer 数据,将连续非零瓦片合并成矩形组件(水平+垂直合并)/// [tileData] - TileLayer.data,按行展开/// [width] - TileLayer 宽度(列数)/// [height] - TileLayer 高度(行数)/// [tileSize] - 每个瓦片大小/// [scale] - 地图缩放/// [createComponent] - 创建每个碰撞块的方法/// [parent] - 父组件,用于添加生成的组件Future<void>addMergedTileLayerV2({required List<int>tileData,required int width,required int height,required double tileSize,double scale=1.0,required Future<PositionComponent>Function(Vector2 position,Vector2 size)createComponent,required Component parent,})async{// Step1: 先按行生成水平合并块List<List<int>>rects=[];// 每行的开始列和宽度List<List<int>>horizontalRects=List.generate(height,(_)=>[]);for(vary=0;y<height;y++){int startX=-1;for(varx=0;x<=width;x++){finalisFilled=x<width&&tileData[y*width+x]!=0;if(isFilled&&startX==-1){startX=x;}if((!isFilled||x==width)&&startX!=-1){horizontalRects[y].addAll([startX,x-startX]);startX=-1;}}}// Step2: 垂直合并相同列和宽度的块List<List<int>>processed=List.generate(height,(_)=>List.filled(width,0));for(vary=0;y<height;y++){for(vari=0;i<horizontalRects[y].length;i+=2){finalxStart=horizontalRects[y][i];finalw=horizontalRects[y][i+1];if(processed[y][xStart]==1)continue;// 尝试向下合并int rectHeight=1;for(varyy=y+1;yy<height;yy++){bool canMerge=false;for(varj=0;j<horizontalRects[yy].length;j+=2){if(horizontalRects[yy][j]==xStart&&horizontalRects[yy][j+1]==w){canMerge=true;break;}}if(canMerge){rectHeight++;for(varcol=xStart;col<xStart+w;col++){processed[yy][col]=1;}}else{break;}}// 创建组件finalposition=Vector2(xStart*tileSize*scale,y*tileSize*scale);finalsize=Vector2(w*tileSize*scale,rectHeight*tileSize*scale);finalblock=awaitcreateComponent(position,size);awaitparent.add(block);for(varcol=xStart;col<xStart+w;col++){processed[y][col]=1;}}}}

addMergedTileLayerV2用于扫描 TileLayer 的瓦片数据,将相邻的非空瓦片先进行水平方向合并,再在此基础上进行垂直方向合并,最终把多个连续瓦片合并为更大的矩形组件

五. 宝箱 - 无验证交互式解锁

1. 简述

宝箱是一种无需条件验证的可交互地图对象,通过角色接触即可触发交互。

  • 继承关系:无需继承碰撞基类

  • 功能:提供可拾取奖励

  • 交互逻辑

    • 玩家靠近宝箱即可触发打开动作
    • 打开后玩家可选择是否拾取奖励

2. Tiled 前置工作

  1. 新建Object Layer图层,命名为treasure
  2. 点击使用上方矩形框选工具。
  3. 在地图中所需位置,正确绘制宝箱大小的矩形。
  4. 添加属性:
  • type:trasure
  • status:表示宝箱状态,一共有三种:closed(关闭)full(打开未拿取)enmpty(打开已拿取)

3. 构建宝箱类

// treasure_component.dartclassTreasureComponentextendsSpriteComponentwithHasGameReference<MyGame>,CollisionCallbacks{String status;late RectangleHitbox _hitbox;TreasureComponent({requiredthis.status,required Vector2 position,required Vector2 size,}):super(position:position,size:size);@overrideFuture<void>onLoad()async{awaitsuper.onLoad();sprite=awaitSprite.load(status=='closed'?'closed_treasure.png':status=='full'?'full_treasure.png':'empty_treasure.png',);_hitbox=RectangleHitbox();add(_hitbox);}@overridevoidonCollisionStart(Set<Vector2>intersectionPoints,PositionComponent other,){super.onCollisionStart(intersectionPoints,other);if(otherisHeroComponent){attemptOpen(other);}}Future<void>_open()async{if(status=='closed'){status='full';sprite=awaitSprite.load('full_treasure.png');}}Future<void>_collect(HeroComponent hero)async{if(status=='full'){status='empty';sprite=awaitSprite.load('empty_treasure.png');UiNotify.showToast(game,'获得宝物');}}voidattemptOpen(HeroComponent hero){if(status=='closed'){_open();}elseif(status=='full'){finalexists=game.camera.viewport.children.query<DialogComponent>().isNotEmpty;if(!exists){finaldialog=DialogComponent.confirm(message:'是否拿取宝物',onConfirm:()=>_collect(hero),onCancel:(){},);game.camera.viewport.add(dialog);}}}}

TreasureComponent作为宝箱实体类,负责显示与交互的逻辑:

  • 继承SpriteComponent+ 碰撞回调

    • 具备位置、尺寸和碰撞检测能力
  • 状态管理

    • status = closed:关闭状态,触碰后自动打开
    • status = full:已打开状态,弹出对话框确认是否拾取
    • status = empty:已拿取状态,仅显示空宝箱
  • 视图更新

    • 根据宝箱状态动态加载不同贴图
  • 交互逻辑

    • 人物触碰到宝箱区域后,根据当前状态,决定下一步行为

4. 加载宝箱

// my_game.dartfinaltreasureLayer=tiled.tileMap.getLayer<ObjectGroup>('treasure');if(treasureLayer!=null){for(finalobjintreasureLayer.objects){if(obj.properties['type']?.value=='treasure'){finalstatus=obj.properties['status']!.valueasString;finalx=mapScale*obj.x;finaly=mapScale*obj.y;finalw=mapScale*obj.width;finalh=mapScale*obj.height;finaltreasureComponent=TreasureComponent(status:status,position:Vector2(x,y),size:Vector2(w,h),);treasureComponent.debugMode=true;awaitworld.add(treasureComponent);}}}

六. 钥匙与门 - 验证交互式解锁

1. 简述

钥匙与门是一组具备条件验证的交互对象,通过拾取钥匙 → 解锁对应门实现地图通行控制。

  • 继承关系:门继承碰撞基类,当门未打开时具备阻挡能力,与墙体或水面碰撞逻辑一致

  • 钥匙:可拾取对象,角色接触后获得对应keyId

  • :阻挡地图通行,需角色持有匹配钥匙才能打开

  • 交互逻辑

    • 角色与门交互时,判断是否持有匹配钥匙
    • 条件满足时门打开,否则保持阻挡状态

2. Tiled 前置工作

  1. 新建两个Object Layer图层,分别命名为keydoor
  2. 点击使用上方矩形框选工具。
  3. 在地图中所需位置,正确绘制钥匙和门大小的矩形。
  4. 添加属性:
  • 钥匙
    • type:key
    • keyId:对应指定的钥匙与门
    • type:door
    • keyId:对应指定的钥匙与门
    • status:表示门状态,一共有两种:closed(关闭)open(打开)

3. 构建钥匙类

// key_component.dartclassKeyComponentextendsSpriteComponentwithHasGameReference<MyGame>,CollisionCallbacks{finalString keyId;KeyComponent({requiredthis.keyId,required Vector2 position,required Vector2 size,}):super(position:position,size:size);@overrideFuture<void>onLoad()async{awaitsuper.onLoad();sprite=awaitSprite.load('key.png');// 直接加载 assets/key.pngadd(RectangleHitbox());}@overridevoidonCollisionStart(Set<Vector2>intersectionPoints,PositionComponent other){super.onCollisionStart(intersectionPoints,other);if(otherisHeroComponent){other.addKey(keyId);removeFromParent();// 拾取后移除当前 tile}}}

KeyComponent作为可拾取的钥匙实体,用于角色解锁对应门:

  • 继承SpriteComponent+ 碰撞回调

    • 具备位置、尺寸和碰撞检测能力
  • 钥匙标识keyId

    • 用于与门的keyId对应,实现解锁验证
  • 碰撞拾取逻辑

    • 当角色碰到钥匙:
      keyId存入角色钥匙集合
      从地图中移除钥匙实体,避免重复拾取

4. 构建门类

// door_component.dartclassDoorComponentextendsBlockerComponentwithHasGameReference<MyGame>{finalString keyId;bool isOpen;DoorComponent({requiredthis.keyId,requiredthis.isOpen,super.position,requiredsuper.size,}):super(addHitbox:!isOpen);@overrideFuture<void>onLoad()async{awaitsuper.onLoad();sprite=awaitSprite.load(isOpen?'open_door.png':'closed_door.png');}void_unlock()async{if(!isOpen){isOpen=true;sprite=awaitSprite.load('open_door.png');hitbox.removeFromParent();}}voidattemptOpen(HeroComponent hero){if(!isOpen&&hero.hasKey(keyId)){unlock();}elseif(!isOpen){UiNotify.showToast(game,'需要钥匙 $keyId 才能打开');}}}

DoorComponent作为门的实体类,主要职责是阻挡角色通行并支持钥匙验证解锁

  • 继承BlockerComponent

    • 默认有碰撞体阻挡角色
    • 可根据isOpen状态决定是否添加碰撞体
  • 状态管理

    • isOpen = false:门关闭,阻挡角色
    • isOpen = true:门打开,移除碰撞体,允许角色通过
  • 钥匙验证逻辑

    • attemptOpen(hero):当角色接触门时触发

      • 如果角色持有匹配keyId,调用_unlock()打开门
      • 否则弹出提示,告知需要钥匙
  • 视图更新

    • 根据门状态动态加载不同贴图(open_door.png/closed_door.png
    • 打开时同步移除碰撞体,保证角色能通过

5. 加载钥匙与门

// my_game.dart// ---- 处理 Key Layer 中的钥匙 ----finalkeyLayer=tiled.tileMap.getLayer<ObjectGroup>('key');if(keyLayer!=null){for(finalobjinkeyLayer.objects){if(obj.properties['type']?.value=='key'){finalkeyId=obj.properties['keyId']!.valueasString;finalx=mapScale*obj.x;finaly=mapScale*obj.y;finalw=mapScale*obj.width;finalh=mapScale*obj.height;finalkeyComponent=KeyComponent(keyId:keyId,// Tiled 对象坐标为左上或底对齐;这里使用底对齐放置更符合 tile 外观position:Vector2(x,y),size:Vector2(w,h),);keyComponent.debugMode=true;awaitworld.add(keyComponent);}}}// ---- 处理 Door Layer 中的门 ----finaldoorLayer=tiled.tileMap.getLayer<ObjectGroup>('door');if(doorLayer!=null){for(finalobjindoorLayer.objects){if(obj.properties['type']?.value=='door'){finalkeyId=obj.properties['keyId']!.valueasString;finalx=mapScale*obj.x;finaly=mapScale*obj.y;finalw=mapScale*obj.width;finalh=mapScale*obj.height;finaldoorComponent=DoorComponent(keyId:keyId,isOpen:obj.properties['status']?.value=='open'?true:false,// Tiled 对象坐标为左上或底对齐;这里使用底对齐放置更符合 tile 外观position:Vector2(x,y),size:Vector2(w,h),);doorComponent.debugMode=true;awaitworld.add(doorComponent);}}}

6. 完善逻辑

HeroComponent中,为角色添加钥匙管理与门验证功能:

(1). 钥匙管理
finalSet<String>keys={};
  • 存储角色已拾取的钥匙keyId集合
voidaddKey(String keyId){keys.add(keyId);UiNotify.showToast(game,'获得钥匙: $keyId');}
  • 角色拾取钥匙时调用
  • 弹出提示通知玩家获得钥匙
boolhasKey(String keyId)=>keys.contains(keyId);
  • 判断角色是否持有指定钥匙
  • 用于门解锁验证

(2). 门交互逻辑
for(finaldooringame.world.children.query<DoorComponent>()){if(!door.isOpen&&door.collidesWith(heroRect)){door.attemptOpen(this);if(!door.isOpen)returntrue;}}

在角色移动碰撞检测_wouldCollideWithBlockers()中:

  • 遍历所有门组件
  • 检测角色是否与关闭的门发生碰撞
  • 调用door.attemptOpen(this)尝试解锁
  • 如果门仍未打开,则返回阻挡信息,阻止角色移动

七. 地刺

1. 简述

地刺是一种周期性造成伤害的地图障碍,通过状态切换形成完整的伤害与死亡判定逻辑。

  • 继承关系:无需继承碰撞基类

  • 功能:对接触的角色造成伤害

  • 状态:突出与收起两种状态,周期性切换

  • 交互逻辑

    • 角色接触突出状态的地刺会受到伤害
    • 触发死亡判定后可触发游戏重启或复活逻辑
    • 周期性切换状态增强地图挑战性

2. Tiled 前置工作

  1. 新建Object Layer图层,命名为thorn
  2. 点击使用上方矩形框选工具。
  3. 在地图中所需位置,正确绘制地刺大小的矩形。
  4. 添加属性:
  • type:thorn
  • status:表示地刺状态,一共有两种:on(突出)off(收入)

3. 构建地刺类

// thorn_component.dartclassThornComponentextendsSpriteComponentwithHasGameReference<MyGame>,CollisionCallbacks{String status;late RectangleHitbox _hitbox;double _elapsed=0;double period=2.0;bool hurted=false;ThornComponent({super.position,requiredsuper.size,requiredthis.status});@overrideFuture<void>onLoad()async{awaitsuper.onLoad();sprite=awaitSprite.load(status=='on'?'thorn_on.png':'thorn_off.png',);_hitbox=RectangleHitbox();add(_hitbox);}@overridevoidonCollisionStart(Set<Vector2>intersectionPoints,PositionComponent other,){super.onCollisionStart(intersectionPoints,other);if(otherisHeroComponent){attemptDamage(other);}}voidattemptDamage(HeroComponent hero){if(status=='on'&&!hurted){hero.loseHp(1);hurted=true;}}void_toggle()async{if(status=='on'){status='off';}else{status='on';hurted=false;}sprite=awaitSprite.load(status=='on'?'thorn_on.png':'thorn_off.png',);}@overridevoidupdate(double dt){super.update(dt);_elapsed+=dt;if(_elapsed>=period){_elapsed=0;_toggle();}}}

ThornComponent负责地刺的显示、周期切换和伤害逻辑

  • 继承SpriteComponent+ 碰撞回调

    • 具备位置、尺寸和碰撞检测能力
  • 状态管理

    • on:突出,触碰造成伤害
    • off:收起,不造成伤害
  • 伤害逻辑

    • attemptDamage(hero):保证每周期只造成一次伤害
  • 周期切换

    • _toggle()在固定period时间间隔内切换状态,并更新贴图

4. 加载地刺

// my_game.dart// ---- 处理 thorn 中的荆棘 ----finalthornLayer=tiled.tileMap.getLayer<ObjectGroup>('thorn');if(thornLayer!=null){for(finalobjinthornLayer.objects){if(obj.properties['type']?.value=='thorn'){finalstatus=obj.properties['status']!.valueasString;finalx=mapScale*obj.x;finaly=mapScale*obj.y;finalw=mapScale*obj.width;finalh=mapScale*obj.height;finalthornComponent=ThornComponent(status:status,position:Vector2(x,y),size:Vector2(w,h),);thornComponent.debugMode=true;awaitworld.add(thornComponent);}}}

5. 完善逻辑

HeroComponent中, 实现了角色生命值管理 + 死亡判定的功能:

  • 增加hp属性,记录角色生命值

    // 生命值int hp=5;
  • loseHp(amount):被地刺等障碍伤害时调用,扣除生命值并显示提示

    voidloseHp(int amount){hp=hp-amount;if(hp<=0){UiNotify.showToast(game,'死亡');}else{UiNotify.showToast(game,'HP -$amount 剩余 $hp');}}
  • hp <= 0:触发gameOver()

    • 播放死亡动画
    • 2 秒后显示重新开始弹窗
    Future<void>gameOver()async{if(_isGameOver)return;_isGameOver=true;// 播放死亡动画if(animations.containsKey(HeroState.dead)){_setState(HeroState.dead);}// 等待 2 秒,让死亡动画播放awaitFuture.delayed(constDuration(seconds:2));// 显示重新开始弹窗finalexists=game.camera.viewport.children.query<RestartOverlay>().isNotEmpty;if(!exists){game.camera.viewport.add(RestartOverlay());}}

八. 总结与展望

总结

本章主要介绍了Flutter&Flame开发2D像素游戏关于人物与地图元素交互的基础实践。
通过上述步骤,我们完成了宝箱、钥匙、门 和 地刺这几类常见元素的交互 。

截至目前为止,游戏主要包括了以下内容:

  • 角色与动画:使用精灵图 (SpriteSheet) 创建角色,支持 idle/run 等动画状态切换。
  • 玩家交互:通过摇杆控制角色移动,并根据方向翻转动画。
  • 地图加载:通过Tiled绘制并在Flame中加载的2d像素地图
  • 地图交互:通过组件化模式,新建了多个可供交互的组件如(门、钥匙、宝箱、地刺),为游戏增加了互动性。
  • 统一碰撞区检测:将角色与需要产生碰撞的物体统一管理,并实现碰撞时的平滑侧移

展望

  • 思考 🤔 一个有趣的游戏机制ing …

  • 完成攻击与技能系统,包括动画切换、攻击范围和远程弹道。

  • 实现怪物生成、自动攻击与玩家碰撞逻辑。

  • 支持局域网多玩家联机功能。

🚪 github 源码
💻 个人门户网站


之前尝试的Demo预览

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:53:56

无刷直流电机调速的Simulink仿真模型:动态控制、无传感器控制与波形纪录全解析

无刷直流电机的调速 Matlab/simulink仿真搭建模型 介绍&#xff1a;该模型展示了无刷直流电机的速度控制。 无刷直流电机有完整的动态模型。 将电机的实际转速与参考转速进行比较&#xff0c;以控制三相逆变器来调节端电压。 该型号的BLDC电机也可用于BLDC电机的无传感器控制。…

作者头像 李华
网站建设 2026/4/16 11:00:15

小红书×云器科技|增量计算+实时湖仓构建小红书实验数仓生产新范式

导读 随着移动互联网内容生态爆发&#xff0c;带来小红书日均千亿级的流量日志增长&#xff0c;与此同时&#xff0c;算法实验迭代的时效要求也在持续提高&#xff0c;传统的数据架构难以在低成本和低延迟之间取得很好的平衡。小红书与云器科技合作&#xff0c;基于增量计算与…

作者头像 李华
网站建设 2026/4/15 13:46:51

用户画像驱动的软件测试设计:方法与实践

在当今用户体验至上的软件时代&#xff0c;传统的测试设计方法往往局限于功能验证与错误检测&#xff0c;而忽视了用户群体多样性对系统行为的深层影响。用户画像&#xff08;User Persona&#xff09;作为一种描绘目标用户特征、行为模式与需求场景的工具&#xff0c;为测试团…

作者头像 李华
网站建设 2026/4/13 0:32:46

ros2安装

Ubuntu 22.04 官网 Ubuntu (deb packages) — ROS 2 Documentation: Humble documentation 1、设置 locale 这是确保你的本地语言支持 UTF-8。 locale # check for UTF-8sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo upd…

作者头像 李华
网站建设 2026/4/16 10:55:31

基于模型的测试设计(MBT):软件测试的智能化革新

在当今快速迭代的软件开发环境中&#xff0c;传统测试方法如手动测试和基于脚本的自动化测试&#xff0c;往往难以应对复杂系统的需求。基于模型的测试设计&#xff08;Model-Based Testing, MBT&#xff09;应运而生&#xff0c;作为一种以模型为核心的测试方法&#xff0c;它…

作者头像 李华
网站建设 2026/4/15 15:49:56

个人财务健康指数开发与应用

个人财务健康指数开发与应用关键词&#xff1a;个人财务健康指数、开发、应用、财务评估、数据建模摘要&#xff1a;本文围绕个人财务健康指数的开发与应用展开深入探讨。详细阐述了个人财务健康指数的核心概念、相关联系以及其背后的核心算法原理&#xff0c;通过数学模型和公…

作者头像 李华