接着教程一,不讲废话,开始创建游戏主体功能,本来想分功能来讲像手柄什么的,想想还是按项目模块来讲,按顺序用到了什么就讲什么:
bloc状态
在main中声明了一个全局状态GlobalCubit globalCubit = GlobalCubit();,让我们看下这个状态应该怎么写:
- global_cubit.dart
js
class GlobalCubit extends Cubit<GlobalState> {
GlobalCubit() : super(GlobalInitial(0));
increment() {
emit(GlobalInitial(state.count + 1));
}
}
这里要注意的是emit传一个新的state,直接改state上的状态是不生效的。
- global_state.dart
js
@immutable
abstract class GlobalState {
int count = 0;
}
class GlobalInitial extends GlobalState {
GlobalInitial(int count) {
this.count = count;
}
}
这里只是意思下只定义了一个count,登录信息等等都可以放在这。
登录
先上代码:
js
class LoginWidget extends StatefulWidget {
const LoginWidget({super.key});
@override
State<StatefulWidget> createState() {
return LoginWidgetState();
}
}
class LoginWidgetState extends State<LoginWidget> {
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: app.globalCubit,
child: BlocBuilder<GlobalCubit, GlobalState>(
builder: (context, state) => Material(
child: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/login_bg.png"),
fit: BoxFit.cover,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'游戏名${state.count}',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red, fontSize: 24),
),
Container(
margin: const EdgeInsets.only(top: 10),
width: 200,
color: Colors.white,
child: TextField(
maxLines: 1,
controller: TextEditingController(),
decoration: const InputDecoration(
hintText: '用户名',
contentPadding: EdgeInsets.fromLTRB(10, 0, 10, 0),
),
),
),
Container(
width: 200,
margin: const EdgeInsets.only(top: 10, bottom: 10),
color: Colors.white,
child: TextField(
maxLines: 1,
controller: TextEditingController(),
decoration: const InputDecoration(
hintText: '密码',
contentPadding: EdgeInsets.fromLTRB(10, 0, 10, 0),
),
),
),
ElevatedButton(
onPressed: () {
// context.read<GlobalCubit>().increment();
app.globalCubit.increment();
Navigator.pushReplacementNamed(context, "game");
},
style: const ButtonStyle(
// backgroundColor: MaterialStateProperty.all(Colors.red),
),
child: const SizedBox(
width: 170,
child: Text(
'登 录',
textAlign: TextAlign.center,
),
),
),
],
),
),
),
),
);
}
}

内容很简单,就是一个背景、两个输入框、一个登录按钮。这里要格外注意的一点是:因为是全局的原因,必须是BlocProvider.value这种形式,如果是builder形式在跳转后,页面销毁,state会被close掉,后续使用就状态异常了。
主场景
老规矩,先上代码再讲:
js
class MyGame extends ContextGame {
late TiledComponent mapComponent;
late final CameraComponent cameraComponent;
MyGame(super.context);
@override
Future<void> onLoad() async {
var joystick = MyJoystickComponent();
add(joystick);
var player = PlayerComponent(joystick);
var skillJoystick = SkillJoystickComponent(
size: Vector2(200, 200),
position: Vector2(canvasSize.x - 200, canvasSize.y - 200),
player: player,
);
add(skillJoystick);
final world = CollisionDetectionWorld();
final CameraComponent cameraComponent = CameraComponent(world: world);
// final CameraComponent cameraComponent = CameraComponent.withFixedResolution(
// world: world,
// width: 16 * 280,
// height: 16 * 140,
// );
mapComponent = await TiledComponent.load('map1.tmx', Vector2(32.0, 32.0));
world.add(mapComponent);
world.add(player);
// world.add(RectangleCollidable(canvasSize));
cameraComponent.follow(player);
//获取硬币的占位,然后动态添加
final objectGroup = mapComponent.tileMap.getLayer<ObjectGroup>('AnimatedCoins');
for (final object in objectGroup!.objects) {
world.add(CoinComponent(position: Vector2(object.x, object.y)));
}
addAll([cameraComponent]);
await add(FlameMultiBlocProvider(
providers: [FlameBlocProvider<GlobalCubit, GlobalState>.value(value: app.globalCubit)],
children: [world],
));
}
}
这里我把经常用的的组件基本都上了:
- joystick 手柄:这个框架自带了一个基本可以直接使用。
- player 游戏主角
- skillJoystick:自定义的一个技能盘
- world:1.8.2以前没有world这个概念,其实就是在Game增加了个layer,相当于安卓activity中放了个fragment,切换场景功能的增强。
- cameraComponent:1.8.2以前Game自带了一个camera对象,1.8.2以后就开始废弃了。
cameraComponent.follow(player);让视口跟着主角。 - mapComponent:背景地图。这里直接加载tmx资源。地图中有一些比如金币什么的需要动态创建,因为还有吃掉硬币的碰撞检查。
- FlameMultiBlocProvider:全局状态绑定
至此app运行后可能是这个样子的:
