Programming Tips #1 - If Statements
Programming Tips #2 - Making Progress
Programming Tips #3 - Design
Programming Tips #4 - Datums
Programming Tips #5 - Organization
Programming Tips #6 - Procs & Organizing Code
Programming Tips #7 - Comments and Whitespace
Programming Tips #8 - Loops and Ifs
When you first start programming, the problem you're trying to solve is "how do I write code that'll do ______?". Initially you're happy just to get something working, who cares what the code looks like. But when you try to develop a complete game this indifference can become a problem. As your code gets messier and messier, the project becomes harder to work on - you're more likely to write code that has bugs and they become harder to track down and fix. Eventually it'll get to the point where you're wasting a lot of time, don't feel productive, and are more likely to stop working on the project.
As a more experienced programmer, instead of asking "how do I do ______?", you should start asking "what's the best way to do ______?". In this post we'll look at some techniques you can use to keep your projects organized.
Using the Map Editor
BYOND's Dream Maker program gives you a map editor for creating your game's maps. You don't have control over its features, but you do have control over the objects you're placing on the map. You define what objects can be placed on the map and you define what these objects do when the game starts up. You can use both of these things to your advantage to simplify how you use the map editor.
Here are some tips and techniques I've found that make the map editor easier to use.
1. Instead of using multiple object types or the instance editor, use the object's constructor.
The instance editor is difficult to use and it's hard to manage the instances you've made changes to. Having multiple object types lets you avoid using the instance editor but can really bloat how many object types you have (which can also make things hard to manage).
Suppose you want to use four different grass icon states to make things look less tiled. You could create a single object type (ex: /turf/grass) and use the instance editor to change it's icon state, but this would be very annoying. You could create four different grass types (ex: /turf/grass1, /turf/grass2, etc.) but this makes your object tree bloated.
You can use the object's constructor to pick a random state. Because you just want some variation and don't care about assigning specific grass states to specific tiles, this is fine:
turf
grass
icon_state = "grass"
New()
..()
dir = pick(NORTH, SOUTH, EAST, WEST)
The grass icon state is a 4-directional icon where each direction is one of the variations. This way we're not crowding the icon file with tons of icon states.
The object's constructor can also be used to create autojoining tiles. Instead of having tons of different turf types that are used to create walls or floor types that use different icon states for different borders, you can write code that figures out which icon state should be used. Adding a little bit of code to handle this can really simplify map creation.
For example, in the Sidescroller library the walls have one pixel black borders around the edges. There are 16 different icon states for these walls. If I created 16 different object types and had to place them properly when creating maps, it'd take forever to make a simple map. Instead, I just use this code:
turf
wall
density = 1
icon_state = "wall"
New()
..()
var/n = 0
var/turf/t = locate(x,y+1,z)
if(t && istype(t,type)) n += 1
t = locate(x+1,y,z)
if(t && istype(t,type)) n += 2
t = locate(x,y-1,z)
if(t && istype(t,type)) n += 4
t = locate(x-1,y,z)
if(t && istype(t,type)) n += 8
icon_state = "wall-[n]"
Tiny Heroes uses a similar approach except the black outline is an overlay.
2. Be mindful of how many object types are dervied from the same parent type.
Being able to close and expand nodes of the object tree lets you limit what objects are shown in the map editor. If you tons of turf types that are all just children of /turf you can either show them all or hide them all, there's no in between.
If you wanted to create four different grass states and have control over where each type is placed, you could do it this way:
turf
grass1
grass2
grass3
grass4
But then you're bloating the object tree. If you have different types of grass, each with four different states, things can get really out of hand. Instead you can do it this way:
turf
grass
grass1
grass2
grass3
grass4
That way can keep /turf/grass collapsed when you don't need to use the other four types.
3. Remember that you can easily change maps by changing the objects you've defined.
If you make a new water icon you can easily update all water turfs to use the new icon by changing the turf's definition. This is obvious, but some of the ways of applying this idea are not as obvious.
Suppose you start working on a game and have just a single water icon, so you define this turf:
turf
water
icon_state = "water"
Then you create a bunch of maps that all have water and use this same turf type. However, later on you decide to make different tilesets and end up with six different water icons. Only a small amount of water tiles will continue to use the original water icon. There's no easy way to modify the turf's definition to make these changes because there's only one type, you'll have to define additional types and manually update maps.
Instead, you could have defined turfs like this from the start:
turf
tileset_1
water
icon_state = "water"
tileset_2
water
icon_state = "water"
tileset_3
water
icon_state = "water"
tileset_4
water
icon_state = "water"
tileset_5
water
icon_state = "water"
tileset_6
water
icon_state = "water"
Even though these all have the same icon, when you create the additional graphics for each tileset you can udpate these definitions as a way to easily update all maps.
4. Use areas to augment an object's constructor.
In Spies, windows are objs placed on the map. Each window needs to have its dir var set so it's facing outside. I could create four sub-types of window, one for each direction it can face. Sometimes this is a fine solution, but sometimes it's not. Instead, I can do this:
area
direction
icon_state = "direction"
layer = TURF_LAYER + 1
New()
..()
icon = null
north
dir = NORTH
south
dir = SOUTH
east
dir = EAST
west
dir = WEST
obj
window
New()
..()
var/turf/t = loc
var/area/a = t.loc
if(istype(a, /area/direction))
dir = a.dir
In the map editor I place the direction areas on the map to control what direction each window object will face. This makes the directions easy to change because I just have to change the turf's area (instead of digging around in the object instance editor). It also gives me a clear visual indication of what direction each window will have.
You can also use the turf's area to determine if something should or shouldn't happen. For example, Tiny Heroes adds black outlines around the edges of dense turfs. These are added at runtime so I don't have to manually add them in the map editor. However, checking all turfs to see which ones need to be outlined can take a long time.
I create an area called /area/outline and place it on all of the edges. At runtime, the game only checks turfs inside that area when adding outlines. This saves a lot of time and still has a minimal impact on how long it takes to make maps.
Another way you could apply this is with shadows that are generated at runtime. For example, in this image from a Dragon Warrior game, walls cast shadows. To the right of every wall tile is a shadow. You could place these on the map editor yourself, but that'll take a lot of time. Instead you can add shadows in the turf's constructor.
The shadows are easy to generate because they follow some simple rules. The problem is, what if there are exceptions to the rules? If there is a case where you want to manually place shadows on the map, you need to have a way to tell the turf's not to generate their own shadows. You can do something like this:
area
manual_shadows
turf
New()
..()
if(!istype(loc, /area/manual_shadows))
// generate shadows
That way you can place /area/manual_shadows on each tile you want to manually place shadows on.
You can't directly control how BYOND's map editor works, but this article shows that the map editor can be easy or difficult to use based on things that you do have control over. I gave a few examples here that I've found to be helpful, but I'm sure there are tons more. When you find that map editing has become a hassle, look for ways that you can code or define objects different to make map editing easier.
Maps can be very important to your game and you will often spend lots of time working on them. These tips will help you be more productive so you spend more time building and improving maps rather than spending all of your time editing the properties of a single object instance.
#11 will tie together ideas from multiple articles
#12 might be about design documents
After that I'm out of ideas. I've been trying to avoid writing articles about super specific topics. These articles are about how to program. They're not about how to implement a specific feature (though they contain specific examples), these articles are about larger, more general concepts that can be applied to whatever you're working on. Instead of writing an article about a specific topic I'd make a demo or library instead.
As always, ideas for more topics are very much appreciated.