Skip to content

Commit

Permalink
fix: Change DialogueView to a mixin class (#2652)
Browse files Browse the repository at this point in the history
The DialogueView is responsible for handling and (almost always) displaying the dialog to the user. A very common use case requires the DialogueView to be a PositionComponent or other visual component in the game. This would require extending from both PositionComponent and DialogueView.

Problem: The current DialogueView implementation is an abstract class. Dart does not support multiple inheritance. This means that the case above is difficult to implement.

Solution: Changing the DialogueView from an abstract class to a mixin class allows the developer to extend PositionComponent and mix in DialogueView. The class can now be used as a base class OR as a mixin. All existing tests still pass. An additional test has been added that validates the use as a mixin (extending one class and mixing in DialogueView).
  • Loading branch information
projectitis authored Aug 20, 2023
1 parent 94963c3 commit f3d4158
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 5 deletions.
15 changes: 10 additions & 5 deletions packages/flame_jenny/jenny/lib/src/dialogue_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import 'package:meta/meta.dart';
/// a game engine. This class describes how {ref}`line <DialogueLine>`s and
/// {ref}`option <DialogueOption>`s are presented to the user.
///
/// The class is abstract, which means you must create a concrete
/// implementation in order to use Jenny's dialogue system. The concrete
/// `DialogueView` objects will then be passed to a [DialogueRunner], which
/// will orchestrate the dialogue's progression.
/// There are two ways to use this class:
///
/// - Extending DialogueView
/// - Adding DialogueView as a mixin
///
/// In both cases you will need to create concrete implementations of the
/// abstract event handler methods in order to use Jenny's dialogue system.
/// The concrete `DialogueView` objects will then be passed to a
/// [DialogueRunner], which will orchestrate the dialogue's progression.
///
/// The class defines a number of "event handler" methods, which can be
/// overridden in subclasses in order to respond to the corresponding event.
Expand All @@ -27,7 +32,7 @@ import 'package:meta/meta.dart';
/// be implemented either synchronously or asynchronously. In the latter case
/// the dialogue runner will wait for the future to resolve before proceeding
/// (futures from several dialogue views will be awaited simultaneously).
abstract class DialogueView {
abstract mixin class DialogueView {
DialogueRunner? _dialogueRunner;

/// The owner of this `DialogueView`. This property will be `null` when the
Expand Down
117 changes: 117 additions & 0 deletions packages/flame_jenny/jenny/test/dialogue_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,50 @@ void main() {
);
});

test('run a sample dialogue using mixin', () async {
final yarn = YarnProject()
..commands.addOrphanedCommand('myCommand')
..parse(
dedent('''
title: Start
---
First line
-> Option 1
Continuation line 1
-> Option 2
Continuation line 2
Last line
<<myCommand 123 boo>>
===
'''),
);
final view1 = _DefaultDialogueView();
final view2 = _RecordingDialogueViewAsMixin();
final dialogueRunner = DialogueRunner(
yarnProject: yarn,
dialogueViews: [view1, view2],
);
await dialogueRunner.startDialogue('Start');
expect(
view2.events,
const [
'onDialogueStart',
'onNodeStart(Start)',
'onLineStart(First line)',
'onLineFinish(First line)',
'onChoiceStart([-> Option 1][-> Option 2])',
'onChoiceFinish(-> Option 2)',
'onLineStart(Continuation line 2)',
'onLineFinish(Continuation line 2)',
'onLineStart(Last line)',
'onLineFinish(Last line)',
'onCommand(<<Command(myCommand)>>)',
'onNodeFinish(Start)',
'onDialogueFinish()',
],
);
});

test('jumps and visits', () async {
final yarn = YarnProject()
..parse(
Expand Down Expand Up @@ -233,6 +277,79 @@ class _RecordingDialogueView extends DialogueView {
}
}

class _SomeOtherBaseClass {}

class _RecordingDialogueViewAsMixin extends _SomeOtherBaseClass
with DialogueView {
_RecordingDialogueViewAsMixin([this.waitDuration = Duration.zero]);

Check notice on line 284 in packages/flame_jenny/jenny/test/dialogue_view_test.dart

View workflow job for this annotation

GitHub Actions / analyze

A value for optional parameter 'waitDuration' isn't ever given.

Try removing the unused parameter. See https://dart.dev/diagnostics/unused_element to learn more about this problem.
final List<String> events = [];
final Duration waitDuration;

@override
FutureOr<void> onDialogueStart() {
events.add('onDialogueStart');
}

@override
FutureOr<void> onNodeStart(Node node) {
events.add('onNodeStart(${node.title})');
}

@override
FutureOr<void> onNodeFinish(Node node) {
events.add('onNodeFinish(${node.title})');
}

@override
FutureOr<bool> onLineStart(DialogueLine line) async {
events.add('onLineStart(${line.text})');
if (waitDuration != Duration.zero) {
await Future.delayed(waitDuration, () {});
}
return true;
}

@override
void onLineSignal(DialogueLine line, dynamic signal) {
super.onLineSignal(line, signal);
events.add('onLineSignal(line="${line.text}", signal=<$signal>)');
}

@override
FutureOr<void> onLineStop(DialogueLine line) {
super.onLineStop(line);
events.add('onLineStop(${line.text})');
}

@override
FutureOr<void> onLineFinish(DialogueLine line) {
events.add('onLineFinish(${line.text})');
}

@override
Future<int> onChoiceStart(DialogueChoice choice) async {
final options =
[for (final option in choice.options) '[-> ${option.text}]'].join();
events.add('onChoiceStart($options)');
return 1;
}

@override
FutureOr<void> onChoiceFinish(DialogueOption option) {
events.add('onChoiceFinish(-> ${option.text})');
}

@override
FutureOr<void> onCommand(UserDefinedCommand command) {
events.add('onCommand(<<$command>>)');
}

@override
FutureOr<void> onDialogueFinish() {
events.add('onDialogueFinish()');
}
}

class _InterruptingCow extends DialogueView {
@override
FutureOr<bool> onLineStart(DialogueLine line) async {
Expand Down

0 comments on commit f3d4158

Please sign in to comment.