Motivation
Hey guys, its been a while since I've posted here. With SmileBasic BIG getting a release, and the talk of more platforms in the future, its still a great time to start developing an ecosystem for game development. I created Lowerdash for the purpose of building libraries and distributing them, hoping to make SmileBasic game development easier and faster.
But maybe you've never heard of Object-Oriented programming, or the awesome power it can bring? Maybe you're just starting out programming and Lowerdash is that thing that was too hard to learn? Then these tutorials are for you.
In this series, I will attempt to teach how to use Lowerdash and build a foundation for learning object-oriented programming techniques. This first tutorial focuses on what OOP is, and later tutorials will have you building a basic Asteroids clone.
OOP OOP!
sorry, not sorry
So what is all the fuss about anyway? What are objects and why do I want them?
OOP is a style of programming that breaks down the things that need to happen into your game into smaller, isolated pieces that are easier to think about. Each piece can run without the rest of the program, and can even be used in other games or libraries. We call these pieces objects.
Why Not Spaghetti?
As I mentioned, OOP is about creating reusable code. Code that gets used a lot of places needs to maintainable - that is understandable, adaptable, and extendable.
If you're serious about making a good game, you're not going to release it on the world and never fix a bug or add new features. If your program then contains lots of copy/pasted code, you open yourself up to not fixing it everywhere, or having things work in slightly different ways by the time you're done.
Think about how you re-use
DEFs and
Labels and the convenience they add. OOP is that, after some rare candies.
As an example:
Consider the code:
var monsterName = ['Goblin', 'Dragon', 'Goblin']
var monsterHealth = [20, 300, 18]
This is a common pattern, using IDs to reference data in an array.
Assume when a monster is killed, you call a function to remove it from the arrays,
slayBeast(index)
This code can be problematic.
First, because you have to make sure any variables referencing monster have been updated to point to the new array indexes.
And second, What would happen if you now decided that all monsters should be able to cast spells, and now need a mana pool?
slayBeast and anywhere else that modifies arrays now needs to be updated to also remove the monster from
monsterMana. If it doesn't, you might find your Goblin casting Doomsday a little too often.
Getting Set Up
I'll start off by showing the basic Lowerdash building blocks in context. If you want to follow along, you'll need to get Lowerdash set-up. You'll want to have a new project with the required Lowerdash files copied into it.
If you haven't yet, see
Setting Up A Lowerdash Project for instructions. Writing our imports and main entry point are part of this tutorial, so you can skip the last two steps.
Now, you'll want to create a blank file and save it as "_SHIP".
Pro Tip: For organization's sake, I always put a "_" before the name of any file containing Lowerdash Modules. I recommend adopting this or another convention for when your projects get large.
Orienting to Objects
In Lowerdash, objects take two forms:
Instances and
Modules. I'll try to illustrate both.
An
Instance is an object that is created from a
Module using the
new command:
VAR mySpacecraft$ = new ZClassCruiser()
you don't need to copy this
Using
new creates a new object using the "blueprint" of a Z Class Cruiser and returns it. Here we store it in an ordinary variable. In Lowerdash, objects are returned as "pointers". The variable type $ is a
pointer; in Lowerdash, it can point to Objects
or Strings.
As I mentioned, the aforementioned
Module can be thought of as a blueprint. A Module encapsulates the functions and variables used by something of its "type". When using
new, the
module describes the materials (variables) needed to assemble the type of object it describes.
In other languages, a
Module might be called a
Class, except in Lowerdash, Modules are also objects ( "singletons"). They can behave very similar to Instances without ever needing to create Instances.
Lets create a
Module for our Z Class Cruiser. "ZClassCruiser" is a little long to be typing on the 3DS, so lets creatively rename this module to "Ship"!
Note: Anytime you see text like
Ship, it denotes a
Module or "type" of object.
We'll write the following code in the "_Ship" file:
MODULE Ship
END
Now when somebody calls the
new command with your module, Lowerdash will actually try to call a specific function inside the
module called
new.
Lets add that function:
MODULE Ship
EXPORT DEF new()
RETURN me
END
END
This
DEF has
EXPORT before it. That means its going to the "class table", or in our blueprint metaphor, being drawn on the plans for this Module's Instances. EXPORT DEFs are made available to
Instances and are often called the
methods an object has.
new is what is called the "constructor function" for the
Ship module. As I mentioned, when you create a new Instance of a Module, this function is called. When that happens, the variable
me is set to a
new Lowerdash object Instance. In this code, we RETURN
me because we intend to store our object in a variable somewhere.
Rhetorical Devices
Now that we know what an object looks like in Lowerdash, lets get into what makes one up. Since an object is expected to be an isolated piece, it is considered to have a
State and
Behaviors separate from the rest of the program.
The
State is how you would describe the object - often using
is or
has as descriptors. A spaceship
has an ion cannon and an alien
is out of shields.
The
Behavior of an object is what it can
do - often in the form of a verb. A spaceship
scans for aliens,
fires its thrusters, and
shoots its ion cannon.
Solid State
You may have seen the object-oriented example with cars and adding tires or something to them. To illustrate state, we're going to do something similar and add an ion cannon to our spaceship.
In Lowerdash, we can write:
MODULE Ship
MEM gun$
EXPORT DEF new()
me.gun$ = new IonCannon()
RETURN me
END
END
Two things are happening here:
- We added a MEM declaration to the top of our module. MEM adds whats called a "member variable" to the class. Every object created in new will have a unique copy of every member variable.
- We set me.gun$ in the constructor. me is an object and we can lookup variables on it using the "dot" operator.
Any given
Ship instance now owns an
IonCannon instance.
An Ongoing Example
Now that I've hopefully laid some groundwork on what OOP is, I want to motivate its use through an example. For the rest of this series, we are going to be building an Asteroids clone! What a great way to re-use our
Ship module!
Asteroids is a great example, because it contains a very small number of describable "pieces".
Wikipedia describes the game as:
The objective of Asteroids is to score as many points as possible by destroying asteroids and flying saucers. The player controls a triangular-shaped ship that can rotate left and right, fire shots straight forward, and thrust forward. As the ship moves, momentum is not conserved – the ship eventually comes to a stop again when not thrusting.
You'll notice several distinct "things" that are being described in this paragraph. There are asteroids, flying saucers, a player controlled ship, and even bullets. The description goes farther and tells us about the
behavior of the player's ship: it can rotate left and right, shoot, and thrust.
Let's add those behaviors to
Ship. Remember, we can add methods to a module with
EXPORT DEF:
MODULE Ship
MEM gun$
EXPORT DEF new()
me.gun$ = new IonCannon()
RETURN me
END
EXPORT DEF shoot
END
EXPORT DEF rotate M#
END
EXPORT DEF thrust M#
END
END
We're at the end of this first part. Go ahead and save this file!
Think about how you might implement the code behind these methods. The next tutorial will have us fleshing out the player's
Ship Module.
On to Part 2!