LoginLogin
Might make SBS readonly: thread

Starting Out With Lowerdash Part 3

Root / Submissions / [.]

kldck_hulCreated:

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!

No posts yet (will you be the first?)