Skip to content

Commit

Permalink
feat: achievements and global save (#47)
Browse files Browse the repository at this point in the history
* feat: global save data system

* achievements working

* changelog, docs and package version
  • Loading branch information
liana-p committed Apr 10, 2023
1 parent cef092e commit 669c120
Show file tree
Hide file tree
Showing 59 changed files with 887 additions and 131 deletions.
35 changes: 32 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,42 @@
# Narrat changelog

## [2.12.0] Achievements and global save data

## Achievements

See [achievements docs](https://docs.get-narrat.com/features/achievements.html)

## Global Save Data

The engine now supports global save data. Global save data isn't associated with any save slot and is instead global for the entire game. This allows tracking meta data across multiple playthrough, or enabling features like achievements across multiple saves.

To use, set values in the `global` object instead of `data`. For example:

```narrat
main:
talk player idle "hello world"
add global.counter 1
talk player idle "Global counter is %{$global.counter}"
```

Every time a new game is started, this script will increase the global counter despite it being a new save.

To reset global save data, use the `reset_global_save` command.

### Notable Change

There is a change to the name of the save file. It has been renamed because using a static name can make multiple games have their save files clash if hosted on the same website (for example games hosted on [itch.io](https://itch.io)).

The name of the save file is now generated based on the new `saveFileName` option configured in `config.yaml`.

## [2.11.1] Small bugfixes

* fix: there was an issue where it was possible to select a "hidden" choice (choice that didn't pass a condition) by pressing the corresponding keyboard number
- fix: there was an issue where it was possible to select a "hidden" choice (choice that didn't pass a condition) by pressing the corresponding keyboard number

## [2.11.0] Audio volume improvement and saves fix

* Audio volume in an individual audio file's config wasn't used properly and is now correctly mixed with the relevant volume for channels (by [@jornvandebeek](https://github.com/jornvandebeek))
* There was a problem where some game data didn't reset properly if a player continued playing after a save was made, went back to the main menu and reloaded the saved without reloading the browser
- Audio volume in an individual audio file's config wasn't used properly and is now correctly mixed with the relevant volume for channels (by [@jornvandebeek](https://github.com/jornvandebeek))
- There was a problem where some game data didn't reset properly if a player continued playing after a save was made, went back to the main menu and reloaded the saved without reloading the browser

(technical note: pinia stores weren't all reset properly. If there are more bugs of this kind please signal them)

Expand Down
1 change: 1 addition & 0 deletions demo-template/public/data/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
gameTitle: Narrat Demo
saveFileName: Narrat Demo
images:
narrat: img/backgrounds/narrat.webp
map: img/backgrounds/map.webp
Expand Down
5 changes: 3 additions & 2 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,16 @@ const config = {
text: 'Feature Guides',
collapsible: true,
items: [
{ text: 'Achievements', link: '/features/achievements' },
{ text: 'Audio', link: '/features/audio' },
{ text: 'HUD Stats', link: '/features/hud-stats' },
{ text: 'Inventory', link: '/features/inventory' },
{ text: 'Items', link: '/features/items' },
{ text: 'Quests', link: '/features/quests' },
{ text: 'Save and Load', link: '/features/save-and-load' },
{ text: 'Viewport', link: '/features/viewport' },
{ text: 'Skills', link: '/features/skills' },
{ text: 'Transitions', link: '/features/transitions' },
{ text: 'Items', link: '/features/items' },
{ text: 'Viewport', link: '/features/viewport' },
],
},
{
Expand Down
30 changes: 19 additions & 11 deletions docs/commands/all-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ description: This page contains a list of all the existing narrat commands
| Command | Example | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [choice](choice-function.md) | <p><br><code>choice:</code><br><code>"Would you like tea?"</code><br><code>"Yes":</code><br><code>"Your friend serves you a cup of tea"</code><br><code>"No":</code><br><code>"Your friend makes tea for themselves"</code><br></p> | Lets the player choose between two or more options. See the linked documentation for more info. |
| [text_field](text-field.md) | `text_field "A prompt text"` | Creates a text field for the player to enter text with a prompt. Returns the text entered |
| [text_field](text-field.md) | `text_field "A prompt text"` | Creates a text field for the player to enter text with a prompt. Returns the text entered |

#### Logic operators and conditions

Expand Down Expand Up @@ -76,6 +76,13 @@ description: This page contains a list of all the existing narrat commands
| [enable_interaction](../guides/using-items.md#interaction-tags) | `enable_interaction mytag` | Enables an interaction tag (see docs) |
| [`disable_interaction`](../guides/using-items.md#interaction-tags) | `disable_interaction mytag` | Disables an interaction tag (see docs) |

#### Achievements

| Command | Example | Description |
| ------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------- |
| [unlock_achievement](../features/achievements.md) | `unlock_achievement win_game` | Unlocks an achievement |
| [has_achievement?](../features/achievements.md) | `set data.hasWon (has_achievement? win_game)` | Returns true if the player has the passed achievement, otherwise false |

#### Notifications

| Command | Example | Description |
Expand All @@ -86,16 +93,16 @@ description: This page contains a list of all the existing narrat commands

#### Quests

| Command | Example | Description |
| ------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------- |
| [start_quest](../features/quests.md) | `start_quest myQuest` | Starts a quest |
| [complete_quest](../features/quests.md) | `complete_quest myQuest` | Completes a quest |
| `` [`start_objective`](../features/quests.md) `` | `start_objective myQuest myObjective` | Starts an objective in a quest (useful for quests with hidden objectives) |
| [complete_objective](../features/quests.md) | `complete_objective myQuest myObjective` | Completes an objective |
| [quest_completed?](../features/quests.md) | `quest_completed? myQuest // returns true or false` | Check if a quest is completed |
| [objective_completed?](../features/quests.md) | `objective_completed? myQuest myObjective //returns true or false` | Check if a quest objective is completed |
| [quest_started?](../features/quests.md) | `quest_started? myQuest // Returns true or false` | Check if a quest is started |
| [objective_started?](../features/quests.md) | `objective_started? myQuest myObjective // Returns true or false` | Check if a quest objective is started |
| Command | Example | Description |
| ------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------- |
| [start_quest](../features/quests.md) | `start_quest myQuest` | Starts a quest |
| [complete_quest](../features/quests.md) | `complete_quest myQuest` | Completes a quest |
| `` [`start_objective`](../features/quests.md) `` | `start_objective myQuest myObjective` | Starts an objective in a quest (useful for quests with hidden objectives) |
| [complete_objective](../features/quests.md) | `complete_objective myQuest myObjective` | Completes an objective |
| [quest_completed?](../features/quests.md) | `quest_completed? myQuest // returns true or false` | Check if a quest is completed |
| [objective_completed?](../features/quests.md) | `objective_completed? myQuest myObjective //returns true or false` | Check if a quest objective is completed |
| [quest_started?](../features/quests.md) | `quest_started? myQuest // Returns true or false` | Check if a quest is started |
| [objective_started?](../features/quests.md) | `objective_started? myQuest myObjective // Returns true or false` | Check if a quest objective is started |

#### Random

Expand Down Expand Up @@ -208,6 +215,7 @@ delete_sprite $data.playerSprite
| log | `log "what's the value of test? %{test}" // Will print this log in the console` | Prints a log in the browser developer tools. Useful for debugging or checking variable values |
| menu_return | `menu_return` | Exits the game and returns to the main menu |
| [save](save-commands.md#save) | `save [save file name]` | Opens the manual save screen for the player to save the game (optional parameter for the name of the save file, useful to pass the name of the level/chapter for example) |
| `reset_global_save` | `reset_global_save` | Resets the global part of the save |
| [save_prompt](save-commands.md#save_prompt) | `save_prompt [save file name]` | Same as save, but asks the user if they want to save first |
| [wait](wait.md) | `wait 500` | Makes the script pause for x milliseconds |
| load_data | `set data.myData (load_data data/myDataFile.yaml)` | Loads data from the data file path passed and returns it |
2 changes: 1 addition & 1 deletion docs/commands/save-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ Same as save, but will ask the player if they want to save first, and cancel if

### More info about saving

[saving-and-reloading.md](../features/saving-and-reloading.md)
[saving-and-reloading.md](../features/save-and-load.md)
62 changes: 62 additions & 0 deletions docs/features/achievements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
description: Narrat games can have players unlock achievements
---

# Achievements

The achievements system allows players to unlock achievements, which appear in their own UI.

Possible achievements can be defined in a `achievements.yaml` file:

```yaml
categories:
- id: default
title: Achievements
- id: secret
title: Secret Achievements

defaultAchievementIcon: img/achievements/trophy.png

achievements:
win_game:
name: Won the game
description: You got this achievement by being a true ultimate pro gamer
icon: img/items/bread.webp
secret_achievement:
name: Secret boss found
description: You beat the secret boss
icon: img/items/book.webp
secret: true
category: secret

notifyNewAchievements: true

secretAchievements:
censorDescription: true
censorName: false
hideUntilObtained: false
```
The location of `achievements.yaml` should be set in `config.yaml`:

```yaml
achievements: data/achievements.yaml
```

Then achievements can be unlocked in game scripts:

```narrat
main:
"There is a boss"
choice:
"Kill the boss?"
"Yes":
"You kill the boss"
unlock_achievement win_game
"No, run away":
"You run away"
```

## Achievements UI

The achievements UI will automatically appear as a tab in the game menu if the game has any achievements defined.
19 changes: 19 additions & 0 deletions docs/features/save-and-load.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Saving and Reloading

**Important:** The `saveFileName` key in the `config.yaml` file is the name of the save file, and if this value is changed old saves will stop working. Once you have chosen a save file name for a game, do not change it in the future. The name you use should contain the name of your game to avoid clashes with other games

### How saving works

Narrat supports automatic saving and reloading, but there are some important details worth knowing about.
Expand Down Expand Up @@ -45,6 +47,23 @@ To let the player save manually, there are two commands:
Because save data is only generated when jumping to a new label, save prompts should ideally be at the start of a label. Otherwise, the data saved will be outdated.
:::

## Global Save Data

The engine now supports global save data. Global save data isn't associated with any save slot and is instead global for the entire game. This allows tracking meta data across multiple playthrough, or enabling features like achievements across multiple saves.

To use, set values in the `global` object instead of `data`. For example:

```narrat
main:
talk player idle "hello world"
add global.counter 1
talk player idle "Global counter is %{$global.counter}"
```

Every time a new game is started, this script will increase the global counter despite it being a new save.

To reset global save data, use the `reset_global_save` command.

### The problem with saving a specific line

::: details Why we can only save on label change
Expand Down
3 changes: 2 additions & 1 deletion packages/narrat-2d/public/data/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
gameTitle: Narrat Game Example
gameTitle: Narrat 2D
saveFileName: narrat_2D
images: {}
dialogPanel:
textSpeed: 30
Expand Down
15 changes: 6 additions & 9 deletions packages/narrat-2d/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import {
CommandPlugin,
CustomMenuTab,
CustomStores,
NarratPlugin,
error,
useInputs,
warning,
gameloop,
Action,
Vec2,
useMain,
} from 'narrat';
import * as PIXI from 'pixi.js';
import { GameObject } from './scene/GameObject';
Expand Down Expand Up @@ -207,7 +204,7 @@ export class PixiPlugin extends NarratPlugin {
node: createContainerNode(),
});

const camera = createComponent<CameraComponent, CameraComponentOptions>(
createComponent<CameraComponent, CameraComponentOptions>(
CameraComponent,
this.scene.root,
{
Expand All @@ -225,7 +222,7 @@ export class PixiPlugin extends NarratPlugin {
agumonSprite.node.scale.set(3);
agumonSprite.node.anchor.set(0.5, 1);
agumon.setPosition(Vec2.create(1100, 1200));
const agumonCollider = createComponent<
createComponent<
ColliderComponent,
ColliderComponentOptions
>(ColliderComponent, agumon, {
Expand All @@ -236,7 +233,7 @@ export class PixiPlugin extends NarratPlugin {
y: -50,
},
});
const playerComponent = createComponent<PlayerComponent>(
createComponent<PlayerComponent>(
PlayerComponent,
agumon,
);
Expand All @@ -248,7 +245,7 @@ export class PixiPlugin extends NarratPlugin {
npc.layer = 2;
npc.node.anchor.set(0.5, 1);
npc.setPosition(Vec2.create(1300, 1400));
const npcCollider = createComponent<
createComponent<
ColliderComponent,
ColliderComponentOptions
>(ColliderComponent, npc, {
Expand All @@ -267,7 +264,7 @@ export class PixiPlugin extends NarratPlugin {
});
npcTalkZone.layer = 2;
npcTalkZone.setPosition(Vec2.create(0, -50));
const npcComp = createComponent<NpcComponent, NpcComponentOptions>(
createComponent<NpcComponent, NpcComponentOptions>(
NpcComponent,
npcTalkZone,
{
Expand All @@ -286,7 +283,7 @@ export class PixiPlugin extends NarratPlugin {
},
});
npcTalkCollider.isTrigger = true;
const player = createComponent<
createComponent<
CharacterComponent,
CharacterComponentOptions
>(CharacterComponent, agumon, {
Expand Down
5 changes: 3 additions & 2 deletions packages/narrat-2d/tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
},
"include": ["vite.config.ts"]
"include": ["vite.config.ts", "package.json"]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions packages/narrat/examples/games/default/data/achievements.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
categories:
- id: default
title: Achievements
- id: secret
title: Secret Achievements

defaultAchievementIcon: img/achievements/trophy.png

achievements:
win_game:
name: Won the game
description: Get this achievement by being a true ultimate pro gamer
icon: img/items/bread.webp
find_book:
name: Bookworm
description: Find the book in the hidden room
icon: img/items/bread.webp
chapter_1:
name: Chapter 1
description: Finish chapter 1
icon: img/items/book.webp
chapter_2:
name: Chapter 2
description: Finish chapter 2
icon: img/items/book.webp
secret_achievement:
name: Secret boss found
description: Beat the secret boss
icon: img/items/book.webp
secret: true
category: secret

notifyNewAchievements: true

secretAchievements:
censorDescription: true
censorName: true
hideUntilObtained: false
4 changes: 3 additions & 1 deletion packages/narrat/examples/games/default/data/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
gameTitle: Narrat Game Example
gameTitle: Narrat Default
saveFileName: Narrat Default
images:
narrat: img/backgrounds/narrat.webp
map: img/backgrounds/map.webp
Expand Down Expand Up @@ -57,6 +58,7 @@ hudStats:
minValue: 0
maxValue: 10
items: data/items.yaml
achievements: data/achievements.yaml
interactionTags:
default:
onlyInteractOutsideOfScripts: true
Expand Down
Loading

0 comments on commit 669c120

Please sign in to comment.