Flutter游戏开发框架插件game_scaffold的使用
Flutter游戏开发框架插件game_scaffold的使用
game_scaffold
是一个用于游戏开发的 Flutter 扩展插件,基于 game_scaffold_dart
。它提供了一套完整的解决方案来帮助开发者快速搭建和管理游戏逻辑。
使用示例
示例代码
import 'package:flutter/material.dart';
import 'package:game_scaffold/game_scaffold.dart';
import 'package:game_scaffold_games/games.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart';
void main() {
TicTacToeGame.register();
Logger.root.clearListeners();
Logger.root.level = Level.FINE;
Logger.root.onRecord.listen((record) =>
// 忽略:避免打印
print('[${record.level}] ${record.loggerName}: ${record.message}'));
runApp(ProviderScope(
overrides: [
GameProviders.clientType
.overrideWithValue(StateController(OnDeviceClient)),
],
child: const TicTacToeApp(),
));
}
class TicTacToeApp extends StatelessWidget {
const TicTacToeApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark().copyWith(
primaryColor: Colors.blueGrey,
),
home: const TicTacToeWidget(),
);
}
class TicTacToeWidget extends StatelessWidget {
const TicTacToeWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
body: Row(children: [
Expanded(
child: ProviderScope(
overrides: [GameProviders.playerID.overrideWithValue(P1)],
child: const Player(),
),
),
Container(width: 10, color: Colors.black),
Expanded(
child: ProviderScope(
overrides: [GameProviders.playerID.overrideWithValue(P2)],
child: const Player(),
),
),
]),
));
}
class Player extends HookConsumerWidget {
const Player({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) => const GameNavigator(
connected: CreateOrJoinWidget(),
lobby: LobbyWidget(),
game: GameWidget(),
);
}
class CreateOrJoinWidget extends HookConsumerWidget {
const CreateOrJoinWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final playerID = ref.watch(GameProviders.playerID);
final allGames = ref.watch(GameProviders.allGames);
return Scaffold(
body: Center(
child: Column(
children: [
const SizedBox(height: 40),
Text('Player $playerID'),
const SizedBox(height: 20),
if (playerID == P1)
ElevatedButton(
key: Key('Create Game Button $playerID'),
onPressed: () async {
ref.read(GameProviders.config.notifier).state =
const GameConfig(
adminID: P1,
customNames: false,
gameType: 'tictactoe',
rounds: 2,
maxPlayers: 2,
);
await GameProviders.createGame.refresh(ref);
await GameProviders.joinGame.refresh(ref);
},
child: const Text('Create Game'),
),
if (playerID == P2) ...[
SizedBox(
width: 200,
height: 30,
child: TextField(
textAlignVertical: TextAlignVertical.center,
decoration:
const InputDecoration(hintText: 'Enter Game Code'),
onChanged: (text) =>
ref.read(GameProviders.code.notifier).state = text,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
GameProviders.joinGame.refresh(ref);
},
child: const Text('Join Game'),
)
],
if (allGames.value != null)
for (final info in allGames.value!)
ElevatedButton(
onPressed: () async {
ref.read(GameProviders.code.notifier).state = info.gameId;
await GameProviders.joinGame.refresh(ref);
},
child: Text(
'Started Game: ${info.gameId}, Players: ${info.players}'),
),
],
),
),
);
}
}
class LobbyWidget extends HookConsumerWidget {
const LobbyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final lobby = ref.watch(GameProviders.lobby);
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(children: [
const SizedBox(height: 40),
const Text('Lobby'),
Text('${lobby.value}'),
]),
),
);
}
}
class GameWidget extends HookConsumerWidget {
const GameWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final gameState = ref.watch(GameProviders.game);
final gameStatus = ref.watch(GameProviders.status);
final playerID = ref.watch(GameProviders.playerID);
ref.listen<AsyncValue<GameError>>(GameProviders.error, (prevError, error) {
if (error != prevError) {
showDialog(
context: context,
builder: (c) => Dialog(
backgroundColor: Colors.white,
child: Text(error.toString()),
),
);
}
});
return gameState.when(
error: (e, st) => Text('$e, $st'),
loading: () => const Center(child: CircularProgressIndicator()),
data: (g) => Scaffold(
appBar: AppBar(),
body: Center(
child: ListView(
children: [
Text('$gameState'),
const SizedBox(height: 20),
for (final r in [0, 1, 2])
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final c in [0, 1, 2])
GestureDetector(
key: Key('$playerID square $r $c'),
onTap: () async {
await GameProviders.sendEvent(
TicTacToeGameEvent(playerID, r * 3 + c).asGameEvent,
).refresh(ref);
},
child: ColoredBox(
color: Colors.black,
child: Container(
width: 20,
height: 20,
color: Colors.white,
margin: const EdgeInsets.all(1),
child: Center(
child: Text(
(g as TicTacToeGame)
.board
.xOrO(playerID, r * 3 + c),
),
),
),
),
),
],
),
if (gameStatus == GameStatus.BetweenRounds && !g.readyPlayers.contains(playerID)) ...[
const SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
await GameProviders.newRound.refresh(ref);
},
child: const Text('Next Round'),
),
],
],
),
),
),
);
}
}
extension TextX on List<String?> {
String xOrO(String playerID, int location) {
if (this[location] == null) {
return '';
} else if (this[location] == P1) {
return 'X';
} else {
return 'O';
}
}
}
示例说明
-
导入库
import 'package:flutter/material.dart'; import 'package:game_scaffold/game_scaffold.dart'; import 'package:game_scaffold_games/games.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart';
-
主函数
void main() { TicTacToeGame.register(); Logger.root.clearListeners(); Logger.root.level = Level.FINE; Logger.root.onRecord.listen((record) => // 忽略:避免打印 print('[${record.level}] ${record.loggerName}: ${record.message}')); runApp(ProviderScope( overrides: [ GameProviders.clientType .overrideWithValue(StateController(OnDeviceClient)), ], child: const TicTacToeApp(), )); }
-
创建应用
class TicTacToeApp extends StatelessWidget { const TicTacToeApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) => MaterialApp( title: 'Flutter Demo', theme: ThemeData.dark().copyWith( primaryColor: Colors.blueGrey, ), home: const TicTacToeWidget(), ); }
-
游戏布局
class TicTacToeWidget extends StatelessWidget { const TicTacToeWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) => MaterialApp( home: Scaffold( body: Row(children: [ Expanded( child: ProviderScope( overrides: [GameProviders.playerID.overrideWithValue(P1)], child: const Player(), ), ), Container(width: 10, color: Colors.black), Expanded( child: ProviderScope( overrides: [GameProviders.playerID.overrideWithValue(P2)], child: const Player(), ), ), ]), )); }
-
玩家组件
class Player extends HookConsumerWidget { const Player({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) => const GameNavigator( connected: CreateOrJoinWidget(), lobby: LobbyWidget(), game: GameWidget(), ); }
-
创建或加入游戏
class CreateOrJoinWidget extends HookConsumerWidget { const CreateOrJoinWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final playerID = ref.watch(GameProviders.playerID); final allGames = ref.watch(GameProviders.allGames); return Scaffold( body: Center( child: Column( children: [ const SizedBox(height: 40), Text('Player $playerID'), const SizedBox(height: 20), if (playerID == P1) ElevatedButton( key: Key('Create Game Button $playerID'), onPressed: () async { ref.read(GameProviders.config.notifier).state = const GameConfig( adminID: P1, customNames: false, gameType: 'tictactoe', rounds: 2, maxPlayers: 2, ); await GameProviders.createGame.refresh(ref); await GameProviders.joinGame.refresh(ref); }, child: const Text('Create Game'), ), if (playerID == P2) ...[ SizedBox( width: 200, height: 30, child: TextField( textAlignVertical: TextAlignVertical.center, decoration: const InputDecoration(hintText: 'Enter Game Code'), onChanged: (text) => ref.read(GameProviders.code.notifier).state = text, ), ), const SizedBox(height: 20), ElevatedButton( onPressed: () { GameProviders.joinGame.refresh(ref); }, child: const Text('Join Game'), ) ], if (allGames.value != null) for (final info in allGames.value!) ElevatedButton( onPressed: () async { ref.read(GameProviders.code.notifier).state = info.gameId; await GameProviders.joinGame.refresh(ref); }, child: Text( 'Started Game: ${info.gameId}, Players: ${info.players}'), ), ], ), ), ); } }
-
大厅组件
class LobbyWidget extends HookConsumerWidget { const LobbyWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final lobby = ref.watch(GameProviders.lobby); return Scaffold( appBar: AppBar(), body: Center( child: Column(children: [ const SizedBox(height: 40), const Text('Lobby'), Text('${lobby.value}'), ]), ), ); } }
-
游戏组件
class GameWidget extends HookConsumerWidget { const GameWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { final gameState = ref.watch(GameProviders.game); final gameStatus = ref.watch(GameProviders.status); final playerID = ref.watch(GameProviders.playerID); ref.listen<AsyncValue<GameError>>(GameProviders.error, (prevError, error) { if (error != prevError) { showDialog( context: context, builder: (c) => Dialog( backgroundColor: Colors.white, child: Text(error.toString()), ), ); } }); return gameState.when( error: (e, st) => Text('$e, $st'), loading: () => const Center(child: CircularProgressIndicator()), data: (g) => Scaffold( appBar: AppBar(), body: Center( child: ListView( children: [ Text('$gameState'), const SizedBox(height: 20), for (final r in [0, 1, 2]) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ for (final c in [0, 1, 2]) GestureDetector( key: Key('$playerID square $r $c'), onTap: () async { await GameProviders.sendEvent( TicTacToeGameEvent(playerID, r * 3 + c).asGameEvent, ).refresh(ref); }, child: ColoredBox( color: Colors.black, child: Container( width: 20, height: 20, color: Colors.white, margin: const EdgeInsets.all(1), child: Center( child: Text( (g as TicTacToeGame) .board .xOrO(playerID, r * 3 + c), ), ), ), ), ), ], ), if (gameStatus == GameStatus.BetweenRounds && !g.readyPlayers.contains(playerID)) ...[ const SizedBox(height: 20), ElevatedButton( onPressed: () async { await GameProviders.newRound.refresh(ref); }, child: const Text('Next Round'), ), ], ], ), ), ), ); } }
-
扩展方法
extension TextX on List<String?> { String xOrO(String playerID, int location) { if (this[location] == null) { return ''; } else if (this[location] == P1) { return 'X'; } else { return 'O'; } } }
更多关于Flutter游戏开发框架插件game_scaffold的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter游戏开发框架插件game_scaffold的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
game_scaffold
是一个为 Flutter 游戏开发提供的基础框架插件,它旨在简化游戏开发中的一些常见任务,例如管理游戏循环、处理输入、渲染等。通过使用 game_scaffold
,开发者可以更专注于游戏逻辑的实现,而不必从头开始搭建基础框架。
安装 game_scaffold
首先,你需要在 pubspec.yaml
文件中添加 game_scaffold
插件的依赖:
dependencies:
flutter:
sdk: flutter
game_scaffold: ^0.1.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
基本使用
game_scaffold
提供了一个 GameScaffold
类,你可以将其作为游戏的主框架。以下是一个简单的使用示例:
import 'package:flutter/material.dart';
import 'package:game_scaffold/game_scaffold.dart';
void main() {
runApp(MyGame());
}
class MyGame extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: GameScaffold(
game: MyGameLogic(),
),
);
}
}
class MyGameLogic extends Game {
@override
void update(double delta) {
// 更新游戏逻辑
}
@override
void render(Canvas canvas) {
// 渲染游戏内容
}
}
GameScaffold
的主要功能
-
游戏循环管理:
GameScaffold
会自动管理游戏循环,调用update
和render
方法。你只需要在update
方法中处理游戏逻辑,在render
方法中绘制游戏内容。 -
输入处理:
GameScaffold
提供了对触摸输入的支持。你可以通过重写onTapDown
、onTapUp
等方法来处理用户的触摸事件。 -
屏幕适配:
GameScaffold
会自动处理不同屏幕尺寸的适配问题,确保游戏在不同设备上都能正常显示。
示例:处理触摸事件
class MyGameLogic extends Game {
Offset? tapPosition;
@override
void update(double delta) {
// 更新游戏逻辑
}
@override
void render(Canvas canvas) {
if (tapPosition != null) {
final paint = Paint()..color = Colors.red;
canvas.drawCircle(tapPosition!, 20, paint);
}
}
@override
void onTapDown(TapDownDetails details) {
tapPosition = details.localPosition;
}
@override
void onTapUp(TapUpDetails details) {
tapPosition = null;
}
}
在这个示例中,当用户触摸屏幕时,游戏会在触摸位置绘制一个红色的圆圈。当用户松开手指时,圆圈会消失。
自定义游戏循环
如果你需要自定义游戏循环的频率,可以通过 GameScaffold
的 updateInterval
参数来设置:
GameScaffold(
game: MyGameLogic(),
updateInterval: Duration(milliseconds: 16), // 大约 60 FPS
);