Quantcast
Channel: Envato Tuts+ Code - Mobile Development
Viewing all articles
Browse latest Browse all 1836

Create a Collect-the-Pieces Game with Corona

$
0
0
tag:code.tutsplus.com,2005:PostPresenter/mobile-22400

In this tutorial, I will show you how to create a collect-the-pieces game using the Corona SDK and the Lua programming language. We will explore how to use touch controls and events, create shapes using the graphics API, and we will also make use of physics collisions and timers. Let's get started.


1. Application Overview

In this game, the player will be able to control a puck on the screen to collect other pieces with the same color. The player has only a limited time to collect as many pieces as possible. If the player touches a piece from another color, the game is over.

App Overview
This application uses the Graphics 2.0 Library. If you're using an older version of the Corona SDK, you may run into problems. Consult the migration guide for more information.

In this project you will learn the following skills:

  • physics collisions
  • create text fields
  • user timers and images
  • how to use touch controls and events
  • create shapes using the new Graphics API

2. Target Device

Target Device

The first thing we have to do is select the platform we want to run our application on. This enables us to choose the correct sizes for the artwork that we'll use.

The iOS platform has the following requirements:

  • iPad 1/2/Mini: 1024px x 768px, 132 PPI
  • iPad Retina: 2048px x 1536px, 264 PPI
  • iPhone/iPod Touch: 320px x 480px, 163 PPI
  • iPhone/iPod Retina: 960px x 640px, 326 PPI
  • iPhone 5/iPod Touch: 1136px x 640px, 326 PPI

Since Android is a more open platform, there are many devices and possible resolutions. A few of the more common ones are listed below:

  • Asus Nexus 7 Tablet: 800px x 1280px, 216 PPI
  • Motorola Droid X: 854px x 480px, 228 PPI
  • Samsung Galaxy SIII: 720px x 1280px, 306 PPI

In this tutorial, we'll be focusing on the iOS platform and the iPhone and iPod Touch in particular. However, the code used in this tutorial can also be used for the Android platform.


3. Interface

Interface

We'll use a simple and friendly interface with multiple shapes, buttons, bitmaps and more. The artwork for the interface can be found in the source files of this tutorial.


4. Export Graphics

Export Graphics

Depending on the device you've selected, you may need to convert the graphics to the recommended resolution (PPI), which you can do in your favorite image editor. I used the Adjust Size… option in the Tools menu of the Preview application on OS X. Remember to give the images a descriptive name and save them in your project folder.


5. Application Configuration

We'll use a configuration file, config.lua, to make the application go full screen across devices. The configuration file shows the original screen size and the method used to scale the content in case the application is run on a different resolution.

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = 'letterbox'
    },
}

6. main.lua

Let's write the actual application. Open your preferred Lua editor. Any plain text editor will work, but it is recommended to use a text editor that has syntax highlighting. Create a new file and save it as main.lua in your project folder.


7. Code Structure

We'll structure our code as if it were a class. If you're familiar with ActionScript or Java, you should find the project structure familiar.

Necessary Classes

Variables and Constants

Declare Functions

    constructor (Main function)

    class methods (other functions)

call Main function

8. Hide Status Bar

display.setStatusBar(display.HiddenStatusBar)

This code snippet hides the status bar. The status bar is the bar at the top of the device's screen that shows the time, signal, and other indicators.


9. Default Anchors

Setting the display's default anchors is useful if you're porting an application from the previous Graphics library, that is, the projects you've created with previous version of the Corona SDK. Since the release of the Graphics 2.0 library, the reference point of every image has changed from its top-left to its center. To change this in every image that you use in your project, we can invoke the setDefault method and pass a value from 0.0 to 1.0, with 0.0 being the left if you change the x anchor and the top if you change the y anchor.

display.setDefault('anchorX', 0)
display.setDefault('anchorY', 0)

10. Import Physics

We'll be using the physics library to handle collisions. Import and start the library using the code snippet shown below.

-- Physics

local physics = require('physics')
physics.start()

11. Background

Background

A simple background for the application's user interface. The code snippet below draws the background to the screen.

-- Graphics
-- [Background]
local gameBg = display.newImage('gameBg.png')

12. Title View

Title View

This is the title view. It's the first interactive screen to appear in our game. These variables store its components.

-- [Title View]

local title
local playBtn
local creditsBtn
local titleView

13. Credits View

Credits View

The credits view shows the credits and copyright of the application. The creditsView variable is used to store it.

-- [CreditsView]

local creditsView

14. Score Text Field

Score

We'll create a text field for showing the player's score a bit later in this tutorial. We store a reference to this text field in the scoreTF variable.

-- Score TextField

local scoreTF

15. Pucks

Pucks

The pucks in the game are created and distributed randomly on the stage. The pucks group will be used to group them so that we can manipulate them easily.

-- Pucks

local pucks

16. Alert

Alert

The alertView variable keeps a reference to the alert view that is displayed when a puck of the wrong color is touched by the player. The alert view will show the player the game is over.

-- Alert

local alertView

17. Sounds

Sounds

We'll use sound effects to give the game extra character. The sounds that I've used in this project were obtained from as3sfxr. You can find the background music on Play On Loop.

-- Sounds

local bgMusic = audio.loadStream('POL-sky-sanctuary-short.mp3')
local blip = audio.loadSound('blip.wav')
local wrong = audio.loadSound('lose.wav')

18. Variables

The next code snippet lists the variables that we'll use in the game. The totalPucks variable stores the number of pucks that are placed on the stage, timerSrc keeps a reference to the game's timer, and time references the rectangle that shows the remaining time.

-- Variables

local totalPucks = 20
local timerSrc
local time

19. Declare Functions

We declare the functions as local at the very beginning. In Lua, you can forward declare a function by declaring its name before implementing the function's body. This makes it easier to keep track of the various functions that we'll use in this project.

-- Functions

local Main = {}
local startButtonListeners = {}
local showCredits = {}
local hideCredits = {}
local showGameView = {}
local gameListeners = {}
local createPuck = {}
local movePuck = {}
local reduceTime = {}
local alert = {}

20. Constructor

Let's start by creating a stub implementation for the function that will initialize the game logic, the Main function.

function Main()
end

21. Add Title View

Next, we draw the title view to the screen and add a tap listener to each button. The newImage method is used to load the images and display them on the screen using the positions passed to the function. We also create a group named titleView that serves as a container for the newly created elements.

function Main()
  titleBg = display.newImage('titleBg.png')
  playBtn = display.newImage('playBtn.png', 220, 168)
  creditsBtn = display.newImage('creditsBtn.png', 204, 230)
  titleView = display.newGroup(titleBg, playBtn, creditsBtn)

  startButtonListeners('add')
end

22. Start Button Listeners

In startButtonListeners, we add the event listeners to the title view's buttons. When the play button is tapped, we show and start the game. When the credits button is tapped, we show the game's credits.

function startButtonListeners(action)
  if(action == 'add') then
    playBtn:addEventListener('tap', showGameView)
    creditsBtn:addEventListener('tap', showCredits)
  else
    playBtn:removeEventListener('tap', showGameView)
    creditsBtn:removeEventListener('tap', showCredits)
  end
end

23. Show Credits

In showCredits, we hide the buttons, display the credits, and add a tap listener to hide the credits when the player taps the credits.

function showCredits:tap(e)
  playBtn.isVisible = false
  creditsBtn.isVisible = false
  creditsView = display.newImage('credits.png', -110, display.contentHeight-80)
  transition.to(creditsView, {time = 300, x = 0, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})
end

24. Hide Credits

When the player taps the credits, the view is tweened from the stage and removed.

function hideCredits:tap(e)
  playBtn.isVisible = true
  creditsBtn.isVisible = true
  transition.to(creditsView, {time = 300, y = display.contentHeight+creditsView.height, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
end

25. Show Game View

When the player taps the play button to start a new game, the title view is tweened from the stage and hidden. This shows the game view. The game view is the heart of the game. Let's break the rest of the showGameView step by step.

function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})
end

26. Score Text Field

We start by creating the score text field as shown below.

function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})

  -- TextFields

  scoreTF = display.newText('0', 25, 18, 'Courier', 15)
  scoreTF:setFillColor(15, 223, 245)
end

The newText method accept a number of arguments.

  • The initial text of the text field.
  • The text field's x coordinate.
  • The text field's y coordinate.
  • The text field's font and font size.

On iOS, you have access to a wide range of fonts. On Android there are only three fonts available; Droid Sans, Droid Serif, and Droid Sans Mono.


27. Timer

In the next step, we create the timer's rectangle shape using Corona built-in vector graphics library. The newRect function creates a rectangle that is 20pt wide, 6pt tall, and it places it in the top-right of the stage. The default color of new shapes is white so we need to change the rectangle's color by invoking setFillColor and passing in an RGB value. The Graphics 2.0 library doesn't use values ranging from 0 to 255 for RGB values. Instead an RGB value is expected to range from 0.0 to 1.0.

function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})

  -- TextFields

  scoreTF = display.newText('0', 25, 18, 'Courier', 15)
  scoreTF:setFillColor(15, 223, 245)

  -- Timer

  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)
end

A quick and easy approach for converting old RGB value to new values is by dividing the old value by 255. For example, 123 becomes 123/255, which translates to 0.48.


28. Pucks

The pucks group stores all the pucks so that we can manipulate them all at once.

function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})

  -- TextFields

  scoreTF = display.newText('0', 25, 18, 'Courier', 15)
  scoreTF:setFillColor(15, 223, 245)

  -- Timer

  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)

  -- Pucks

  pucks = display.newGroup()
end

29. Start Game

To finish the showGameViewMethod, we install the game listeners and start the background music. By setting the loops parameter to -1, the background music will loop until we tell it to stop.

function showGameView:tap(e)
  transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})

  -- TextFields

  scoreTF = display.newText('0', 25, 18, 'Courier', 15)
  scoreTF:setFillColor(15, 223, 245)

  -- Timer

  time = display.newRect(450, 20, 20, 6)
  time:setFillColor(0.05, 0.87, 0.96)

  -- Pucks

  pucks = display.newGroup()

  gameListeners('add')
  audio.play(bgMusic, {loops = -1, channel = 1})
end

30. Game Listeners

In showGameView, we call the gameListeners function in which the pucks are created by invoking the createPucks function. We also create a rectangle for displaying the game's time.

function gameListeners(action)
  if(action == 'add') then
    createPucks()
    timerSrc = timer.performWithDelay(1000, reduceTime, 20)
  else
    for i = 1, pucks.numChildren do
      pucks[i]:removeEventListener('touch', movePuck)
    end
    timer.cancel(timerSrc)
    timerSrc = nil
  end
end

31. Create Pucks

In createPucks, we use a for loop to instantiate the pucks. The number of pucks is stored in totalPucks, which we set to 20 a bit earlier in this tutorial.

A random position is calculated for each puck using math.random. We also use math.random to help us load a different color from the project's images. How does this work? We generate a random integer between 1 and 4, and add the result to the name of the image that we want to load. For example, if the random number is equal to 3, the game loads an image named puck3.png.

function createPucks()
  for i = 1, totalPucks do
    local p
    local rnd = math.floor(math.random() * 4) + 1
    p = display.newImage('puck' .. tostring(rnd) .. '.png', math.floor(math.random() * display.contentWidth), math.floor(math.random() * display.contentHeight))
    p.name = 'p' .. tostring(rnd)
    p:addEventListener('touch', movePuck)
    -- Physics
    physics.addBody(p, 'dynamic', {radius = 12})
    p.isSensor = true
    pucks:insert(p)
  end
end

32. Move Function

We use touch events to let the player move the pucks. When the player touches a puck, it is aligned with the position of the touch, the player's finger, and is then moved by updating its position. We also add a collision listener to the active puck to detect when the puck collides with another puck. This event listener is removed when the player releases the puck by lifting their finger from the screen.

function movePuck(e)
  if(e.phase == 'began') then
    -- Collision
    e.target.x = e.x - e.target.width/2
    e.target.y = e.y - e.target.height/2
    e.target:addEventListener('collision', onCollision)
  end
  if(e.phase == 'moved') then
    e.target.x = e.x - e.target.width/2
    e.target.y = e.y- e.target.height/2
  end
  if(e.phase == 'ended') then
    e.target:addEventListener('collision', onCollision)
  end
end

33. Timer

The reduceTimer function is in charge of the timer rectangle we created earlier. Every second, the width of the shape is reduced by setting its xScale property and it is removed from the stage when it's no longer visible. An alert view is shown to the player when the time's up.

function reduceTime(e)
  time.xScale = time.xScale - 0.05
  time.x = 450
  if(time.xScale <= 0.2) then
    display.remove(time)
    alert()
  end
end

34. Handling Collisions

The onCollision function is in charge of handling collisions between pucks.

function onCollision(e)
  if(e.other ~= nil) then
    if(e.other.name == e.target.name) then
      audio.play(blip)
      -- Local Score
      local score = display.newText('50', e.other.x, e.other.y, 'Courier New Bold', 14)
      transition.to(score, {time = 500, xScale = 1.5, yScale = 1.5, y = score.y - 20, onComplete = function() display.remove(score) score = nil end })
      -- Remove
      display.remove(e.other)
      e.other = nil
      -- Total Score
      scoreTF.text = tostring(tonumber(scoreTF.text) + 50)
      scoreTF.x = 15
    else
      audio.play(wrong)
      alert('lose')
    end
  end
end

This is what happens when a collision occurs:

  • We first check whether the names of the pucks are the same to see if their color matches.
  • If they have the same color, we play a sound effect.
  • The player's score is increased by 50 points and the score text field is updated.
  • If the color of the pucks don't match, a different sound effect is played and the game over alert view is shown to the player.

35. Alert

The alert function is a simple helper function to show an alert view and animate it. We stop the audio after 700 milliseconds to make sure that we can play a sound effect. The game listeners are removed to end the game and we show an appropriate image to the player.

function alert(action)
  timer.performWithDelay(700, function() audio.stop(1) audio.dispose(bgMusic) bgMusic = nil end, 1)
  gameListeners('rmv')
  if(action == 'lose') then
    alertView = display.newImage('gameOver.png', 155, 125)
  else
    alertView = display.newImage('timeUp.png', 155, 125)
    local score = display.newText(scoreTF.text, 225, 160, 'Courier New Bold', 20)
    score:setFillColor(255, 255, 255)
  end
  transition.from(alertView, {time = 300, xScale = 0.5, yScale = 0.5})

  -- Wait 100 ms to stop physics
  timer.performWithDelay(10, function() physics.stop() end, 1)
end

36. Call Main

To start the game, we invoke the Main function as shown below.

Main()

37. Loading Screen

Loading Screen

On the iOS platform, the file named Default.png is displayed while the application is launching. Add this image to your project's source folder, and it will be automatically added by the Corona compiler.


38. Icon

Icon

Using the graphics you created earlier, you can now create a nice icon. The dimensions of the icon size for a non-retina iPhone are 57px x 57px, while the retina version needs to be 114px x 114px. The artwork for iTunes is required to be 1024px x 1024px. I suggest creating the iTunes artwork first and then creating the smaller sized images by scaling the iTunes artwork down to the correct dimensions. There is no need to make the application icon glossy or add rounded corners as this is taken care of by the operating system.


39. Testing in the Simulator

Testing

It's time to test our application in the simulator. Open the Corona Simulator, browse to your project folder, and click Open. If everything works as expected, you're ready for the final step.


40. Build

Build

In the Corona Simulator, go to File > Build and select the target device. Fill out the required fields and click Build. Wait a few seconds and your application is ready to test on a device and/or to be submitted for distribution.


Conclusion

In this tutorial we've learned about touch listeners, collision detection, and physics, as well as a few other skills that can be useful in a wide number of games. Experiment with the final result and try to modify the game to create your own version. I hope you liked this tutorial and found it helpful. Thank you for reading.

2014-01-31T12:30:27.000Z2014-01-31T12:30:27.000ZCarlos Yanezhttp://code.tutsplus.com/tutorials/create-a-collect-the-pieces-game-with-corona--mobile-22400

Viewing all articles
Browse latest Browse all 1836

Trending Articles