Skip to content

Commit

Permalink
feat: video portraits, hidden portraits, custom size portraits (#133)
Browse files Browse the repository at this point in the history
* feat: adding video portraits support and also hidden portraits

* feat: video portraits, hidden portraits, custom size portraits
  • Loading branch information
liana-p committed Jul 21, 2023
1 parent 1186d75 commit 7e62501
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 129 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ const config = {
text: 'Changing the player character',
link: '/features/changing-player-character',
},
{
text: 'Characters and portraits',
link: '/features/characters-and-portraits',
},
{ text: 'Game Settings', link: '/features/game-settings' },
{ text: 'Gamepad support', link: '/features/gamepad' },
{
Expand Down
98 changes: 98 additions & 0 deletions docs/features/characters-and-portraits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Characters and portraits
description: This page explains how to add or edit characters in the characters.yaml file
---

# Characters and portraits

## characters.yaml

The `characters.yaml` file contains the config for all characters that can speak in the game. They should all at least have a name value, and a list of different sprite variants . The sprite is used for displaying character portraits during dialogue

When using a talk command like `talk player idle "Hello!"`, the engine will look for a character with the name `player` in the `characters.yaml` file, and display the `idle` sprite variant of that character.

::: danger
Do not delete the default game and player characters.

The `game` character is the placeholder one used for empty text commands.
The `player` character is the one used for the text when the player makes choices.

You can change which character is used as the default with the `playerCharacter` and `gameCharacter` options as seen below.
:::

## Example characters config

```yaml
---
config:
imagesPath: img/characters/
playerCharacter: player
gameCharacter: game
characters:
game:
name: ''
color: white
player:
style:
color: orange
sprites:
idle: player.webp
name: You
player2:
style:
color: green
sprites:
idle: player.webp
name: Player 2
```
## Video Characters
Characters can have a video instead of an image to display animated characters. For example:
```yaml
characters:
helper:
sprites:
idle:
image: helper_cat.webp
videoPose:
video: helper_video.mp4
width: 200
height: 355
style:
color: green
name: Helper Cat
```
Then using `talk helper videoPose "Hello!"` will play the video inside the portrait window.

## Hidden characters

If you want the portrait for a character to _not_ appear, you can set the value of a specific sprite to `"none"`:

```yaml
characters:
player:
style:
color: orange
sprites:
idle: none
name: You
```

Then using `talk player idle "Hello"` will make the player's name appear as the speaker, but no portrait window will be shown.

## Available options

The color character names appears as can be changed with the `color` value in the `style` property of the character (the value can be any valid CSS color).

A character's config can have the following values:

- `name`: The name the character will appear as
- `sprites`: A key-value object of pose names to the url of the picture to use for that pose. Poses are used with the talk command (the command `talk player idle "A sentence"` would use the character "player" with the picture for the pose named "idle")
- `style`: An object to customise how that character looks with the following options:
- color: a CSS color (ie. "red", or #FFF)
- boxCss: [CSS style object](https://www.w3schools.com/jsref/dom_obj_style.asp) for custom-styling of the box encapsulating a dialogue from that character.
- nameCss: Same as above, but the styling will apply to the name of the character specifically
- textCss: Same as above, but will apply to the text "spoken" by the character
71 changes: 5 additions & 66 deletions docs/guides/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,72 +87,11 @@ The engine follows this process for loading the config:

Additionally to `config.yaml`, there is a separate `characters.yaml` file containing the config for all characters in the game.

A character's config can have the following values:
See more info at the [characters and portraits guide](../features/characters-and-portraits.md)

- `name`: The name the character will appear as
- `sprites`: A key-value object of pose names to the url of the picture to use for that pose. Poses are used with the talk command (the command `talk player idle "A sentence"` would use the character "player" with the picture for the pose named "idle")
- `style`: An object to customise how that character looks with the following options:
- color: a CSS color (ie. "red", or #FFF)
- boxCss: [CSS style object](https://www.w3schools.com/jsref/dom_obj_style.asp) for custom-styling of the box encapsulating a dialogue from that character.
- nameCss: Same as above, but the styling will apply to the name of the character specifically
- textCss: Same as above, but will apply to the text "spoken" by the character
### Other config files

::: danger
Do not delete the default game and player characters.
There are individual config files for most narrat features which you can edit. The best way to learn about them is to look at example games and see how they are used.

The `game` character is the placeholder one used for empty text commands.
The `player` character is the one used for the text when the player makes choices.
:::

Example character config file:

```yaml
---
config:
imagesPath: './img/characters/'
characters:
game:
name: ''
color: white
player:
style:
color: orange
sprites:
idle: player.webp
name: You
cat:
sprites:
idle: cat_idle.webp
style:
color: white
name: Generic Cat
shopkeeper:
sprites:
idle: shop_cat.webp
style:
color: white
name: Shopkeeper
helper:
sprites:
idle: helper_cat.webp
style:
color: green
name: Helper Cat
music_cat:
sprites:
idle: music_cat.webp
style:
color: '#7f06e2'
boxCss:
background-color: red
textCss:
color: white
font-family: Comic Sans MS
name: Music Cat
inner:
sprites:
idle: inner_voice.webp
style:
color: red
name: Inner Voice
```
- [Example test games in the narrat repo](https://github.com/liana-p/narrat-engine/tree/main/packages/narrat/examples/games)
- [Example games in the narrat-examples repo](https://github.com/liana-p/narrat-examples)
2 changes: 2 additions & 0 deletions docs/guides/editing-game.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ The `characters.yaml` file contains the config for all characters that can speak

The color character names appears as can be changed with the `color` value in the `style` property of the character (the value can be any valid CSS color).

See more details at [characters and portraits](../features/characters-and-portraits.md)

### Other config files

There are individual config files for most narrat features which you can edit. The best way to learn about them is to look at example games and see how they are used.
Expand Down
Binary file not shown.
10 changes: 8 additions & 2 deletions packages/narrat/examples/games/default/data/characters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ characters:
name: Generic Cat
shopkeeper:
sprites:
idle: shop_cat.webp
# idle: shop_cat.webp
idle: none
style:
color: white
name: Shopkeeper
helper:
sprites:
idle: helper_cat.webp
idle:
image: helper_cat.webp
videoPose:
video: helper_video.mp4
width: 200
height: 355
style:
color: green
name: Helper Cat
Expand Down
116 changes: 72 additions & 44 deletions packages/narrat/src/components/dialog-picture.vue
Original file line number Diff line number Diff line change
@@ -1,55 +1,82 @@
<template>
<div class="dialog-picture override" :style="boxStyle">
<img :src="getAssetUrl(pictureUrl!)" class="picture override" />
<img
:src="getAssetUrl(getCharacterPicUrl(picture?.image))"
class="picture override"
v-if="picture"
/>
<video
v-if="video"
class="picture override"
:autoplay="video.autoplay === false ? false : true"
:loop="video.loop === false ? false : true"
:muted="video.muted ? true : false"
>
<source :src="getAssetUrl(getCharacterPicUrl(video.video))" />
</video>
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { getConfig, getAssetUrl, getDialogPanelWidth } from '@/config';
import { defaultConfig } from '@/config/config-output';
import { useRenderingStore } from '@/stores/rendering-store';
import { mapState } from 'pinia';
import { defineComponent } from 'vue';
export default defineComponent({
props: {
pictureUrl: String,
},
methods: {
getAssetUrl(url: string) {
return getAssetUrl(url);
},
},
computed: {
...mapState(useRenderingStore, ['layoutMode']),
boxStyle(): any {
const rendering = useRenderingStore();
const layout = getConfig().layout;
let right: any = 0;
let bottom: any = 0;
const portrait = layout.portraits;
if (this.layoutMode === 'vertical') {
const portraitMode = portrait.offset?.portrait ?? {
right: 0,
bottom: 0,
};
right = 20 + portraitMode.right;
bottom = rendering.dialogHeight + portraitMode.bottom;
} else {
const landscape = portrait.offset?.landscape ?? { right: 0, bottom: 0 };
const panelOffset =
getConfig().dialogPanel.rightOffset ??
defaultConfig.dialogPanel.rightOffset;
right = getDialogPanelWidth() - 10 + landscape.right + panelOffset;
bottom = 200 + landscape.bottom;
}
return {
right: `${right}px`,
bottom: `${bottom}px`,
width: `${layout.portraits.width}px`,
height: `${layout.portraits.height}px`,
};
},
},
import { computed } from 'vue';
import {
ImageCharacterPose,
VideoCharacterPose,
} from '@/config/characters-config';
import { getCharacterPicUrl } from '@/utils/characters';
const props = defineProps<{
picture: ImageCharacterPose | undefined;
video: VideoCharacterPose | undefined;
}>();
const video = computed(() => {
return props.video;
});
const layoutMode = computed(() => {
return useRenderingStore().layoutMode;
});
const boxStyle = computed(() => {
const rendering = useRenderingStore();
const layout = getConfig().layout;
let right: any = 0;
let bottom: any = 0;
const portrait = layout.portraits;
if (layoutMode.value === 'vertical') {
const portraitMode = portrait.offset?.portrait ?? {
right: 0,
bottom: 0,
};
right = 20 + portraitMode.right;
bottom = rendering.dialogHeight + portraitMode.bottom;
} else {
const landscape = portrait.offset?.landscape ?? { right: 0, bottom: 0 };
const panelOffset =
getConfig().dialogPanel.rightOffset ??
defaultConfig.dialogPanel.rightOffset;
right = getDialogPanelWidth() - 10 + landscape.right + panelOffset;
bottom = 200 + landscape.bottom;
}
let width = layout.portraits.width;
let height = layout.portraits.height;
if (props.video) {
width = props.video.width ?? width;
height = props.video.height ?? height;
}
if (props.picture) {
width = props.picture.width ?? width;
height = props.picture.height ?? height;
}
return {
right: `${right}px`,
bottom: `${bottom}px`,
width: `${width}px`,
height: `${height}px`,
};
});
</script>
Expand All @@ -62,6 +89,7 @@ export default defineComponent({
border-radius: 10px;
background-color: grey;
z-index: 99;
transition: all 0.5s ease-in-out;
}
.dialog-picture img {
Expand Down
Loading

0 comments on commit 7e62501

Please sign in to comment.