Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Twine 2 Tutorial: Picking up & Placing Items

The story is coming along nicely. In the last tutorial, you created objects and then dynamically linked them to print out their description. This is awesome and greatly simplifies your story. Now, comes the part where you place the objects throughout the world.

Remember, on easy mode, the items are put in pre-determined locations. In hard mode, the item locations are randomized. Finally, the player will only pick up items. They won’t put them down. You could write that Code for dropping items but it doesn’t add any value to the player. That said, feel free to write that code yourself.

Creating the Id Numbers

Before you place the items, download the starter project or use the one that you’ve been using throughout this series.

picking-up-items-starter-projectDownload

In an earlier tutorial, you used an array to store all the items for the player. You’re going to use that array to hold your Inventory items, so you should feel comfortable with this. For each room item, you’ll assign that item a room id. You could create inventory arrays for each room, but that’s overkill since the player will only pick up items, not put them down.

Remember, a “room” is an invented concept. Twine has no concept of a room. Everything in Twine is a Passage. Here is a list of room’s now:

Starting with the Camp Entrance and moving clockwise, each room will obtain its own id as follows:

Now each room has an id. The actual number is arbitrary. You could have randomly distributed the numbers based on star charts. By using a clearly defined system, over time, you’ll mentally map the numbers to the rooms so you won’t need to look up the values when you need them.

Codifying the Rooms

At this point, YOU know about the rooms but the STORY doesn’t. It’s all in your head. It’s time to create individual room objects in code.

Open up the Startup passage. At the top of the passage, add the following code. Make sure to add it after the opening brace.

(set: $rooms to 
  (a:
    (dm: "name", "Camp Entrance", "id", 1),
    (dm: "name", "Side Road", "id", 2),
    (dm: "name", "Waterfront", "id", 3),
    (dm: "name", "Lakeside Road", "id", 4),
    (dm: "name", "Crafts Station", "id", 5),
    (dm: "name", "Intersection", "id", 6),
    (dm: "name", "Store", "id", 7),
    (dm: "name", "Parade Ground", "id", 8),
    (dm: "name", "Mess Hall", "id", 9),
    (dm: "name", "Soccer Field", "id", 10),
    (dm: "name", "Back Road", "id", 11),
    (dm: "name", "Hopi Campground", "id", 12),
    (dm: "name", "Dusty Road", "id", 13),
    (dm: "name", "Tombstone Campground", "id", 14),
    (dm: "name", "Camp Path", "id", 15)
   )
)

It should look like the following:

There’s a lot going on in this code. First, you create a variable called $rooms. This stores all the room data.

Next, you use the (a :) macro to create an array. Remember, an array allows you to store multiple values in a list. If you’ve forgotten about arrays then do yourself a favor and reread this tutorial.

Finally, you create a data map for each room. Each data map contains two values. It contains the location’s name and then the location’s id.

So to review: the $rooms variable contains an array that stores all the room data in the game.

Setting Locations

When a player starts the story, they have two options: easy and hard mode. In easy mode, items are placed in the same location. In hard mode, items are randomly scattered throughout the world. To make things simple, you’ll write the code from easy mode first. You’ll implement hard mode in the next tutorial.

To get started, the flashlight can be found at the Camp Entrance (1). The battery can be found at the Store (7). The car keys can be found at the Hopi Campground (12). The gas can be found at the Waterfront (3). Finally, the recipe can be found in the Mess Hall (9).

In the Startup passage. Update all the items to the following:

(set: $carKeys to (dm:
  "id", 1,
  "location", 12,
  "name", "keys",
  "description", "These look like Bernie's keys to his pickup.",
))
(set: $flashlight to (dm:
  "id", 2,
  "location", 1,
  "name", "a worn flashlight",
  "description", "The flashlight is worn and well used. Quite useful in dark areas.",
))
(set: $battery to (dm:
  "id", 3,
  "location", 7,
  "name", "flashlight batteries", 
  "description", "A package of batteries. Perfect for a flashlight.",
))
(set: $gasCan to (dm:
  "id", 4,
  "location", 3,
  "name", "Can of gas",
  "description", "A gallon of the good stuff. Should be enough to escape.",
))
(set: $recipe to (dm:
  "id", 5,
  "location", 9,
  "name", "Chili recipe",
  "description", "Bernie's receipe is scribbled instructions on the back of a gas receipt. It's best left in the same warehouse which houses the lost ark.",
))

Note: I found a bug with the name of Bernie’s Chili Recipe so for the sake of simplicity, change it to just: Chili recipe. The bug has to do with apostrophes and the (print:) macro as you’ll soon see.

The code is mostly the same except you added a location field. Underneath that code, add the following:

(set: $items to (a: $flashlight, $battery, $carKeys, $gasCan, $recipe))

This provides a quick easy way to loop through all the items to find their current location. You’ll see this in action in just a few moments.

Finally, update the inventory to the following:

(set: $inventory to (a:))

It should look like the following:

Now the player starts without items.

Listing Items

At this point, you have defined all the items and created all the rooms in code. Now you want to list the items. For the approach, you’ll create a passage specifically for listing items and you’ll call that passage in every room passage. Time to get to work.

Create a new passage. Name it: Room Items

This going to contain a bit of code, so hang on. First, you want to remove all whitespace, so start by including a pair of braces.

{

}

Next, define a variable called $roomItems that will contain all the items found in the room.

{
  (set: $roomItems to (a:))
}

Now loop through every single item.

{
  (set: $roomItems to (a:))
  (for: each _item, ...$items) [
   
  ]
}

Compare the item’s location to the current location id. You haven’t set it yet. Don’t worry. You’ll do that in a moment.

{
  (set: $roomItems to (a:))
  (for: each _item, ...$items) [
    (if: $currentLocation is _item's location) [
    
    ]
  ]
}

Now for a little defensive programming. If the item is contained in the player’s inventory, then the item shouldn’t be displayed.

{
  (set: $roomItems to (a:))
  (for: each _item, ...$items) [
    (if: $currentLocation is _item's location) [
      (if: $inventory contains _item's id is false) [

      ]
    ]
  ]
}

Finally, add the item to the $roomItems.

{
  (set: $roomItems to (a:))
  (for: each _item, ...$items) [
    (if: $currentLocation is _item's location) [
      (if: $inventory contains _item's id is false) [
        (set: $roomItems to $roomItems + (a: _item))
      ]
    ]
  ]
}

This last bit of code creates a new array with the item in it and adds it to the $roomItems. Notice the code doesn’t care about the number of items in the room. By our own internal logic, each item will be placed in a separate room but the story does support multiple items in the same room.

The final passage should look like the following:

Awesome work! Except … the code doesn’t do anything. Not yet!

Displaying the Items

Now that you’ve defined a variable to contain your items, it’s time to print them out. Don’t worry, this code will look very familiar. First, open the Startup passage. Add the following:

(set: $selectedItemId to 0)

When the selectedItemId is zero, then no item is selected.

Next, open the Room Items passage. Underneath the last code block, add the following:

(if: $roomItems's length > 0) [
  (print: "
") On the ground, you see the following: (for: each _item, ...$roomItems) [ ] ]

It should look like the following:

Now comes the fun part. You want to link each item to a passage that will display the item information. Add the following:

(link: '" + _item's name + "')[ 
  (set: $selectedItemId to " + (text:_item's id) + ")
  (go-to: 'Item Description') 
]

It should look like the following:

This does a couple of things. First, it creates a text link with the name of the item. When the user clicks the link, it’s the $selectedItem variable to item’s id. Then it sends the user to the Item Description passage. You’ll add this momentarily. For now, open up the Camp Entrance passage. At the top of the passage, add the following:

(set: $currentLocation to 1)

This sets the current location id to 1. This lets you know the current location of the player. Now replace all the code at the bottom of the passage with the following:

{
  (display: "Room Items")
}

It should look as follows:

The (display :) macro allows us to print out a passage within the current passage. In this case, it will display the inventory. Run the story.

When you get to the Camp Entrance, scroll all the way to the bottom of the page. You’ll see the following:

This is what we call a big old mess. Everything is broken. This is because you tried to dynamically create a link in a loop. This doesn’t work. It confuses the system. Instead, you have to write a far more confusing bit of code.

Open the Room Items passage. Replace your link code with the print statement. It should look like the following:

(if: $roomItems's length > 0) [
  (print: "
") On the ground, you see the following: (for: each _item, ...$roomItems) [ (print: "(link: '" + _item's name + "')[ (set: $selectedItemId to " + (text:_item's id) + ")(go-to: 'Item Description') ]") ] ]

It should look as follows:

Notice instead of directly using the (link :) macro, you use the (print :) macro instead. When the user reaches this part of the code, the (print :) macro actually prints out the code for the (link :) macro which the computer immediately evaluates.

One other thing: notice you are passing just the item id and not the item itself. This isn’t possible using this (print: ) macro trick. This actually causes a problem.

Run the story. You’ll see it actually works:

You’ve made your links entirely dynamic based on choices created by the player. Unfortunately, not everything is working as expected. If you click on a worn flashlight, you’ll run into the following error.

The Item Description passage expects a data map, yet you are passing an item id. Time to fix this.

Fixing the Item Description

Fixing the Item Description passage requires some thinking. It may be easier for the passage to require just an item id instead of an object, but that requires you to rewrite the logic in another passage. That may be a sound approach, but later in your story, you may have no choice but to pass in an object.

The moral of the story: think about how you will ultimately use this passage and then be consistent throughout the rest of your story. Don’t automatically default to the “easy” solution because you may find the easy solution is cheap up front, but may “cost” more time and labor down the road. This is referred to as technical debt.

In your case, you’ll design the passage to receive a data map, and if one isn’t present, then you’ll use the id to find the data map in your list of objects. That provides a greater degree of flexibility.

Open the Item Description passage. Update it to the following:

{
(if: $selectedItemId > 0) [
  (for: each _item, ...$items) [
    (if: _item's id is $selectedItemId) [
      (set: $selectedItem to _item)
      (set: $selectedItemId to 0)
    ]
  ]
]
(print: $selectedItem's description)
(print: "

")
(set: $previous to (history:)'s last) (link: "Back") [ (set: $previous to (history:)'s last) (goto: $previous) ] }

It should look like the following:

If the $selectedItemId variable is not zero, then the code will look up the actual object in all the items. Once found, it sets the $selectedItemId back to zero.

Run the story. Select the Flashlight and now you can actually read the description. Except you can’t pick it up. That comes next.

Picking Up Items

Believe it or not, the hard stuff is done. When the player picks up an object, you should add that object to the player’s inventory. And that’s all there is to it.

With Item Description passage open, update the code to the following:

{
(if: $selectedItemId > 0) [
  (for: each _item, ...$items) [
    (if: _item's id is $selectedItemId) [
      (set: $selectedItem to _item)
      (set: $selectedItemId to 0)
    ]
  ]
]
(print: $selectedItem's description)
(print: "

") (set: $previous to (history:)'s last) (link: "Back") [ (set: $previous to (history:)'s last) (goto: $previous) ] (if: $inventory does not contain $selectedItem) [ (link: "
Pick up " + $selectedItem's name) [ (set: $inventory to $inventory + (a: $selectedItem)) (print: "
Taken.") ] ]
}

It should look as follows:

First, you check to see if the item is contained in the $inventory array. If it isn’t, you add it to the player’s inventory. Notice instead of sending the user back to the previous passage, you are instead printing out the word, “Taken”.

Run your story and pick up the flashlight. It works. The link changes to simple text. But, if you go back to the camp entrance, the flashlight is still listed as on the ground while the player is carrying it.

Now we are running into some code friction with the Harlowe story format. In an attempt to ease the learning curve for beginners, simple tasks in other languages become rather complex in Harlowe. This is why many Twine authors prefer the Sugarcube format. While SugarCube provides some Twine-specific features, it’s written in Javascript giving you flexibility and control.

Back in the Item Description passage, update the pick-up code to the following:

(link: "Back") [
    (set: $previous to (history:)'s last)
    (goto: $previous)
]

(if: $inventory does not contain $selectedItem) [
  (link: "
Pick up " + $selectedItem's name) [ (set: $inventory to $inventory + (a: $selectedItem)) (set: $selectedItem's location to 0) (set: $updatedItems to (a:)) (for: each _item, ...$items) [ (if: _item's id is $selectedItem's id) [ (set: $updatedItems to $updatedItems + (a: $selectedItem)) ] (else:) [ (set: $updatedItems to $updatedItems + (a: _item)) ] ] (set: $items to (a:) + $updatedItems) (set: $selectedItem's location to 0) (print: "
Taken.") ] ] }

It should look as follows:

Wow! That’s a lot of code. Welcome to Harlowe. First, you set the $selectedItem’s location to zero. This means it’s no longer in a room. Next, you create a new array and loop through all the items, and place it in that array. If it’s the current object being picked up, you use the $selectedItem data map instead of the data map already in the array. The old data map object has the location set to 1. The new one is set to 0. Finally, you assign it back to the $items array.

This is a lot of code simply to change one property of a data map and allow it to be accessible throughout the rest of the story.

Run the story. Now you can pick up a flashlight and it shows up in your inventory, and not in the room.

Applying the Changes

Great – you have a working inventory and room items, but only for one room. It’s time to apply your changes throughout the rest of the locations. In the case of the Camp Entrance passage, you used a (display :) macro. A better solution is to use the footer tag. This allows the code to be run on every single passage.

You can add the footer tag to many different passages, but I prefer to use one passage and then use (display :) to reference other passages.

Create a new passage. Call it: Location Footer. Add the following:

{
(if: (passage:)'s tags contains "Room-Items") [
  (display: "Room Items")
]
(if: (passage:)'s tags contains "Inventory") [
  (display: "Inventory")
]
}

It should look like the following:

With the Location Passage still open, click on the dropdown for the Add Tag field.

Select the footer option.

Next, open the Camp Entrance passage. Update it to the following (making sure to remove the difficulty selection text):

(set: $currentLocation to 1)A worn looking sign says, "Welcome to Adams Pond Scout Camp" yet there the surrounding forest loom heavy and foreboding like the night that engulfs it.

A [[dusty dirt strewn road->Dusty Road]] leads into the darkness to southeast. A sign next to the road reads, "Camp Center". A [[side road->Side Road]] branches off to the northeast. A sign next to it reads: "Waterfront". Finally, [[a graveled strewn road->Back Road]] goes to the southwest. A sign next to it reads: "Facilities" 

The passage should look as follows:

Since you are calling the Inventory passage from the Location Footer passage. Click the dropdown arrow on the footer tag, then select the Remove option.

Now run the story. The Camp Entrance looks as before except there’s some unwanted spacing.

First, you need to make adjustments. Open up the Inventory passage and update it to the following:

{ (if: (passage:)'s tags contains "Inventory") [


You are carrying:  (for: each _item, ...$inventory) [ (link: _item's name) [ (set: $selectedItem to _item) (goto: "Item Description") ](if: $inventory's last is not _item)[, ] ]]}

It should look as follows:

The   is known as an HTML character for a non-breaking space. This isn’t a Twine convention but works with all HTML pages. There are a lot of HTML codes. Here is a list of them.

Open up the Room Items passage and add some breaking tags. Part of the fun of working with Twine is getting all your spacing right.

  (if: $roomItems's length > 0) [
    (print: "

")
On the ground, you see the following: (for: each _item, ...$roomItems) [ (print: "(link: '" + _item's name + "')[ (set: $selectedItemId to " + (text:_item's id) + ")(go-to: 'Item Description') ]") ] ] }

It should look as follows:

Now comes the fun part, open every location passage, and add the “Room Items” tag. To get started, open the Camp Entrance passage. Click the + Tag button. Name the tag: Room Items. Finally, click the + Add button.

Next, open the Side Road passage and click the + Tag button. From the Add Tag dropdown, select the Room Items tag. Then click the + Add button.

Do this for all the locations.

To visually see the tag placement, click the Story menu option, and from the sub-menu, select the Passage Tags option.

You’ll see a list of tags with color options next to them. For the Inventory, set the color to yellow. For the Room-Items, set the color to orange. For the footer, set the color to red.

Now, you’ll see all your passages are marked in various colors to denote the passage tags.

You’ll notice that you added the Room-Items tag to passages that have the Inventory tag. Why not just use the Inventory tag instead?

There may be locations where you’d rather hide the inventory but display room items or vice-versa. The addition of a new tag gives you finer control at the expense of a little more overhead. This may help or hinder you. It’s up to choose a practice that works best for your workflow and then be consistent with it.

The last thing to do is set the $currentLocation variable in each passage. You did this with the Camp Entrance. Now you need to tag the rest of the locations.

For example, the Side Road passage looks as follows:

(set: $currentLocation to 2)Giant trees encompass the road, coiling around it like a hungry python. Not a bird squawks. Not a peeper peeps. Anything could be out there. You just hope it doesn't have chili.

The [[road continues->Camp Entrance]] into the darkness to the southwest. The road also turns a [[sharp bend->Waterfront]] and disappears to the southeast.

Do this for all fifteen location passages. Here are the reference numbers:

Run the story. And at long last, you can pick up every single item in the camp and inspect it. Well done!

Where to go from here

Wow! Excellent work. That was a lot of code, but now you can really see the framework of the Twine story coming together.

If you ran into issues, definitely compare your code with the final product to see where you may have missed something:

picking-up-items-final-projectDownload

But, you’re not done just yet. You’ve only written the code for easy mode. What about hard mode? Also, there are some locations that require certain objects. You’ll add these in the next tutorial, completing the entire camp.

The post Twine 2 Tutorial: Picking up & Placing Items appeared first on Jezner Blog.



This post first appeared on Jezner - Old Time Radio, please read the originial post: here

Share the post

Twine 2 Tutorial: Picking up & Placing Items

×

Subscribe to Jezner - Old Time Radio

Get updates delivered right to your inbox!

Thank you for your subscription

×