Creating the objects

Now that there's an overall plan, it's time to start making that plan a reality and creating some actual code in a project we can run! As important as planning is, there's no denying that the exciting part of the project is watching the things you've been working on come to life and actually show up on the screen.

Getting ready

Using events in Corona requires tying your conceptual objects to specific types of Corona objects that support events. There are three types of such objects by default: display objects, storyboard scene objects, and the global Runtime object. (It's possible to create your own, but that's not something we're discussing at this point.) The creature objects are visible on screen and need to receive touch events, so it's logical to represent them with display objects. The world needs to hold a bunch of creatures and give them spatial relationships, so using a display group for it makes sense.

The game doesn't have a direct correspondence to anything on the screen, although it's responsible for showing the contents of the world as well as the display of the score. We'll make it a scene, because it has much the same life cycle and because we can use its associated display group to hold both the world and, later, the interface group.

Getting on with it

When you start the Corona Simulator, the splash screen asks whether you want to create a new project, launch the simulator, view your Corona Dashboard for info on apps you've released, or review demos and sample code.

  1. Click on the icon for New Project; the pop-up screen that you take next changes slightly depending whether you are running the simulator on Windows or Mac OS X:
    • On a Mac, enter the project name and select Scene from the template list. Leave the other two entries (size and default orientation) at their defaults and select the Next button. Choose a location for your new project file, then select Show in Finder from the last pop-up.
    • Under Windows, the dialog is somewhat more compact. Enter the project name and select Multiscreen Application. Use the Browse button near the upper-right corner to select whatever project directory you want to use. The OK button will open your project in both Windows Explorer and the Corona Simulator.
  2. Once a window is showing the project contents, find the file called scenetemplate.lua and make a copy of it. Name the copy game.lua.
  3. Open the file main.lua in the project folder using the text editor of your choice. Find the last line, where it says storyboard.gotoScene( "scenetemplate" ), and change it to storyboard.gotoScene( "game" ).
  4. Save this file. This changes the file which the project will look in for its initial screen content.

Loading art assets and libraries

Download the project pack, if you haven't already:

  • Copy the images directory and its contents into your project folder. This includes both the sprite sheet for our creatures and the background graphics for our world, as well as some images we'll use later to create our splash screen.
  • Also, copy the files world.lua and bat.lua from the version 1 subfolder into your directory. We'll include these files in the main game file and discuss their contents later.

Loading the world

Open the file game.lua. This file is prepopulated with the skeleton of a storyboard scene, which we'll fill in with code to run the game, and supplement with listener functions to respond to world events.

Find the block for the scene:createScene function and start replacing the comment that talks about inserting your own code with several new lines:

display.newRect(group, 0, 0, display.contentWidth, display.contentHeight):setFillColor(0, 0)

  self.World = require "world" {
    Backdrop = "images/exterior-parallaxBG1.png",
    Tile = "images/wall.png";
    Inhabitants = {
      bat = require "bat"
    }
  }
  group:insert(self.World)

This loads and calls the world.lua module, which generates a new function that constructs a world object with the specified options.

Note

Depending on how much Lua programming you've done, the syntax of the require call may or may not be familiar to you; when a name or expression is followed directly by a string literal or table constructor, Lua attempts to call the value of the name or expression (assuming it is a function) with the string or table as its only argument. So the name require followed by the literal world is equivalent to the function call require "world". Since require returns whatever is returned by the module it loads, and world.lua returns a function (more on this in a bit), the require "world" call itself ends up being equivalent to a function, and followed by the table constructor, the {braces}, and their contents, it becomes a call to that function using that table. The table specifies graphics files to be used by the new world object for its presentation, as well as a list of creatures that the world needs to be able to include and create.

Then, we take the newly created world object and add it to the scene's display group so that it will appear on the screen and be hidden or deleted properly when our game leaves or purges the scene.

Linking the game with the world

Finally, we establish some event trigger relationships between the game object and the world object that it has created:

  group:insert(self.World)

  self.World:addEventListener('Death', self)
  self.World:addEventListener('Despawn', self)

  self:addEventListener('Game', self.World)
end

As specified previously, the game object will listen to the world for events where a creature has been defeated or has otherwise despawned, so that it can determine when the player scores and when the game should end. It also registers the world object to receive game-related events such as the game beginning and ending. Note that the game has no idea how the world object will respond to these events, or even if it will respond at all. It simply makes sure that the world will be notified when these things happen.

Loading a new game into the display

The createScene event is dispatched to scenes only when they are first loaded or if their displays have been unloaded to save memory. When the scene actually begins or is reloaded from a different scene, the createScene event might be skipped, but the enterScene event always fires.

  1. We'll go down to the next event responder, and replace the contents of that function with code that actually starts the game:
    function scene:enterScene( event )
      self.ScoreTotal = 0
    
  2. When a game starts, the player's score should always be reset to 0:
      self.ScoreTotal = 0
      self.StartingCount = 1
      self.Count = self.StartingCount
    
  3. While the design calls for 30 creatures traversing the screen in turn, first we want to make sure that the basic mechanisms we're creating work. So to start, we will create only one bat so that we don't have to wait for the whole scene to finish in order to try again. While the game only needs to track the remaining count of creatures, we remember the total count we were going to spawn for the game, so it can be stored with high-score information easily:
      self.Count = self.StartingCount
      self:dispatchEvent{name = 'Game'; action = 'start', duration = (self.Count + 1) * 1500}
    

    This is our first custom event that we trigger! This notifies anyone who's listening (like, possibly, the world) that a game is about to begin, and that it's expected to last a certain number of milliseconds. Many games will not have a fixed duration field, but we want to provide an estimate of the game time so that the scrolling parallax background will move continuously throughout the game. This code allows a second and a half for each bat (since that's how often they'll be released), plus another second and a half for the last bat to cross the screen.

    Note

    Notice that the event's name starts with a capital letter. All Corona built-in events have names beginning with lowercase letters; using uppercase letters to start the names of our own custom events ensures that we won't have any naming conflicts with Corona, and also makes it easy to recognize basic events from our own.

Preparing the game challenges

Once we've triggered the game start, we prepare the bats to be dispatched:

  self:dispatchEvent{name = 'Game'; action = 'start', duration = (self.Count + 1) * 1500}
  for i=1,self.Count do
    local x, y = 0, display.contentHeight,
    timer.performWithDelay(i * 1500, 
      function(...) 
        self.World:Spawn{kind = 'bat', x = x, y = y, 'brown'} 
      end
    )
  end
end

This loop prepares the creation of as many bats as the game level is supposed to release. Right now, it will generate only one, because the StartingCount event has been reduced to 1 for test purposes; but increasing that number will automatically schedule more bats every second and a half.

The timer.performWithDelay pattern is probably a familiar one; it creates a new temporary function on each pass through the loop, which will call another function with the parameters specified in that pass through the loop. The x and y values specified are currently fixed for testing purposes, but later we will add some variety so that the bats do not all follow the exact same line.

Responding to world changes

Finally, we go up to the top of the scene file after the storyboard.createScene() call, and define one more function of the scene object. The scene has been registered as a listener for Despawn events on the world, so we need to explain how the game should handle those events when they occur:

local scene = storyboard.newScene()

function scene:Despawn(event)
  if not tonumber(self.Count) or self.Count <= 1 then

Note

When a table (which includes scene and display objects) is registered as a listener on some event target, it should have a field name matching the event name (including matching case; Lua is case-sensitive), containing a function that will process those events. If it does not contain such a field when the relevant event occurs, it will simply be ignored until the next such event (later, we will use this deliberately). The game is concerned about these events because once all the bats it directed the world to spawn have despawned, the game is complete.

Monitoring game progress

The first thing we do when a creature despawn occurs in the world is check how many more despawns we're expecting. As a sanity check to avoid errors, if the count of remaining creatures is missing or not a number, we also assume that the game is over:

  if not tonumber(self.Count) or self.Count <= 1 then
    self:dispatchEvent{name = 'Game'; action = 'stop'}

Concluding the game

If the count of creatures still waiting to spawn isn't more than one, indicating that there are extra bats still waiting their turn, then the game is over (this count reduces by 1 for each bat that spawns). We dispatch a Game event with the stop action to notify interested listeners (like the world) that the game is over:

    self:dispatchEvent{name = 'Game'; action = 'stop'}
    os.exit( )

Once other game elements have performed end-of-game cleanup, we leave the game. Eventually, we will want to return to the menu screen and submit our score to the high-scores table, but that module doesn't exist yet:

    os.exit( )
  else
    self.Count = self.Count - 1
  end
end

If the game is still waiting for more than one creature to pass, all we need to do is reduce the count we're waiting for by one to account for the count that just passed.

The game module is now ready to test! Before we finish, we can just go down towards the bottom of the file, and remove the skeleton functions for exitScene and destroyScene, as well as the scene:addEventListener calls for them. They will not be needed for this module in this project.

Understanding your libraries

It's worth examining the world.lua and bat.lua modules briefly, because they implement the rest of the critical event chain that we designed in the second step. Notice in world.lua how the world constructor function adds a function to the world object called self:Game(event). Since the game registered the world as a listener for Game events by itself, this function will be called automatically when the game object sends a start or stop event.

The world object also provides the Spawn function that the game calls to create new bats, and one of the things it does there is add itself as a listener for Death and remove events on the newly spawned creature. It responds to Death events by reposting them to itself, and to remove events by posting Despawn events for the game to pick up.

The bat.lua module has a couple of points of interest. We've covered in the abstract how custom events can be substituted for physical events; the bat illustrates this directly, by publishing a Death event to itself when it is tapped. It then also unregisters itself for future tap events so that you can't repeatedly tap dead bats for extra score.

Note

Notice that the bat object registers for its own Death event, as well as the world registering for it. It uses this so that it can separate the cosmetic reaction to its death (currently very simple) from the logical events required to make it happen. It also means that it will respond properly even if something else posts a Death event to it from outside.

What did we do?

At this point we have a rudimentary test game that illustrates the flow of events up and down the object layers. For the sake of brevity, we imported existing modules to support two of the three layers we need to deal with, and constructed the third one to control and respond to the middle layer. The game layer does not ever interact with the individual bat objects, honoring the programming principle called encapsulation or weak coupling .

So far, the game will run a single bat across the screen and then quit. If you manage to tap the bat, it will fade out but finish its trip across the screen. Obviously, this is not a very satisfactory game for the work needed to produce all these files (assuming that you had to write all three of them yourself). However, the next few tasks will show us how comparatively easy it is to incorporate these features into the robust infrastructure we have established.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.45.162