This article provides an overview of slices, the system used for customisable graphical display in the OHRRPGCE. You can also have a look at the Slices FAQ, and Category:Slices for more articles on Slices.
As an alternative introduction to slices, see also BMR's more detailed but less comprehensive Simple Slices Tutorial:
Or as an alternative alternative, Sword's Slice Tutorial (very short and incomplete):
- 1 What is a slice?
- 2 Slice Collection Editor
- 3 Family Metaphor
- 4 Slice types
- 5 Slices and Variables
- 6 Lookup codes
- 7 Handles
- 8 X/Y vs Screen X/Screen Y
- 9 Attaching slices to parents
- 10 Looping through slices
- 11 Deleting slices
- 12 Layering
- 13 Alignment and Anchors
- 14 Learning More
What is a slice?
A slice is a rectangular thing drawn to the screen. There are several types, some of which are just invisible containers of other slices. Each has an X/Y position, a width and a height, and many other common attributes such as visibility. Slices are organised in a "tree": except for the "root" slice, every slice is attached to another slice which is called its parent (meaning it's positioned relative to its parent).
Map layers, NPCs, and textboxes are examples of things drawn with slices, which allows their appearance to be customised using scripts.
Slice Collection Editor
A great way to get used to what slices are capable of is to try out the Slice Collection Editor in Custom. The slice collection editor lets you build complex collections of slices using a graphical interface. You can load these collections into your scripts, but even if you will be creating all your slices manually in your plotscript, the slice collection editor is still a great place to test out slices and get a feel for how they work and what options they have.
You can also access the editor while playing a game by pressing the Ctrl+F4 debug key.
See the Slice Collection Editor Tutorial.
You will notice that slices use a lot of family metaphors, especially "parent" and "child". You can think of the slice tree as a family tree.
A parent slice is any slice that has one or more children. If you move the parent, all of its children (and grandchildren) will move too. Resizing parent slice can affect the children too, depending on how they are attached to the parent.
A parent slice is always displayed behind its children.
Every slice (except the root slice) is a child of some other slice. Child slices are positioned relative to their parent, so when you want to move around a whole group of slices, you can just move their parent, you don't have to bother to move each individual child.
Child slices are always displayed in front of their parent.
Sibling slices are any two slices that share the same parent. Slices are displayed in order from the first (oldest) slice to the last (youngest) slice. That means that younger siblings will overlap older siblings. There are commands that let you rearrange the order of siblings, so you can easily make a specific slice appear behind or in front of another slice.
one big happy family
All slices are part of one big family tree. Every slice is connected to every other slice, as children, grand children, great-grand-children, parents, grand parents, great-grand-parents, siblings, cousins, second cousins, aunts, great aunts, niblings, grand niblings (slices are always female, don't you know?)
why does family matter?
The familial relations of slices matter for how they overlap each other on the screen, and how they move together in groups. If slices don't overlap or don't need to move together, it doesn't really matter how they are related.
The types of slices are:
These display a frame of a sprite set. For example, you can draw the attacking frame of hero spriteset 2 to screen. Unlike the other types, these have fixed width and height.
Sprite slices have different subtypes, each created by a different command:
- walkabout (| }} load walkabout sprite)
- hero (| }} load hero sprite)
- small enemy (| }} load small enemy sprite)
- medium enemy (| }} load medium enemy sprite)
- large enemy (| }} load large enemy sprite)
- attack (| }} load attack sprite)
- weapon (| }} load weapon sprite)
- box border (| }} load border sprite)
- portrait (| }} load portrait sprite)
- backdrop (| }} load backdrop sprite)
It's not possible to load a tileset or font as a sprite slice.
One subtype can be turned into another with the replace ... sprite commands, eg | }} replace backdrop sprite.
These are rectangles (with optional box borders), like the back of a text box, or the health meters in battle.
Draws a one-pixel wide straight line from one corner to the opposite corner. You can give a line slice a negative width or height. The (width,height) of the slice is used as the offset of the end of the line from the start. So if you parent one line slice to another with top-left alignment and anchoring and (0,0) offset they will join up.
Like all slice types, line slices are still treated as rectangles for collision checking.
These are invisible boxes containing text. The text can be configured to wrap around to stay inside the box.
These are invisible. Containers are not special, they are basically plain slices. Every other slice type can do everything that they can (with the exception that sprite slices, and sometimes text slices, can't be resized).
Grids are special containers that will automatically arrange their children into a grid. You specify the number of rows and columns, and if there are more than rows * columns children then the last ones are not displayed.
Ellipse slices draw an oval to the screen which has maximum width and height equal to the slice width and height. The oval can be optionally filled, and the border is one pixel thick. The "major" and "minor" axes of the ellipse are always aligned with the X and Y axes.
A Scroll slice is a special container that automatically displays scrollbars when its child (or descendant) slices overlap its edges. The Check Depth property of a scroll slice says whether just its children should be checked for overlap, or two, or more generations of children, or all descendants. Typically you will want to set a scroll slice to Clip Children, so that children off the edges won't be visible.
A Select slice is a convenient way to have a set of slices, just one of which is visible at a time. The visible child is the "selected" child. A select slice is like a container slice, with the addition of a "selected index". For example if the index is 1 then the second child is visible. If the index is invalid, e.g. -1, then no child is selected/visible.
A panel slice is a special container that automatically resizes and repositions its first two children. The children will each take up a portion of the panel slice's space. It can divide space horizontally or vertically, and can be configured to share space according to different percentages or fixed numbers of pixels. If it has more than 2 children, the additional children will always be hidden.
These are certain slices created automatically, such as the 'textbox layer' slice and the root slice. They are just containers. You can't create or destroy them, and there are no special commands for any of them.
Each map layer is a slice. These are a different type of special slice that represents the map. You can't create or destroy them, and there are no special commands for this slice type.
Slices and Variables
Anyway, the main way you will use slices is with plotscripting, so on to coding examples! Suppose you want to put a large enemy sprite on the screen.
variable(sl) sl := load large enemy sprite(0)
We have now added a slice containing a copy of large enemy sprite #0 to the slice tree. The slice tree is always drawn; all we need to do is add things to it and move them around. Here is how we move a slice:
set slice x(sl, slice x(sl) + 10)
That moves the sprite 10 pixels to the right.
If you want to load a collection of slices that you designed in the Slice Collection Editor, use this command:
variable(sl) sl := load slice collection(0)
A slice lookup code is a name for a slice. (Multiple slices can have the same name.) All of the engine's auto-created slices, such as map layer slices, have unique names. You can use | }} lookup slice to search for the handle of a specific slice, either globally or just part of the slice tree, such in the collection that you just loaded. The slice collection editor allows you to assign lookup code names to your slices. Edit a slice in the Slice Collection Editor, press Enter on "Lookup Code", then go to a blank space at the bottom of the menu (press Down or End), and then type in a name like "my wonderful slice".
variable(col, sl) col := load slice collection(0) sl := lookup slice(sli:my wonderful slice, col)
You can also use the | }} set slice lookup command to give a slice a lookup code, perhaps one you created with a script, so that you can find it back later. First, you should to create a lookup code in the slice collection editor (even if you don't use the editor for anything else): follow the instructions above (create a new slice to edit if necessary if you don't have any and then delete it later), It is also possible to just use numbers instead of names. You must use large positive numbers like 10000. Small numbers are assigned to lookup codenames.
Then you set a lookup code like so:
variable(sl) sl := load walkabout sprite(4) set slice lookup(sl, sli:my wonderful slice)
What is a slice handle? A slice handle is a magic number that represents a specific slice. You store this magic number in a variable. Almost every slice-related plotscripting command works with slice handles. You don't need to store a handle for every single slice. You only need to use handles for slices that you need to move, change, rearrange, or remove.
X/Y vs Screen X/Screen Y
You may notice in the plotscripting dictionary that there are two sets of functions for working with slice x and y
- x| | }} slice x
- slice x| | }} set slice x
- y| | }} slice y
- slice y| | }} set slice y
- screen x| | }} slice screen x
- slice screen x| | }} set slice screen x
- screen y| | }} slice screen y
- slice screen y| | }} set slice screen y
At first glance these might seem to be totally redundant. Indeed, if you check out the values for our newly loaded large enemy sprite, you will notice that the x/y and screen x/screen y are the same.
The difference is, the slice x/y values are relative to the slice's parent. Every slice (except the root) has a parent. When we first load a sprite, the default parent for it is layer| | }} sprite layer which is a "special" slice. The sprite layer slice has an x/y position of 0,0 and a width/height of 320,200 just the same size as the screen.
Attaching slices to parents
One of the really cool things about slices is that you can attach them to a parent. For example, let's load a small enemy sprite that resembles a top-hat and put it on the large enemy sprite we loaded before.
variable(hat) hat := load small enemy sprite(0) set parent(hat, sl) set slice x(hat, 23) set slice y(hat, -28)
I'm just guessin' on the x/y here, because I don't know exactly where the head of your large enemy sprite will be, but the point is that we have detached the hat sprite from the screen, and attached it to the large enemy sprite. Now if we say:
set slice y(sl, slice y(sl) + 5)
then BOTH the enemy sprite AND his hat will move together.
Looping through slices
Now suppose that in this way we have set up 5 different enemy sprites, each of them wearing a hat. That makes a total of 10 slices on screen. Does that mean that we need to keep 10 variables to store their handles? Well, not really. Sure, you could be tucking the slice handles away in a fake array using global| | }} write global, but you don't really need to.
The fact is, the children of a parent slice are already kinda like an array. So let's see how you loop through them without needing to know their handles in advance.
variable(handle) handle := first child(sprite layer) while(handle) do, begin #do something to each handle here handle := next sibling(handle) end
So first we use the child| | }} first child command. This takes any slice and gives us the handle to its first child, or 0 if there are no children.
Slice handles are always non-zero, so then we can say while(handle) to do our loop.
At the end of the loop, we use the sibling| | }} next sibling command to get the next slice handle, or 0 if there are no more (and as you know, a 0 will cause the "while" loop to stop)
Now if we loop through our hat-wearing enemy sprites in this way, the while loop is going to loop through 5 times, once for each enemy.
Why not for the hats? The hats are NOT children of the layer| | }} sprite layer anymore. Remember? we re-parented them with the parent| | }} set parent command. The hats are now children of the enemy sprites (and thusly they are grandchildren of the sprite layer)
So what if we want to loop through all the hats and manipulate them?
variable(handle, hat) handle := first child(sprite layer) while(handle) do, begin hat := first child(handle) if(hat) then, begin #do something to each hat here end handle := next sibling(handle) end
See? We can use the "first child" command on the enemy sprite's slice handle and get the hat. Now suppose we wanted to allow enemies to maybe wear more than one hat... or glasses... or gloves or something.
variable(handle, clothing) handle := first child(sprite layer) while(handle) do, begin clothing := first child(handle) while(clothing) do, begin # do something with each article of clothing here clothing := next sibling(clothing) end handle := next sibling(handle) end
And yes, children can have children which can have children, and so-on, until you create a family tree of slices that is so big you run out of memory.
One other thing to note is that if you delete a slice using slice| | }} free slice then all of its children will be deleted too, as if you are chopping the whole branch off of the family tree.
Layering is done oldest-to-youngest, with depth-first traversal of the tree.
- That means that we start with the slice layer
- Then we draw the first child. (big enemy 1)
- Then we draw the first child of the first child (big enemy 1's hat)
- Then we draw the second child (big enemy 2)
- Then we draw the first child of the the second child (big enemy 2's hat)
- Then we draw the third child (big enemy 3)
And so-on. The upshot is that younger siblings are always drawn on top of older siblings, and that children of a parent are always drawn directly on top of their parent. Once you get use to it, it is pretty easy to understand how to arrange your slices to make them draw in the order you want. If you have not done so already, this might be a good time to use the "Slice Collection Editor" tool in custom, since you can use it to experiment with the way that slices are layered, and it shows you a visual representation of the slice tree.
Containers can be great for controlling how slices layer. A container is simply an invisible slice for use as a parent to other slices that you want to group together either for looping or for controlling draw-order. You can change the width and height of a container however you want.
Alignment and Anchors
So, you already know that a slice has a relative x/y position to its parent. By default, a slice is aligned to the top left corner of its parent, and it is anchored by its own top left corner. For some purposes, positioning everything according to top left corners is all you are going to need, but sometimes it becomes more convenient to position a slice in other ways. Check out this example where I attach a four NPC sprites to a container, one on each corner
variable(box, sl) box := create container(150, 150) sl := load walkabout sprite(4) set parent(sl, box) # this one starts in the top left sl := load walkabout sprite(5) set parent(sl, box) set horiz align(sl, edge:right) set horiz anchor(sl, edge:right) # put this one in the top right sl := load walkabout sprite(6) set parent(sl, box) set vert align(sl, edge:bottom) set vert anchor(sl, edge:bottom) # put this one in the bottom left sl := load walkabout sprite(7) set parent(sl, box) set horiz align(sl, edge:right) set horiz anchor(sl, edge:right) set vert align(sl, edge:bottom) set vert anchor(sl, edge:bottom) # put this one in the bottom right
Now each of these four sprites will be arranged at the corners of a 15x150 container. Notice that we did not change the x/y of any of these slices? If you use x| | }} slice x and y| | }} slice y you will see that they are still all at 0,0 but if you check with screen x| | }} slice screen x and screen y| | }} slice screen y you will see that they now all have been moved.
Now what gets really cool is if you resize the container.
variable(i) for(i, 0, 49) do, begin set slice width(box, slice width(box) + 1) set slice height(box, slice height(box) + 1) wait(1) end
The sprites attached to the corners will move automatically as you resize the box!
Now that you understand the basics of what a slice is, read the Slice Collection Editor Tutorial next.
You may also want to browse the Slices section of the Plotscripting Dictionary and see all available commands.
Here is a list of games that use slices, which include script files that you can look at for examples. (Note that some of these actually get quite complicated, and might not actually be good examples unless you already have a solid understanding of plotscripting.) Some also use slice collections which are worth inspecting.
- Space InvadeOHRs (fairly easy)
- Slice Test Case RPGs (fairly easy)
- Large Walkabout Script (fairly easy) obsolete but still instructional
- Mr. The Hamster's Math Class (advanced?) lots slice collections you can inspect
- Bob the Hamster in the Crypt of Baconthulhu (somewhat advanced)
- Don't Eat Soap (advanced!)
- Bell of Chaos (advanced!)
- Vocabmosaic (advanced!)
If you've written your own game using slices and you think it will be a good example for other people (and you have included your script file), please add it to this list.