ID:120822
 
Be sure to check out:
Programming Tips #1 - If Statements
Programming Tips #2 - Making Progress
Programming Tips #3 - Design
Programming Tips #4 - Datums
Programming Tips #5 - Organization


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 different ways to write code to see how implementing a feature different ways can make game development easier.


Most games are just collections of simple features. Think about any BYOND game, even a complex one. Every feature in that game can either be implemented easily or it can be decomposed into a set of features which can all be implemented easily. Something that you think is complex (ex: a combat system) is really just a collection of simple features. A more complex combat system is just a larger collection of features.

If every individual feature is easy to write, why is game development hard? There are two reasons:

1. Games have lots of features. After decomposing features into basic, simple parts instead of saying "have a menu for the player to select attacks", you'd have things like "pressing the up arrow moves the cursor up in the combat menu." If you look at features on that level there are hundreds of them.

2. Features are related to each other. How you implement one proc complicates how you implement another one. You have to be aware of how things work, what procs are called, and what their return values are. As you add more features, there's more to remember, and this becomes difficult to handle.

In this article we're going to look at how you can structure code in different ways to make game development easier on yourself.


BYOND has a lot of built-in features and procs that make it easy to implement things. The problem is that BYOND's built-in features rarely do exactly what you need. There might be a built-in proc that is a 90% fit of what you need to do, but it's not a 100% fit. If every time you use a built-in proc you have to add a little code to make it work a certain way, every proc in your program will be a little more complex than it needs to be. This complexity will add up.

Instead of using BYOND's built-in features to implement your game, first use BYOND's features to implement the features your game will need, then use those features to make the game.

For example, you can use the oview() proc to find what mobs are near an enemy. You can use this in the enemy's AI routine to make it select a target. The problem is that oview() doesn't do exactly what we need - it'll return the list of all mobs near the enemy but not every mob is a valid target. We don't want the enemy to attack its friends or dead mobs. The oview() proc helps us implement this but it doesn't do everything we need. Here are two ways to implement this:

// first method:
mob
enemy
proc
ai()
var/mob/target
for(var/mob/m in oview(5, src))

// ignore dead and friendly mobs when looking for a target
if(m.dead) continue
if(m.team == team) continue

// if we have a target, only select m as the new target if its
// health is less than the current target's health
if(target)
if(m.health < target.health)
target = m
else
target = m

if(target)
attack(target)

// second method:
mob
enemy
proc
get_targets(r = 5)
var/list/targets = list()
for(var/mob/m in oview(r, src))

// ignore dead and friendly mobs when looking for a target
if(m.dead) continue
if(m.team == team) continue
targets += m

return targets

// override the enemy's get_target() proc to make it select the weakest mob
get_target(r = 5)
var/mob/target
for(var/mob/m in get_targets(r))

// if we have a target, only select m as the new target if its
// health is less than the current target's health
if(target)
if(m.health < target.health)
target = m
else
target = m
return target

ai()
var/mob/target = get_target(5)
if(target)
attack(target)

Both methods do the same thing using almost the same exact code. The first method does it all in one proc - it loops through nearby mobs, filters out non-targets, and selects the weakest one. The second method uses the get_target() and get_targets() procs as helpers. get_targets() returns a list of possible targets, get_target() picks the weakest one. Doing things the second way gives us a few benefits:

1. The code looks complex but it's better organized. Each proc has one clear task and its own name. If you do too much work in a single proc you'll have to remember what that proc does. If the larger function is split into smaller tasks and each task is put in its own proc, each proc gets a name that can accurately describe what it does.

2. Each function is in a separate proc so we can override individual parts. If you wanted to make the AI select the strongest enemy, in the first method you have to rewrite the whole thing. With the second method you'd just have to override the get_target() proc.

3. The ai() proc is way simpler. In a real project the ai() proc will likely grow to become the most complex of them all. Once it reaches a certain size it'll be hard to work with. If it takes you 11 lines of code to make super-basic AI you're limiting how much you can expand on it before it becomes too difficult to modify. In the second example the ai() proc is only three lines - you can easily add to it without it getting complex.


We're not doing this because the oview() proc is bad. We're just using it to create a proc that does exactly what we need and is easy to use. This keeps your code simple and easy to work with. As I said in the first "programming tips" article: " Good programmers don't effortlessly make sense of complex code, they know how to write simple-looking code to do the same thing." If you're making a complex game your code is going to be complex, it doesn't need your help. It's important to find ways to keep it simple from the start. If you don't, eventually the code will become very difficult to work with, progress will slow down, and you'll lose interest - this is probably why most BYOND games are never finished.


Here's another example, consider a basic "who" command:

// first method:
mob
verb
who()
for(var/mob/m in world)
if(m.client)
src << m

// you could also do it this way:
for(var/client/c)
src << c.mob

// second method:
var
list/players = list()

mob
Login()
..()
players += src

Logout()
..()
players -= src

verb
who()
for(var/mob/m in players)
src << m

The first method shows how BYOND lets you use for loops to easily find all of the players in the game. Most people use that type of approach because the second method requires a list that keeps track of players, but creating that list is hardly any extra work. Having a list of players simplifies the who command and many other things you might like to add:

1. What if there are AI players who should be listed too? The second method makes this easier because you just have to add them to the players list when they're created.

2. What if a single game server can support multiple game instances and you want to list players in your game instance, not all players on the server?

3. What if some live players are just observers and you don't want them to be listed, or you want them to be shown separately?

4. What if you want to have the game pick a player at random to be the leader who selects the game mode?

These are all features you may need to have in your game. By relying on the features that BYOND provides these things are a little more difficult to implement. By first implementing the feature you need (maintaining a list of players) these features are all very simple to add.

Lots of BYOND games lack polish because they skimp on these details. People focus on the basic functionality (ex: a combat system) but don't add many other details. The game ends up being more like a demo of the combat system than an actual game. By finding ways to make these features easier to add, you'll be able to add polish to your game more easily.


BYOND gives you some useful procs but using them directly can really complicate your code. By keeping your code simple and organized it'll be easier to work with and you'll be able to accomplish more. One thing that often makes a mess of your code is the interface.

BYOND doesn't do you any favors here. Screen objects and interface controls are great, but they're terrible to work with. It can take a lot of screen objects to create a decent HUD and updating one interface control can take many ugly calls using winset(), winget(), params2list(), list2params(), text2num(), and other procs.

Let's consider a basic game function and how it handles updating the interface:

mob
proc
attack(mob/target)
target.health -= 10

This attack() proc works fine, but what happens when we want to make it update the target's interface? It would seem strange to make my attack() proc update your interface directly. It'll also have problems if there are multiple attacks - in each place we modify your health we also need to update your interfance. We'll change it to this:

mob
proc
attack(mob/target)
target.damage(10)

damage(d)
set_health(health - d)

set_health(h)
health = h

Now we have a single proc, set_health(), that manages changes to your health var. From this proc we can handle updates to the interface:

mob
proc
set_health(h)
health = h
health_meter.icon_state = "health=[h]"

The health_meter object is a screen object we use to display an indication of your health. This code will work, but this still might not be the best way to handle it.

The set_health() proc is grouped with your attack and damage code because that's what it's related to. The problem is that it also updates the interface and it's not grouped with your interface code. Think about every feature that is represented by the interface - health, mana, money, skills, experience, etc. If the code to manage how money is displayed by the interface is with money-related code and the code to manage how the interface displays experience is with the experience/level up code, to update your interface you'll have to make changes all over the place. Instead, you can create procs to manage the interface and put them all in the same place:

Interface
var
mob/mob

New(mob/m)
mob = m

proc
set_health(h)
mob.health_meter.icon_state = "health=[h]"

mob
var
Interface/interface

client
New()
..()
mob.interface = new(mob)

// in other code files:
mob
proc
set_health(h)
health = h

if(interface)
interface.set_health(h)

This gives us two benefits:

1. All of the interface-related code is in the same place. If you want to change the game to use interface controls instead of screen objects you just have to make changes in one place.

2. The code to update the interface is separated from the code that triggers the update. You can make the interface more complex (ex: make the health value displayed using many screen objects instead of just one) and the mob's set_health() proc stays the same, it's just the interface code that changes. The code that calls the interface object's procs stays simple, even if you use tons of complex calls to winset() and winget().


Sometimes BYOND's features do exactly what you need, but usually that's not the case. Usually they're a close fit but not a perfect fit. Instead of using these slightly-misfitting features to create your game, use BYOND's features to implement the features you need, then use the features you implemented to create your game. By implementing the exact feature you need you simplify the code that uses the feature.

Doing this doesn't always take more work. Look at the first code example in the article - the second method has almost exactly the same code as the first example, it's just split up into separate procs. You're not doing extra work, you're just changing how you think about it and how things are organized. You can even see this in the last example. The two lines of code (setting health = h and updating the screen object's icon_state) are present in both cases, it's just a difference in organization.
As always, if people have ideas for additional topics I'd like to hear them. I have an article about whitespace and comments that's almost done but after that I'm out of ideas.
What about an article on "Bad Programming Practices"? Since most newbies do them.
Hashir wrote:
What about an article on "Bad Programming Practices"? Since most newbies do them.

Do you have any specific "bad programming practices" in mind?
Forum_account wrote:
Hashir wrote:
What about an article on "Bad Programming Practices"? Since most newbies do them.

Do you have any specific "bad programming practices" in mind?

Using goto.
I dig it. +yea :)
ExPixel wrote:
Using goto.

I think it's more about abusing goto rather than using goto. There are those few cases where the use of goto, rather than other statements, is justified.
I'm not sure what to say about goto aside from "don't use it" =)
D4RK3 54B3R wrote:
ExPixel wrote:
Using goto.

I think it's more about abusing goto rather than using goto. There are those few cases where the use of goto, rather than other statements, is justified.

Still bad practice in most cases.
Forum_account wrote:
I'm not sure what to say about goto aside from "don't use it" =)

Tell them why.
Forum_account wrote:
As always, if people have ideas for additional topics I'd like to hear them. I have an article about whitespace and comments that's almost done but after that I'm out of ideas.

a shortening and cleaning code article.
The problem is that using goto is never obvious. If I had an example showing "here's a case where someone used goto, here's how you should do it instead", everyone will look at the first example (the one using goto) and think "why the heck would someone do it that way?"

Maybe I can find a way to tie it in with another topic.
A few ideas: AI, fuzzy logic in AI, interface design, modular programming practices, ways to setup flexible HUD systems, using math to create different physics effects like arcs in movement or bouncing off walls, preventing feature creep, how to write a game design document, using the interface elements to eliminate the need for statpanels, designing intuitive control schemes(without lists of verbs or statpanels).
Good luck, and great article.
Forum_account wrote:
The problem is that using goto is never obvious. If I had an example showing "here's a case where someone used goto, here's how you should do it instead", everyone will look at the first example (the one using goto) and think "why the heck would someone do it that way?"

Maybe I can find a way to tie it in with another topic.

You don't use goto when you can use a loop to solve the issue perfectly fine.
KetchupKid wrote:
A few ideas: AI, fuzzy logic in AI, interface design, modular programming practices, ways to setup flexible HUD systems, using math to create different physics effects like arcs in movement or bouncing off walls, preventing feature creep, how to write a game design document, using the interface elements to eliminate the need for statpanels, designing intuitive control schemes(without lists of verbs or statpanels).

Thanks for the ideas!

I'm trying to avoid getting into very specific topics, but I'll have to get into more specific topics eventually. I can also use some of these things as examples in articles, even if these things aren't the actual topic of the article.

I'm trying to write an article about design documents but I have no idea what to say. People put emphasis on the "document" part of "design document" but that's the part that doesn't matter at all. Having a document just means you wrote ideas down. It's the ideas that matter, not the document. Writing ideas down is just a way to organize them and while I've written articles about how to organize code, writing about how to organize ideas seems silly.

ExPixel wrote:
You don't use goto when you can use a loop to solve the issue perfectly fine.

Yeah, I'd probably mention goto briefly in an article about loops.
ExPixel wrote:
Still bad practice in most cases.

Never said it wasn't bad practice. I agree with that. The cases where goto is justified can be a bit too complicated to explain to the typical BYOND programmer, though. This is why I haven't really been pushing the point; It may be futile to try to explain it in the first place and may be better to just say "Don't use it."

Pretty much, there are a few algorithms and code structures that are very difficult to implement sans goto. Lummox says it best.
D4RK3 54B3R wrote:
ExPixel wrote:
Still bad practice in most cases.

Never said it wasn't bad practice. I agree with that. The cases where goto is justified can be a bit too complicated to explain to the typical BYOND programmer, though. This is why I haven't really been pushing the point; It may be futile to try to explain it in the first place and may be better to just say "Don't use it."

Pretty much, there are a few algorithms and code structures that are very difficult to implement sans goto. Lummox says it best.

I understand that but the cases where it is necessary are few and that should be understood at some point by most of the programmers here.
KetchupKid wrote:
A few ideas: AI, fuzzy logic in AI, interface design, modular programming practices, ways to setup flexible HUD systems, using math to create different physics effects like arcs in movement or bouncing off walls, preventing feature creep, how to write a game design document, using the interface elements to eliminate the need for statpanels, designing intuitive control schemes(without lists of verbs or statpanels).
Good luck, and great article.

There's actually already a pretty good article on how to implement fuzzy logic into AI on BYOND.

I'm presently working on a library for flexible HUD systems (through images or client.screen) for my current project. I'm seeking to reproduce almost all .dmf functionality with this library. I may or may not release it; I'm fairly conservative when it comes to releasing my libraries. Maybe Forum_Account should make one :)

I think there was an article a while back on vector math and implementing vector math in a physics system. I believe it featured this vectors library, but it was from a long time ago so I don't remember the specifics.
Really helped, thanks.
I also have an AI demo. The basic physics stuff is handled by the pixel movement and sidescroller libraries. Just multiply an object's velocity by -1 to make it bounce when it hits a wall:

// using the Pixel Movement library:
mob
bump(atom/a, d)
if(d == NORTH || d == SOUTH)
vel_y *= -1
else
vel_x *= -1


I've thought about making a HUD library but I'm not sure what it'd contain. HUDs are specific to each game so it seems like something each game developer will have to write themselves. Screen objects can be annoying to deal with but I'm not sure what features I'd add to them or how I'd make them easier to use. I'll have to think about it some more.
Page: 1 2