LoginLogin

Starting Out With Lowerdash Part 2

Root / Submissions / [.]

kldck_hulCreated:

Recalling that Last We Recalled Asteroids

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 learned what objects are
  • You learned that Modules and Instances are objects
  • You learned that women are not objects
  • You learned that objects have behavior and state

One Man's Junk...

Last time we found that the player's Ship had very well defined behaviors:
  • rotate left and right
  • shoot
  • thrust
For this part of the tutorial we'll be filling in the gaps left in our definition of a ship. The next tutorial will finish fleshing out the behaviors and state of the Ship Module; then we'll move on to actually having the player pilot an instance of it.

Far Out Man...

You may have noticed we glossed over some things going on with the Ship. Lets back up and look at those. One of the biggest question you should have is: How do we really know whether its here or there? Like, how do we know where any of us are, man? You might say the Ship is a "being" or "entity". Something that has a position in space and interacts with matter. All the other objects I pointed out in the game description also fit this description - those asteroids, flying saucers, and bullets all share common properties. Since this is a game about things floating through space, lets define these properties on a new type: SpaceJunk. SpaceJunk exists in our game's world; which is the graphical screen on your 3DS. SmileBasic already ships with a way to represent things on the screen natively - Sprites. But since this is a Lowerdash tutorial, we don't want to be dealing with management numbers and sprites everywhere. SpaceJunk will represent the wrapper for SmileBasic's built-in sprites, bridging the gap to our "type system". SpaceJunk is also a great place to implement the iconic screen wrap around Asteroid's has. When some junk reaches the edge of the screen it should continue moving from the opposite edge. This needs to happen every time the entity updates, and shouldn't be something we think about. SpaceJunk has:
  • X Position
  • Y Position
  • X Velocity
  • Y Velocity
  • A Sprite Management ID
SpaceJunk can:
  • move
  • be placed
  • hit other things
  • update
  • wrap from one end of the screen to the other.
Well, we have a lot of specialized knowledge about junk now, best embrace it... Start by making another blank file, and saving it as "_SpaceJunk". We'll make our Module, but this time with a few additions:
MODULE SpaceJunk
  MEM SP%
  MEM X#, Y#
  MEM VX#, VY#

  EXPORT DEF new(SPID%)
    me.SP% = SPSET(SPID%)
    RETURN me
  END

  EXPORT DEF move DX#, DY#
    me.X# = me.X# + DX#
    me.Y# = me.Y# + DY#
  END

  EXPORT DEF place X#, Y#
    me.X# = X#
    me.Y# = Y#
  END

  EXPORT DEF update
    me.X# = me.X# + me.VX#
    me.Y# = me.Y# + me.VY#

    SPOFS me.SP%, me.X#, me.Y#
  END
END

Woah! Woah... Woah... Woah Woah... A lot of stuff just happened.

I just threw a mostly complete Module at you; theres going to be a lot to take in. Lets try breaking it down slowly.
   MEM SP%
  MEM X#, Y#
  MEM VX#, VY#
This line tells our SpaceJunk Module that an Instance of it will have five member variables - the junks position, its speed and its sprite management number.
  EXPORT DEF new(SPID%)
    me.SP% = SPSET(SPID%)
    RETURN me
  END
This line sets the value of the variable SP% belonging to the object that me refers to. In this line, we are getting a new Sprite management number for the passed in Sprite Defintion ID. We store this management number as part of the me Instance because we want to keep it around for re-use later! We'll be able to directly access this sprite from any Module that is SpaceJunk through its SP% property too!
EXPORT DEF update
    me.X# = me.X# + me.VX#
    me.Y# = me.Y# + me.VY#

    SPOFS me.SP%, me.X#, me.Y#
END
This method will be what our game loop can call to keep this piece of junk updating while the game is running. Here, me is an Instance of SpaceJunk thats been returned from new. me.SP% refers to the management number we got in the constructor, and me.X% and me.Y% are the position that has been set through the move and place methods.

Junk Translocating

As I mentioned, SpaceJunk needs to wrap around the screen. For the purposes of this game, we'll assume we aren't changing any screen settings and using the default 400x240 resolution of the top screen. We want this wrapping behavior to apply whenever the SpaceJunk updates it's sprite - that is in the update method. Clamping a value to the screen is something we want to re-use, but don't need on every instance of a module. We can use whats called a "static function" to add a DEF to SpaceJunk thats always available.
  STATIC DEF wrapTo(V, X)
    IF V < 0 THEN RETURN X + V
    IF V > X THEN RETURN V - X
    RETURN V
  END
Now just call the wrapping function in the update method to enable the iconic effect:
  EXPORT DEF update
    me.X# = me.X# + me.VX#
    me.Y# = me.Y# + me.VY#

    me.X# = _.wrapTo(me.X#, 400.0)
    me.Y# = _.wrapTo(me.Y#, 240.0)

    SPOFS me.SP%, me.X#, me.Y#
  END

Adding "physics"

I mentioned one more action SpaceJunk should be able to do: hit other things. Let's add that now that we know the basics of whats happening. SmileBasic ships with a built-in collision check for its sprites, and we'll be using it. First, modify the constructor to turn on collision for this SpaceJunk's sprite:
  EXPORT DEF new(SPID%)
    me.SP% = SPSET(SPID%)
    SPCOL me.SP%, TRUE 'respect scale
    
    RETURN me
  END
Now, we want to check if there is a collision every frame of the game, and if so trigger the SpaceJunk's "getting hit" behavior:
  EXPORT DEF update
    ...
    SPOFS me.SP%, me.X#, me.Y#
    IF SPHITSP(me.SP%) != -1 THEN me.onHit
  END
We also need to add this "getting hit" behavior:
  EXPORT DEF onHit
    'Intentionally Empty
  END
Why did we make an empty method? When something that is SpaceJunk gets hit, it can have its own onHit defining a different reaction. This concept uses a feature called "Dynamic Dispatch" in Lowerdash. SpaceJunk without its own onHit will call this empty DEF and do nothing on collision. But if a Module using SpaceJunk has a different onHit method, the Lowerdash runtime will find it first during the update method and call it instead. We've completely defined a piece of SpaceJunk with the concepts that it is a moveable sprite, and can hit things. Go Ahead and Save this File! Completed:
MODULE SpaceJunk
  MEM SP%
  MEM X#, Y#
  MEM VX#, VY#

  EXPORT DEF new(SPID%)
    me.SP% = SPSET(SPID%)
    SPCOL me.SP%, TRUE 'respect scale

    RETURN me
  END

  EXPORT DEF move DX#, DY#
    me.X# = me.X# + DX#
    me.Y# = me.Y# + DY#
  END

  EXPORT DEF place X#, Y#
    me.X# = X#
    me.Y# = Y#
  END

  STATIC DEF wrapTo(V, X)
    IF V < 0 THEN RETURN X + V
    IF V > X THEN RETURN V - X
    RETURN V
  END

  EXPORT DEF update
    me.X# = me.X# + me.VX#
    me.Y# = me.Y# + me.VY#

    me.X# = _.wrapTo(me.X#, 400.0)
    me.Y# = _.wrapTo(me.Y#, 240.0)

    SPOFS me.SP%, me.X#, me.Y#
    IF SPHITSP(me.SP%) != -1 THEN me.onHit
  END

  EXPORT DEF onHit
    'Intentionally Empty
  END
END

Tying it Together

Now you may be wondering when we would be getting back to the player's Ship. We just covered a lot of ground, and you might want to take a moment to let it sink in; so I'll see you next time in: Part 3!

Not sure if I like the pacing on this one... Let me know what I can improve on! I just throw most of the important commands into one code block. But the length was really getting up there...