diff --git a/README.md b/README.md index d0b9d60..4ef9b5b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ You can have multiple entities each with their own *Talk* graph. Or you can make ## Actions and Actors Talks are made up of actions that are translated into graph nodes. -Actions can be defined either via the `TalkBuilder` of with asset files and they have this form: +Actions can be defined either via the `TalkBuilder` (where you have more control over the dialogue graph with custom components and events) or with "talk.ron" asset files. With the latter, you are building dialogue nodes by passing `Action` data: ```rust struct Action { @@ -45,11 +45,9 @@ struct Action { } ``` -You won't be writing this struct directly, but you can see that it contains several fields that define the kind of action it can be, the relevant actors, text or choices and the next action to perform (where to go in the graph after). +It contains several fields that define the kind of action it can be, the relevant actors, text or choices and the next action to perform (where to go in the graph after). -The actors are quite simple right now. It is just the name and an identifier (the slug). - -The `Actor` struct is a simple struct that contains the name of the actor and the asset to display on the screen. +The actors are quite simple right now. It is just the name and an identifier (the slug): ```rust struct Actor { @@ -63,36 +61,20 @@ struct Actor { Having a well defined *Talk* with actions and actors will result in spawning a graph where all the nodes are entities. Each action will be an entity "node", and each actor is also an entity. -All the action nodes will be connected with each other with a aery relationship (called *FollowedBy*), following the graph structure given by the actions next and id fields, and each action with actors will result in the corresponding entity being connected with the actors entity with another aery relationship (called *PerformedBy*). +All the action nodes will be connected with each other with an aery relationship (called *FollowedBy*), following the graph structure given by the actions next and id fields, and each action with actors will result in the corresponding entity being connected with the actors entities with another aery relationship (called *PerformedBy*). -Finally all the action entities in the graph will be a child of a main entity that represents the *Talk* itself, with the *Talk* component attached to it. -## The Talk Component +## The Parent Talk -This parent Talk component that "encapsulates" the graph is the main component that you will use to interact with the dialogue system. -With it you can keep track of the current node data, and use it to send events to advance the dialogue (through the related entity). +All the node entities in the graph will be a child of a main entity that represents the *Talk* itself, with the *Talk* component attached to it. -```rust -/// The Talk component. It's used to identify the parent entity of dialogue entity graphs. -#[derive(Component, Debug)] -pub struct Talk { - /// The text of the current node (if not a Talk node it's empty) - pub current_text: String, - /// The kind of the current node - pub current_kind: NodeKind, - /// The actor(s) name of the current node - pub current_actors: Vec, - /// The choices of the current node (if not a Choice node it's empty) - pub current_choices: Vec, -} -``` +You can think of this parent Talk entity as it "encapsulates" the graph and you can use it to identify a dialogue graph. You will use it to send events to advance the dialogue. ## Build Talks from talk.ron files -The plugin can parse ron files to create `TalkData` assets, which can then be used to build the graph. -The files must have the extension: `talk.ron`. +The above-mentioned ron assets files are used to create `TalkData` assets. They can be used to build dialogue graphs via bevy `Commands`. -Here's an example: +The files must have the extension: `talk.ron`. Here's an example: ```rust,ignore ( @@ -127,7 +109,6 @@ let handle: Handle = asset_server.load("simple.talk.ron"); Then you can use `Talk::builder()` to create a `TalkBuilder`, which has the `fill_with_talk_data` method. You can retrieve the `TalkData` from the assets collection `talks: Res>`. - With the builder ready, you can use the Commands extension to spawn the dialogue graph in the world: ```rust @@ -144,10 +125,8 @@ fn spawn(mut commands: Commands, talks: Res>, talk_asset: Res>) { - for talk in talks.iter() { - if text.is_changed() && talk.current_kind == NodeKind::Talk { - println!("{}", text.text()); - } +fn print_text(mut text_events: EventReader) { + for txt_ev in text_events.read() { + let mut speaker = "Narrator"; + println!("{}", txt_ev.text); } } ``` +Note that the actors connected to the node are injected in the event, so you don't need to query them. -To interact with the dialogue graphs you can send 2 different events to advance the Talk to the next node or to jump to a specific node: +### Request Events + +That's the events from a dialogue graph to you. There is also the other direction so you can send requests to the dialogue graph (to advance the dialogue). To move forward to the next action: ```rust -pub struct NextActionRequest(pub Entity); +/// Event to request the next node in a `Talk`. +/// It requires an entity with the `Talk` component you want to update. +#[derive(Event)] +pub struct NextNodeRequest { + /// The entity with the `Talk` component you want to update. + pub talk: Entity, +} ``` To jump to a specific action (used with choices): ```rust -pub struct ChooseActionRequest { - /// The entity with the [`Talk`] component you want to update. +/// An event to jump to some specific node in a graph. +/// It requires an entity with the `Talk` component you want to update. +#[derive(Event)] +pub struct ChooseNodeRequest { + /// The entity with the `Talk` component you want to update. pub talk: Entity, /// The next entity to go to. pub next: Entity, } ``` +There is also an useful event to re-send all the events associated to a node: + +```rust +/// Event to request the current node to re-send all its events. +#[derive(Event)] +pub struct RefireNodeRequest { + /// The entity with the `Talk` component you want to update. + pub talk: Entity, +} +``` + You pass the entity with the `Talk` component in these events, plus the next node entity in case of the choose event. Check out the `examples` folder to see how to use the plugin. @@ -206,29 +211,30 @@ Check out the `examples` folder to see how to use the plugin. - [choices.rs](examples/choices.rs) shows how to use the plugin to create a conversation with choices (jumps in the graph). - [full.rs](examples/full.rs) shows a Talk where all the action kinds are used. - [ingame.rs](examples/ingame.rs) shows how to use the plugin with more than one talk you can interact with. +- [custom_node_event.rs](examples/custom_node_event.rs) shows how to add your own event emitting component to create a custom node. ### Roadmap Some nice-to-haves from the top of my head: -- [ ] Use the built-in bevy_ecs relations (when one day we will have them) +- [x] More node kinds (got rid of node kinds, now nodes are entities with components) +- [x] Extensive documentation/manual wiki (added an mdbook, but always in progress...) +- [x] Extensible Interaction/Trigger system (I mean I'm using events, more decoupled than this is impossible) +- [ ] Use the built-in bevy_ecs relations (when one day when we will have them) - [ ] Dialogue UIs -- [ ] Extensible Interaction/Trigger system (to activate/advance dialogues) - [ ] Graphical editor to create the asset files - [ ] Voice lines/sound support -- [ ] More node kinds (and a custom node kind system) -- [ ] Support other asset formats (?) +- [ ] Support other asset formats (yarn?) - [ ] More examples -- [ ] Extensive documentation/manual wiki (always in progress...) - [ ] Localization with [Fluent](https://projectfluent.org/) ### Bevy Version Support - Compatibility of `bevy_talks` versions: | `bevy_talks` | `bevy` | | :-- | :-- | | `main` | `0.12` | +| `0.5.0` | `0.12` | | `0.4.0` | `0.12` | | `0.3.1` | `0.12` | | `0.3.0` | `0.11` | diff --git a/docs/src/index.md b/docs/src/index.md index bd6c210..09e231e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,6 +1,6 @@ # Introduction to Bevy Talks -> [ⓘ] +> [ⓘ] > Be aware that `bevy_talks`'s API is still undergoing revisions (with possibly big architectural changes). Feedback on its ergonomics and developer experience (DX) is highly appreciated. This [Bevy][bevy] plugin provides a way to create dialogues and conversations in your game as graphs. @@ -13,10 +13,13 @@ just a sequence of texts forming a conversation between actors. You can have multiple entities each with their own *Talk* graph. Or you can make a VN-like game with one single big dialogue graph in the game. +> [!NOTE] +> A more in-depth documentation is being slowly written as an [mdbook here!](https://giusdp.github.io/bevy_talks/) Help is appreciated :) + ## Actions and Actors Talks are made up of actions that are translated into graph nodes. -Actions can be defined either via the `TalkBuilder` of with asset files and they have this form: +Actions can be defined either via the `TalkBuilder` (where you have more control over the dialogue graph with custom components and events) or with "talk.ron" asset files. With the latter, you are building dialogue nodes by passing `Action` data: ```rust struct Action { @@ -35,11 +38,9 @@ struct Action { } ``` -You won't be writing this struct directly, but you can see that it contains several fields that define the kind of action it can be, the relevant actors, text or choices and the next action to perform (where to go in the graph after). - -The actors are quite simple right now. It is just the name and an identifier (the slug). +It contains several fields that define the kind of action it can be, the relevant actors, text or choices and the next action to perform (where to go in the graph after). -The `Actor` struct is a simple struct that contains the name of the actor and the asset to display on the screen. +The actors are quite simple right now. It is just the name and an identifier (the slug): ```rust struct Actor { @@ -53,36 +54,20 @@ struct Actor { Having a well defined *Talk* with actions and actors will result in spawning a graph where all the nodes are entities. Each action will be an entity "node", and each actor is also an entity. -All the action nodes will be connected with each other with a aery relationship (called *FollowedBy*), following the graph structure given by the actions next and id fields, and each action with actors will result in the corresponding entity being connected with the actors entity with another aery relationship (called *PerformedBy*). +All the action nodes will be connected with each other with an aery relationship (called *FollowedBy*), following the graph structure given by the actions next and id fields, and each action with actors will result in the corresponding entity being connected with the actors entities with another aery relationship (called *PerformedBy*). -Finally all the action entities in the graph will be a child of a main entity that represents the *Talk* itself, with the *Talk* component attached to it. -## The Talk Component +## The Parent Talk -This parent Talk component that "encapsulates" the graph is the main component that you will use to interact with the dialogue system. -With it you can keep track of the current node data, and use it to send events to advance the dialogue (through the related entity). +All the node entities in the graph will be a child of a main entity that represents the *Talk* itself, with the *Talk* component attached to it. -```rust -/// The Talk component. It's used to identify the parent entity of dialogue entity graphs. -#[derive(Component, Debug)] -pub struct Talk { - /// The text of the current node (if not a Talk node it's empty) - pub current_text: String, - /// The kind of the current node - pub current_kind: NodeKind, - /// The actor(s) name of the current node - pub current_actors: Vec, - /// The choices of the current node (if not a Choice node it's empty) - pub current_choices: Vec, -} -``` +You can think of this parent Talk entity as it "encapsulates" the graph and you can use it to identify a dialogue graph. You will use it to send events to advance the dialogue. ## Build Talks from talk.ron files -The plugin can parse ron files to create `TalkData` assets, which can then be used to build the graph. -The files must have the extension: `talk.ron`. +The above-mentioned ron assets files are used to create `TalkData` assets. They can be used to build dialogue graphs via bevy `Commands`. -Here's an example: +The files must have the extension: `talk.ron`. Here's an example: ```rust,ignore ( @@ -117,7 +102,6 @@ let handle: Handle = asset_server.load("simple.talk.ron"); Then you can use `Talk::builder()` to create a `TalkBuilder`, which has the `fill_with_talk_data` method. You can retrieve the `TalkData` from the assets collection `talks: Res>`. - With the builder ready, you can use the Commands extension to spawn the dialogue graph in the world: ```rust @@ -134,16 +118,13 @@ fn spawn(mut commands: Commands, talks: Res>, talk_asset: Res B[Alice,Bob Join]; @@ -157,38 +138,64 @@ graph LR; ## Usage -With the `Talk` component you can get the current text/actor/choices for the current action in a talk. -Together with the Change Detection System, you can react to changes in the `Talk` component to update your UI. +Besides building dialogue graphs, at some point you have to interact with them. +After all the nodes are entities with components, so you could just do queries using the special `CurrentNode` component that keeps track of the current node. Then each node could have a `TextNode`, `JoinNode`, `LeaveNode`, `ChoiceNode` or your own custom components (added via the builder). + +Another way is to use a dialogue graph in an event-driven way. The plugin sends events every time you move to a new node based on the components it has. A node with a `TextNode` will send a `TextNodeEvent` event, a node with a `ChoiceNode` will send a `ChoiceEvent` event, and so on. You can also add your own node emitting components to customize the behaviour. + +For example, to display the text of a `TextNode` you can simply listen to the `TextNodeEvent` event: ```rust -fn print_text(talks: Query>) { - for talk in talks.iter() { - if text.is_changed() && talk.current_kind == NodeKind::Talk { - println!("{}", text.text()); - } +fn print_text(mut text_events: EventReader) { + for txt_ev in text_events.read() { + let mut speaker = "Narrator"; + println!("{}", txt_ev.text); } } ``` +Note that the actors connected to the node are injected in the event, so you don't need to query them. + +### Request Events -To interact with the dialogue graphs you can send 2 different events to advance the Talk to the next node or to jump to a specific node: +That's the events from a dialogue graph to you. There is also the other direction so you can send requests to the dialogue graph (to advance the dialogue). To move forward to the next action: ```rust -pub struct NextActionRequest(pub Entity); +/// Event to request the next node in a `Talk`. +/// It requires an entity with the `Talk` component you want to update. +#[derive(Event)] +pub struct NextNodeRequest { + /// The entity with the `Talk` component you want to update. + pub talk: Entity, +} ``` To jump to a specific action (used with choices): ```rust -pub struct ChooseActionRequest { - /// The entity with the [`Talk`] component you want to update. +/// An event to jump to some specific node in a graph. +/// It requires an entity with the `Talk` component you want to update. +#[derive(Event)] +pub struct ChooseNodeRequest { + /// The entity with the `Talk` component you want to update. pub talk: Entity, /// The next entity to go to. pub next: Entity, } ``` +There is also an useful event to re-send all the events associated to a node: + +```rust +/// Event to request the current node to re-send all its events. +#[derive(Event)] +pub struct RefireNodeRequest { + /// The entity with the `Talk` component you want to update. + pub talk: Entity, +} +``` + You pass the entity with the `Talk` component in these events, plus the next node entity in case of the choose event. Check out the `examples` folder to see how to use the plugin. @@ -197,18 +204,48 @@ Check out the `examples` folder to see how to use the plugin. - [choices.rs](examples/choices.rs) shows how to use the plugin to create a conversation with choices (jumps in the graph). - [full.rs](examples/full.rs) shows a Talk where all the action kinds are used. - [ingame.rs](examples/ingame.rs) shows how to use the plugin with more than one talk you can interact with. +- [custom_node_event.rs](examples/custom_node_event.rs) shows how to add your own event emitting component to create a custom node. ### Roadmap Some nice-to-haves from the top of my head: -- [ ] Use the built-in bevy_ecs relations (when one day we will have them) +- [x] More node kinds (got rid of node kinds, now nodes are entities with components) +- [x] Extensive documentation/manual wiki (added an mdbook, but always in progress...) +- [x] Extensible Interaction/Trigger system (I mean I'm using events, more decoupled than this is impossible) +- [ ] Use the built-in bevy_ecs relations (when one day when we will have them) - [ ] Dialogue UIs -- [ ] Extensible Interaction/Trigger system (to activate/advance dialogues) - [ ] Graphical editor to create the asset files - [ ] Voice lines/sound support -- [ ] More node kinds (and a custom node kind system) -- [ ] Support other asset formats (?) +- [ ] Support other asset formats (yarn?) - [ ] More examples -- [ ] Extensive documentation/manual wiki (always in progress...) - [ ] Localization with [Fluent](https://projectfluent.org/) + +### Bevy Version Support + +Compatibility of `bevy_talks` versions: +| `bevy_talks` | `bevy` | +| :-- | :-- | +| `main` | `0.12` | +| `0.5.0` | `0.12` | +| `0.4.0` | `0.12` | +| `0.3.1` | `0.12` | +| `0.3.0` | `0.11` | +| `0.2.0` | `0.11` | +| `0.1.1` | `0.11` | +| `bevy_main` | `main` | + +## License + +Dual-licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](/LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](/LICENSE-MIT) or https://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions.