A walk through the forest

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;
}
4 Likes

this is neat as hell! i’d been wondering about doing something like this. it’s very dumpling.love

1 Like

Oh, this is really cool! Makes perfect sense that this would work but I never thought about it.

1 Like

Another update, I added a little plants widget system to show plant identification and info on some pictures

1 Like

I’ve just finished setting up the entire path and wrote all the main story :eyes:

Now I just need to place all the pictures in the screens and write their descriptions :zzz:

1 Like