Recalling that we didn't actually write the Ship code
This is a continuation of the
Starting Out With Lowerdash Tutorial. In this tutorial series we are building an Asteroids clone using Lowerdash.
Previously on Starting Out With Lowerdash...
- You created the "_Ship" file and Module
- You created and fleshed out the "_SpaceJunk" file and Module
Get back to the Spaceship!
Last time, we built a Module called
SpaceJunk to represent everything that is floating in our game's representation of space. Now, we want our player's
Ship to have all the same behaviors and properites as a piece of junk, and hopefully do a little more. In OOP, this is called
inheriting from
SpaceJunk.
In Lowerdash, you can attach the properties of one Module to another by setting it as the prototype of that other Module using
PROTO.
A prototype is a link to another Module that is followed if a property isn't found on the Module itself. This might be familiar if you've ever used JavaScript. When the constructor is called, Lowerdash will search for
MEMs declared at any level in the
prototype chain.
Lets modify our "_Ship" file. We'll need to IMPORT the "_SpaceJunk" file if we want to reference the Module in it:
IMPORT "_SpaceJunk"
MODULE Ship PROTO SpaceJunk
...
END
What good is extending the blueprint for a ship if we don't build those parts? Lets modify
Ship's constructor to call the constructor for
SpaceJunk as well:
EXPORT DEF new()
DO proto.new, 3299
me.gun$ = new IonCannon()
RETURN me
END
The
DO command will call a function and discard its return value. Here we call the prototype's constructor, which will return the same object that
me is already set to, so there is no reason to keep track of it.
We left out the
member for the
Ship's
state:
...
MODULE Ship PROTO SpaceJunk
MEM HP%, ANG%
...
Something the description misses is that, like any game, our ship needs to have health. Our ship also cares how its rotated.
We'll set the
Ship's HP in the constructor and we won't construct a new gun. In the next part, we'll have our game switch the player's weapon for us.
We also want to set some common values:
VAR ThrustPower# = 5.0
VAR MaxThrust# = 20.0
VAR DecelRate# = 0.6
These VARs are nested inside a MODULE block. Thats means they'll be namespaced under it: Ship_ThrustPower#, Ship_MaxThrust#, etc. You can also lookup these values on
Ship or
me - e.g
Ship.MaxThrust#. In Lowerdash, theres a special placeholder for the current Module:
_. I'll use the lower dash in my examples.
Adding Physics
Now we need to fill in all the
methods we left empty on
Ship:
MODULE Ship PROTO SpaceJunk
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
Ship has very few DEFs in it. Most of them do standard vector math and let
SpaceJunk do the rest of the work.
Since this is a Lowerdash tutorial, I'm not going to spend much time on why some of this code is the way it is. I'd love to elaborate in the comments!
With the skeleton methods filled in, the completed Module will look like:
IMPORT "_SpaceJunk"
MODULE Ship PROTO SpaceJunk
MEM HP%, ANG%
MEM gun$
VAR ThrustPower# = 5.0
VAR MaxThrust# = 20.0
VAR DecelRate# = 0.6
EXPORT DEF new()
DO proto.new, 3299
me.HP% = 5
RETURN me
END
EXPORT DEF thrust
'Convert from degrees to radians
VAR ANG# = RAD(me.ANG%-90)
'Get the components of the thrust
'in the range [-MaxThrust, MaxThrust]
me.VX# = MAX(MIN(_.ThrustPower# * cos(ANG#), _.MaxThrust#), -_.MaxThrust#)
me.VY# = MAX(MIN(_.ThrustPower# * sin(ANG#), _.MaxThrust#), -_.MaxThrust#)
END
EXPORT DEF rotate M#
me.ANG% = me.ANG% + M# * _.ThrustPower#
SPROT me.SP%, me.ANG%
END
EXPORT DEF shoot
'If there is a gun to fire
IF LEN(me.gun$) THEN
'We want to fire the bullet in the same direction we are facing
me.gun$.shoot me.X#, me.Y#, me.ANG%
END
END
EXPORT DEF update
'decelerate
IF abs(me.VX#) > 0 THEN
me.VX# = me.VX# * _.DecelRate#
ENDIF
IF abs(me.VY#) > 0 THEN
me.VY# = me.VY# * _.DecelRate#
ENDIF
proto.update
END
END
You'll notice that in
update we call the
update method on the prototype when we're done. This will chain back to
SpaceJunk and make sure our sprite moves and any collisions happen. I made sure to call this last, since this needs to be done after any calculations for position.
Pro Tip: When you haven't assigned an object to a pointer, or Lowerdash has deleted the object backing a pointer pointed to,
that pointer is set to the empty string. You can check for null values with
LEN()
A Short One
Now we have a completed
Ship module. It can do all the things we expect a ship to do, and has all the properties we'd expect from a real life Spaceship! (Give or take a few...)
Next time, we'll be writing our game loop, and defining how our pilot flies their
Ship.
Onward to Part 4!