A walk through the forest
(Or whatever this game will end up being called)
I’ve started a Narrat Jam game. It’s an interactive forest walk with branching and pictures to click on.
I’m thinking of having pictures of plants and info about them. maybe a fun mode where there’s also a test where you have to identify plants I don’t know.
Video of the basic system working (I can’t upload video here so here’s a link to a mastodon post):
How it works
I’ve made a system to read a yaml data file and use that as the base to dynamically do everything. So in this case, the file defining all the paths etc looks like this:
Source code
I will probably add a basic setup for making this type of point and click game to the narrat examples repo at some point, but for now here’s the important source code (as of today) if anyone wants to use it as a base for something:
Code Details
Utilities
utilities.narrat
which contains general useful functions:
add_paths content:
if $content.paths:
log "adding paths"
for_of $content.paths add_path
elseif (< $current.index (- $current.path.contents.length 1)):
log "Adding default forward path"
var path (new Object)
set path.id "next"
set path.x 960
set path.y 540
set path.arrow "forward"
run add_path $path
if (!= $content.backAllowed false):
var backPath (new Object)
set backPath.direction "back"
set backPath.id "previous"
if $content.back:
set backPath.id $content.back
set backPath.x 960
set backPath.y 900
set backPath.arrow "back"
run add_path $backPath
add_path path:
var arrowType $path.arrow
log "adding path " $path.id " with arrow " $arrowType
var arrowConfig $data.SPRITES[$arrowType]
var spriteData (new Object)
set spriteData.background $arrowConfig.background
set spriteData.x $path.x
set spriteData.y $path.y
set spriteData.width $arrowConfig.width
set spriteData.height $arrowConfig.height
set spriteData.anchor.x $arrowConfig.anchor.x
set spriteData.anchor.y $arrowConfig.anchor.y
var pathCallback (concat "load_path " $path.id )
if (== $path.direction "back"):
set pathCallback (concat "go_back " $path.id)
set spriteData.onClick $pathCallback
run load_sprite $spriteData
load_sprite spriteData:
var sprite (create_sprite $spriteData.background $spriteData.x $spriteData.y)
run setup_sprite $spriteData $sprite
return $sprite
load_object spriteData:
var object (create_object $spriteData.x $spriteData.y)
run setup_sprite $spriteData $object
return $object
setup_sprite spriteData sprite:
if $spriteData.anchor:
set sprite.anchor.x $spriteData.anchor.x
set sprite.anchor.y $spriteData.anchor.y
if $spriteData.width:
set sprite.width $spriteData.width
if $spriteData.height:
set sprite.height $spriteData.height
if $spriteData.onClick:
set sprite.onClick $spriteData.onClick
if $spriteData.layer:
set sprite.layer $spriteData.layer
if $spriteData.cssClass:
set sprite.cssClass $spriteData.cssClass
return $sprite
load_path pathId:
log "Moving to path %{$pathId}"
var previousId $previousPathId
var previous $PATHS[$previousId]
set current.lastIndex $current.index
set previousPathId $current.id
if (== $pathId "next"):
set current.index (+ $current.index 1)
else:
set current.id $pathId
set current.path $PATHS[$pathId]
set current.index 0
run process_current_path
go_back backId:
if (!= $backId "previous"):
set current.id $backId
set current.path $PATHS[$backId]
set current.index (- $current.path.contents.length 1)
else:
set current.index (- $current.index 1)
if (>= $current.index 0):
run process_current_path
process_current_path:
empty_sprites
set current.content $current.path.contents[$current.index]
if $current.content.bg:
log "Setting background to %{$current.content.bg}"
set_screen $current.content.bg 0 fade 1000 0
run add_paths $current.content
run add_pics $current.content
Pics display
pics.narrat
which takes care of reading the info about pics and adding them to the screens so they appear and are clickable:
add_pics content:
if $content.pics:
log "adding pics"
for_of $content.pics add_pic
add_pic pic:
var imagePath (concat "img/pics/" $pic.img)
var width 160
var height 90
var spriteData (new Object)
set spriteData.background $imagePath
set spriteData.cssClass pic-preview
set spriteData.x $pic.x
set spriteData.y $pic.y
set spriteData.width $width
set spriteData.height $height
set spriteData.onClick (concat "show_pic " $pic.label " " $imagePath)
run load_sprite $spriteData
show_pic label img:
log "Show pic %{$label} %{$img}"
set data.overlay (run load_object $data.SPRITES.big_overlay)
var width 1760
var height 990
var spriteData (new Object)
set spriteData.background $img
set spriteData.x 20
set spriteData.y 20
set spriteData.anchor.x 0
set spriteData.anchor.y 0
set spriteData.width $width
set spriteData.height $height
set spriteData.cssClass "full-pic"
set spriteData.onClick "close_pic"
set spriteData.layer 2
set data.currentPic (run load_sprite $spriteData)
if $label:
run $label
close_pic:
log "Closing pic"
delete_sprite $data.currentPic
delete_sprite $data.overlay
Main game script
Basic game.narrat
which mostly uses the above:
main:
set data.PATHS (load_data data/forest.yaml)
set data.SPRITES (load_data data/common_sprites.yaml)
set data.previousPathId forest
set current (new Object)
set current.id forest
set current.path $PATHS.forest
set current.index 0
set_screen @empty 1
set_screen @empty 2
run load_path $current.id
dr_pepper:
clear_dialog
"A can of Dr. Pepper."
"I didn't know they grew in forests."
Yaml file for paths
forest.yaml
, which contains the actual data for all the paths
forest:
id: Forest Entrance
contents:
- bg: entrance1
backAllowed: false
- bg: entrance2
- bg: entrance3
pics:
- img: dr_pepper.jpeg
x: 100
y: 800
label: dr_pepper
ratio: 16/9
paths:
- id: entrance-fork-1
x: 200
y: 500
arrow: left
- id: step-2
x: 1500
y: 300
arrow: forward
entrance-fork-1:
id: Entrance Fork 1
contents:
- bg: entrance-fork-1
back: forest
step-2:
id: Step 2
contents:
- bg: step-2-1
back: forest
- bg: step-2-2
Sprites data
common_sprites.yaml
, which contains a list of sprites info for easily adding commonly needed sprites:
left:
background: img/ui/left.webp
x: 100
y: 250
width: 200
height: 200
anchor:
x: 0.5
y: 0.5
onClick: choose_left
right:
background: img/ui/right.webp
x: 770
y: 250
width: 200
height: 200
anchor:
x: 0.5
y: 0.5
onClick: choose_right
forward:
background: img/ui/front.webp
x: 440
y: 120
width: 200
height: 200
anchor:
x: 0.5
y: 0.5
onClick: choose_front
back:
background: img/ui/back.png
x: 440
y: 380
width: 200
height: 200
anchor:
x: 0.5
y: 0.5
onClick: choose_back
big_overlay:
cssClass: big-overlay
layer: 1
x: 0
y: 0
width: 1920
height: 1080
anchor:
x: 0
y: 0
onClick: close_pic
CSS file
And the main.css
file:
/* See https://docs.narrat.dev/guides/theming-the-game-and-ui for info on how to customise your game with CSS. */
html {
font-size: 20px;
}
#app {
/* Customise CSS variables here. They will override the existing narrat ones. You can also add your own variables */
--bg-color: #131720;
--text-color: #d9e1f2;
--primary: hsl(255, 30%, 55%);
--focus: hsl(210, 90%, 50%);
--secondary: #42b983;
}
/* Add any other CSS you want here */
.big-overlay {
background: rgba(0, 0, 0, 0.5);
}
.pic-preview {
background-color: white;
/* Polaroid style image */
border-radius: 10px;
border-top: 6px solid white;
border-left: 6px solid white;
border-right: 6px solid white;
border-bottom: 6px solid white;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.8), 0 6px 20px 0 rgba(0, 0, 0, 0.8);
}
.full-pic {
border-top: 30px solid white;
border-left: 10px solid white;
border-right: 10px solid white;
border-bottom: 10px solid white;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.8), 0 6px 20px 0 rgba(0, 0, 0, 0.8);
}
.dialog.override {
border-radius: 20px;
}