mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-01 01:18:38 +08:00 
			
		
		
		
	docs: Jenny example (#3086)
Replace this text. --------- Co-authored-by: xjyribro <limcheekee.63@gmail.com> Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								examples/assets/images/dialogue_box.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/assets/images/dialogue_box.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								examples/assets/images/green_button_sqr.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/assets/images/green_button_sqr.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 675 B | 
							
								
								
									
										
											BIN
										
									
								
								examples/assets/images/red_button_sqr.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/assets/images/red_button_sqr.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 670 B | 
							
								
								
									
										21
									
								
								examples/assets/yarn/advanced.yarn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								examples/assets/yarn/advanced.yarn
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| <<character Jenny>> | ||||
| <<declare $winnings = 0>> | ||||
| title: gamble | ||||
| --- | ||||
| Jenny: Hello {$playerName}. This is a game of chance. | ||||
| Jenny: You can win or lose up to 10 coins. Do you want to play? | ||||
| -> No | ||||
|     Jenny: No bids made. | ||||
| -> Yes | ||||
|     <<set $winnings = dice(21) - 11>> // returns a random value from -10 to 10 | ||||
|     <<if $winnings == 0 >> | ||||
|         Jenny: Too bad, you did not win anything. | ||||
|     <<elseif $winnings < 0 >> | ||||
|         Jenny: Bad luck. You lost {$winnings} coins. | ||||
|         Jenny: Play again to change your fortunes. | ||||
|     <<else>> | ||||
|         Jenny: Congratulations! You won {$winnings} coins. | ||||
|         Jenny: Play again to win even more. | ||||
|     <<endif>> | ||||
|     <<updateCoins {$winnings}>> | ||||
| === | ||||
							
								
								
									
										6
									
								
								examples/assets/yarn/simple.yarn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/assets/yarn/simple.yarn
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| <<character Jenny>> | ||||
| title: hello_world | ||||
| --- | ||||
| Jenny: Hello world. My name is Jenny. | ||||
| Jenny: Thanks for using Flame! | ||||
| === | ||||
| @ -16,6 +16,7 @@ import 'package:examples/stories/bridge_libraries/flame_forge2d/joints/revolute_ | ||||
| import 'package:examples/stories/bridge_libraries/flame_forge2d/joints/rope_joint.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_forge2d/joints/weld_joint.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_isolate/isolate.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/jenny.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_lottie/lottie.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_spine/flame_spine.dart'; | ||||
| import 'package:examples/stories/camera_and_viewport/camera_and_viewport.dart'; | ||||
| @ -98,6 +99,7 @@ void runAsDashbook() { | ||||
|   // Bridge package examples | ||||
|   addForge2DStories(dashbook); | ||||
|   addFlameIsolateExample(dashbook); | ||||
|   addFlameJennyExample(dashbook); | ||||
|   addFlameLottieExample(dashbook); | ||||
|   addFlameSpineExamples(dashbook); | ||||
|  | ||||
|  | ||||
| @ -0,0 +1,8 @@ | ||||
| String baseLink(String path) { | ||||
|   const basePath = | ||||
|       'https://github.com/flame-engine/flame/blob/main/packages/flame_jenny/'; | ||||
|  | ||||
|   return '$basePath$path'; | ||||
| } | ||||
|  | ||||
| const double fontSize = 24; | ||||
| @ -0,0 +1,71 @@ | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/dialogue_button.dart'; | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:jenny/jenny.dart'; | ||||
|  | ||||
| class ButtonRow extends PositionComponent { | ||||
|   ButtonRow({required super.size}) : super(position: Vector2(0, 96)); | ||||
|  | ||||
|   void removeButtons() { | ||||
|     final buttonList = children.query<DialogueButton>(); | ||||
|     if (buttonList.isNotEmpty) { | ||||
|       for (final dialogueButton in buttonList) { | ||||
|         if (dialogueButton.parent != null) { | ||||
|           dialogueButton.removeFromParent(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void showNextButton(Function() onNextButtonPressed) { | ||||
|     removeButtons(); | ||||
|     final nextButton = DialogueButton( | ||||
|       assetPath: 'green_button_sqr.png', | ||||
|       text: 'Next', | ||||
|       position: Vector2(size.x / 2, 0), | ||||
|       onPressed: () { | ||||
|         onNextButtonPressed(); | ||||
|         removeButtons(); | ||||
|       }, | ||||
|     ); | ||||
|     add(nextButton); | ||||
|   } | ||||
|  | ||||
|   void showOptionButtons({ | ||||
|     required Function(int optionNumber) onChoice, | ||||
|     required DialogueOption option1, | ||||
|     required DialogueOption option2, | ||||
|   }) { | ||||
|     removeButtons(); | ||||
|     final optionButtons = <DialogueButton>[ | ||||
|       DialogueButton( | ||||
|         assetPath: 'green_button_sqr.png', | ||||
|         text: option1.text, | ||||
|         position: Vector2(size.x / 4, 0), | ||||
|         onPressed: () { | ||||
|           onChoice(0); | ||||
|           removeButtons(); | ||||
|         }, | ||||
|       ), | ||||
|       DialogueButton( | ||||
|         assetPath: 'red_button_sqr.png', | ||||
|         text: option2.text, | ||||
|         position: Vector2(size.x * 3 / 4, 0), | ||||
|         onPressed: () { | ||||
|           onChoice(1); | ||||
|           removeButtons(); | ||||
|         }, | ||||
|       ), | ||||
|     ]; | ||||
|     addAll(optionButtons); | ||||
|   } | ||||
|  | ||||
|   void showCloseButton(Function() onClose) { | ||||
|     final closeButton = DialogueButton( | ||||
|       assetPath: 'green_button_sqr.png', | ||||
|       text: 'Close', | ||||
|       onPressed: () => onClose(), | ||||
|       position: Vector2(size.x / 2, 0), | ||||
|     ); | ||||
|     add(closeButton); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/button_row.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/dialogue_text_box.dart'; | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:jenny/jenny.dart'; | ||||
|  | ||||
| class DialogueBoxComponent extends SpriteComponent with HasGameReference { | ||||
|   DialogueTextBox textBox = DialogueTextBox(text: ''); | ||||
|   final Vector2 spriteSize = Vector2(736, 128); | ||||
|   late final ButtonRow buttonRow = ButtonRow(size: spriteSize); | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     position = Vector2(game.size.x / 2, 96); | ||||
|     anchor = Anchor.center; | ||||
|     sprite = await Sprite.load( | ||||
|       'dialogue_box.png', | ||||
|       srcSize: spriteSize, | ||||
|     ); | ||||
|     await addAll([buttonRow, textBox]); | ||||
|     return super.onLoad(); | ||||
|   } | ||||
|  | ||||
|   void changeText(String newText, Function() goNextLine) { | ||||
|     textBox.text = newText; | ||||
|     buttonRow.showNextButton(goNextLine); | ||||
|   } | ||||
|  | ||||
|   void showOptions({ | ||||
|     required Function(int optionNumber) onChoice, | ||||
|     required DialogueOption option1, | ||||
|     required DialogueOption option2, | ||||
|   }) { | ||||
|     buttonRow.showOptionButtons( | ||||
|       onChoice: onChoice, | ||||
|       option1: option1, | ||||
|       option2: option2, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   void showCloseButton(Function() onClose) { | ||||
|     buttonRow.showCloseButton(onClose); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/commons/commons.dart'; | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flame/input.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class DialogueButton extends SpriteButtonComponent { | ||||
|   DialogueButton({ | ||||
|     required super.position, | ||||
|     required this.assetPath, | ||||
|     required this.text, | ||||
|     required super.onPressed, | ||||
|     super.anchor = Anchor.center, | ||||
|   }); | ||||
|  | ||||
|   final String text; | ||||
|   final String assetPath; | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     button = await Sprite.load(assetPath); | ||||
|     add( | ||||
|       TextComponent( | ||||
|         text: text, | ||||
|         position: Vector2(48, 16), | ||||
|         anchor: Anchor.center, | ||||
|         size: Vector2(88, 28), | ||||
|         textRenderer: TextPaint( | ||||
|           style: const TextStyle( | ||||
|             fontSize: fontSize, | ||||
|             color: Colors.white70, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,85 @@ | ||||
| import 'dart:async'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/dialogue_box.dart'; | ||||
| import 'package:flame/components.dart' hide Timer; | ||||
| import 'package:jenny/jenny.dart'; | ||||
|  | ||||
| class DialogueControllerComponent extends Component | ||||
|     with DialogueView, HasGameReference { | ||||
|   Completer<void> _forwardCompleter = Completer(); | ||||
|   Completer<int> _choiceCompleter = Completer<int>(); | ||||
|   Completer<void> _closeCompleter = Completer(); | ||||
|   late final DialogueBoxComponent _dialogueBoxComponent = | ||||
|       DialogueBoxComponent(); | ||||
|  | ||||
|   @override | ||||
|   Future<void> onNodeStart(Node node) async { | ||||
|     _closeCompleter = Completer(); | ||||
|     _addDialogueBox(); | ||||
|   } | ||||
|  | ||||
|   void _addDialogueBox() { | ||||
|     game.camera.viewport.add(_dialogueBoxComponent); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onNodeFinish(Node node) async { | ||||
|     _dialogueBoxComponent.showCloseButton(_onClose); | ||||
|     return _closeCompleter.future; | ||||
|   } | ||||
|  | ||||
|   void _onClose() { | ||||
|     if (!_closeCompleter.isCompleted) { | ||||
|       _closeCompleter.complete(); | ||||
|     } | ||||
|     final list = game.camera.viewport.children.query<DialogueBoxComponent>(); | ||||
|     if (list.isNotEmpty) { | ||||
|       game.camera.viewport.removeAll(list); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future<void> _advance() async { | ||||
|     return _forwardCompleter.future; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   FutureOr<bool> onLineStart(DialogueLine line) async { | ||||
|     _forwardCompleter = Completer(); | ||||
|     _changeTextAndShowNextButton(line); | ||||
|     await _advance(); | ||||
|     return super.onLineStart(line); | ||||
|   } | ||||
|  | ||||
|   void _changeTextAndShowNextButton(DialogueLine line) { | ||||
|     final characterName = line.character?.name ?? ''; | ||||
|     final dialogueLineText = '$characterName: ${line.text}'; | ||||
|     _dialogueBoxComponent.changeText(dialogueLineText, _goNextLine); | ||||
|   } | ||||
|  | ||||
|   void _goNextLine() { | ||||
|     if (!_forwardCompleter.isCompleted) { | ||||
|       _forwardCompleter.complete(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   FutureOr<int?> onChoiceStart(DialogueChoice choice) async { | ||||
|     _forwardCompleter = Completer(); | ||||
|     _choiceCompleter = Completer<int>(); | ||||
|     _dialogueBoxComponent.showOptions( | ||||
|       onChoice: _onChoice, | ||||
|       option1: choice.options[0], | ||||
|       option2: choice.options[1], | ||||
|     ); | ||||
|     await _advance(); | ||||
|     return _choiceCompleter.future; | ||||
|   } | ||||
|  | ||||
|   void _onChoice(int optionNumber) { | ||||
|     if (!_forwardCompleter.isCompleted) { | ||||
|       _forwardCompleter.complete(); | ||||
|     } | ||||
|     if (!_choiceCompleter.isCompleted) { | ||||
|       _choiceCompleter.complete(optionNumber); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/commons/commons.dart'; | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class DialogueTextBox extends TextBoxComponent { | ||||
|   DialogueTextBox({required super.text}) | ||||
|       : super( | ||||
|           position: Vector2(16, 16), | ||||
|           size: Vector2(704, 96), | ||||
|           textRenderer: TextPaint( | ||||
|             style: const TextStyle( | ||||
|               fontSize: fontSize, | ||||
|               color: Colors.black, | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flame/input.dart'; | ||||
| import 'package:flame/palette.dart'; | ||||
| import 'package:flame/text.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class MenuButton extends ButtonComponent { | ||||
|   MenuButton({ | ||||
|     required super.position, | ||||
|     required super.onPressed, | ||||
|     required this.text, | ||||
|   }) : super(size: Vector2(128, 42)); | ||||
|  | ||||
|   late String text; | ||||
|  | ||||
|   final Paint white = BasicPalette.white.paint(); | ||||
|   final TextPaint topTextPaint = TextPaint( | ||||
|     style: TextStyle(color: BasicPalette.black.color), | ||||
|   ); | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     button = RectangleComponent(paint: white, size: size); | ||||
|     anchor = Anchor.center; | ||||
|     add( | ||||
|       TextComponent( | ||||
|         text: text, | ||||
|         textRenderer: topTextPaint, | ||||
|         position: size / 2, | ||||
|         anchor: Anchor.center, | ||||
|         priority: 1, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										29
									
								
								examples/lib/stories/bridge_libraries/flame_jenny/jenny.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								examples/lib/stories/bridge_libraries/flame_jenny/jenny.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import 'package:dashbook/dashbook.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/commons/commons.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/jenny_advanced_example.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/jenny_simple_example.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
|  | ||||
| void addFlameJennyExample(Dashbook dashbook) { | ||||
|   dashbook.storiesOf('FlameJenny') | ||||
|     ..add( | ||||
|       'Simple Jenny example', | ||||
|       (_) => GameWidget( | ||||
|         game: JennySimpleExample(), | ||||
|       ), | ||||
|       codeLink: baseLink( | ||||
|         'bridge_libraries/flame_jenny/jenny_simple_example.dart', | ||||
|       ), | ||||
|       info: JennySimpleExample.description, | ||||
|     ) | ||||
|     ..add( | ||||
|       'Advanced Jenny example', | ||||
|       (_) => GameWidget( | ||||
|         game: JennyAdvancedExample(), | ||||
|       ), | ||||
|       codeLink: baseLink( | ||||
|         'bridge_libraries/flame_jenny/jenny_advanced_example.dart', | ||||
|       ), | ||||
|       info: JennyAdvancedExample.description, | ||||
|     ); | ||||
| } | ||||
| @ -0,0 +1,76 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/dialogue_controller_component.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/menu_button.dart'; | ||||
| import 'package:flame/components.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
| import 'package:flame/palette.dart'; | ||||
| import 'package:flame/text.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:jenny/jenny.dart'; | ||||
|  | ||||
| class JennyAdvancedExample extends FlameGame { | ||||
|   static const String description = ''' | ||||
|     This is an advanced example of how to use the Jenny Package.  | ||||
|     It includes implementing dialogue choices, setting custom variables, | ||||
|     using commands and implementing User-Defined Commands, . | ||||
|   '''; | ||||
|  | ||||
|   int coins = 0; | ||||
|  | ||||
|   final Paint white = BasicPalette.white.paint(); | ||||
|   final TextPaint mainTextPaint = TextPaint( | ||||
|     style: TextStyle(color: BasicPalette.white.color), | ||||
|   ); | ||||
|   final TextPaint buttonTextPaint = TextPaint( | ||||
|     style: TextStyle(color: BasicPalette.black.color), | ||||
|   ); | ||||
|   final startButtonSize = Vector2(128, 56); | ||||
|  | ||||
|   late final TextComponent header = TextComponent( | ||||
|     text: 'Select player name.', | ||||
|     position: Vector2(size.x / 2, 56), | ||||
|     size: startButtonSize, | ||||
|     anchor: Anchor.center, | ||||
|     textRenderer: mainTextPaint, | ||||
|   ); | ||||
|  | ||||
|   Future<void> startDialogue(String playerName) async { | ||||
|     final dialogueControllerComponent = DialogueControllerComponent(); | ||||
|     add(dialogueControllerComponent); | ||||
|  | ||||
|     final yarnProject = YarnProject(); | ||||
|  | ||||
|     yarnProject | ||||
|       ..commands.addCommand1('updateCoins', updateCoins) | ||||
|       ..variables.setVariable(r'$playerName', playerName) | ||||
|       ..parse(await rootBundle.loadString('assets/yarn/advanced.yarn')); | ||||
|     final dialogueRunner = DialogueRunner( | ||||
|       yarnProject: yarnProject, | ||||
|       dialogueViews: [dialogueControllerComponent], | ||||
|     ); | ||||
|     dialogueRunner.startDialogue('gamble'); | ||||
|   } | ||||
|  | ||||
|   void updateCoins(int amountChange) { | ||||
|     coins += amountChange; | ||||
|     header.text = 'Select player name. Current coins: $coins'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     addAll([ | ||||
|       header, | ||||
|       MenuButton( | ||||
|         position: Vector2(size.x / 4, 128), | ||||
|         onPressed: () => startDialogue('Jessie'), | ||||
|         text: 'Jessie', | ||||
|       ), | ||||
|       MenuButton( | ||||
|         position: Vector2(size.x * 3 / 4, 128), | ||||
|         onPressed: () => startDialogue('James'), | ||||
|         text: 'James', | ||||
|       ), | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,36 @@ | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/dialogue_controller_component.dart'; | ||||
| import 'package:examples/stories/bridge_libraries/flame_jenny/components/menu_button.dart'; | ||||
| import 'package:flame/game.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:jenny/jenny.dart'; | ||||
|  | ||||
| class JennySimpleExample extends FlameGame { | ||||
|   static const String description = ''' | ||||
|     This is a simple example of how to use the Jenny Package.  | ||||
|     It includes instantiating YarnProject and parsing a .yarn script. | ||||
|   '''; | ||||
|  | ||||
|   Future<void> startDialogue() async { | ||||
|     final dialogueControllerComponent = DialogueControllerComponent(); | ||||
|     add(dialogueControllerComponent); | ||||
|  | ||||
|     final yarnProject = YarnProject(); | ||||
|     yarnProject.parse(await rootBundle.loadString('assets/yarn/simple.yarn')); | ||||
|     final dialogueRunner = DialogueRunner( | ||||
|       yarnProject: yarnProject, | ||||
|       dialogueViews: [dialogueControllerComponent], | ||||
|     ); | ||||
|     dialogueRunner.startDialogue('hello_world'); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<void> onLoad() async { | ||||
|     addAll([ | ||||
|       MenuButton( | ||||
|         position: Vector2(size.x / 2, 96), | ||||
|         onPressed: startDialogue, | ||||
|         text: 'Start conversation', | ||||
|       ), | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
| @ -23,6 +23,7 @@ dependencies: | ||||
|   flutter: | ||||
|     sdk: flutter | ||||
|   google_fonts: ^4.0.4 | ||||
|   jenny: ^1.3.0 | ||||
|   meta: ^1.9.1 | ||||
|   padracing: ^1.0.0 | ||||
|   provider: ^6.0.5 | ||||
| @ -49,3 +50,4 @@ flutter: | ||||
|     - assets/tiles/ | ||||
|     - assets/audio/music/ | ||||
|     - assets/audio/sfx/ | ||||
|     - assets/yarn/ | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Lim Chee Keen
					Lim Chee Keen