Build a Snake Game - Adding Interaction


In this tutorial series, you'll learn how to create a game like Snake. The objective of the game is to grab the apples on screen to raise the score. Read on!

Where We Left Off. . .

Please be sure to check part 1 of the series to fully understand and prepare for this tutorial.

Step 1: Start Button Listeners

This function adds the necesary listeners to the TitleView buttons.

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

Step 2: Show Credits

The credits screen is shown when the user taps the about button, a tap listener is added to the credits view to remove it.

function showCredits:tap(e)
	playBtn.isVisible = false
	creditsBtn.isVisible = false
	creditsView = display.newImage('credits.png', display.contentCenterX - 110, display.contentHeight+40), {time = 300, y = display.contentHeight-20, onComplete = function() creditsView:addEventListener('tap', hideCredits) end})

Step 3: Hide Credits

When the credits screen is tapped, it'll be tweened out of the stage and removed.

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

Step 4: Show Game View

When the Start button is tapped, the title view is tweened and removed revealing the game view. There are many parts involved in this view so we'll split them in the next steps.

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

Step 5: Add Game Background

This code places the game background in the stage.

	-- [Add GFX]
	-- Game Bg
	gameBg = display.newImage('gameBg.png')

Step 6: Add Game Pad

In order to move the snake in the screen we'll need a game pad, this will take care of that. A tap listener will be added later to each arrow to handle the movement.

-- Pad

up = display.newImage('up.png', 33.5, 219.5)
left = display.newImage('left.png', 0, 252.5)
down = display.newImage('down.png', 33.5, 286.5)
right = display.newImage('right.png', 66.5, 252.5) = 'up' = 'down' = 'left' = 'right'

Step 7: Score Text

This line adds the score textfield:

-- Score Text

score = display.newText('0', 74, 3, native.systemFontBold, 15)
score:setTextColor(252, 202, 1)

Step 8: Head

Next we add the snake's head. As mentioned in the previous tutorial, a hit area will be added on top of it and then both will be stored in a group.

-- Head

headGFX = display.newImage('head.png', display.contentCenterX-0.3, display.contentCenterY-0.2)
headHitArea = display.newLine(display.contentCenterX+6, display.contentCenterY-0.2, display.contentCenterX + 8, display.contentCenterY-0.2)
headHitArea:setColor(0, 0, 0)
headHitArea.width = 2
head = display.newGroup(headGFX, headHitArea)
lastPart = head
parts = display.newGroup()

Step 9: Initial Apple

The first apple is added by this code at a random position.

	-- Add first apple
	local randomX = 25 + math.floor(math.random() * 402)
	local randomY = 25 + math.floor(math.random() * 258)
	local apple = display.newImage('apple.png', randomX, randomY)
	apples = display.newGroup(apple)

Step 10: Game Listeners

This function adds the necessary listeners to start the game logic.

function gameListeners()
	up:addEventListener('tap', movePlayer)
	left:addEventListener('tap', movePlayer)
	down:addEventListener('tap', movePlayer)
	right:addEventListener('tap', movePlayer)

Step 11: Move Player Function

The direction variable is changed by this function, this will make the snake go in the pressed direction.

function movePlayer(e)
	dir =
	if(started == nil) then
		timerSrc = timer.performWithDelay(speed, update, 0)
		started = true

Step 12: Hit Test Objects

We'll use an excellent and useful function for collision detection without physics, you can find the original example and source at the CoronaLabs Code Exchange website.

function hitTestObjects(obj1, obj2)
        local left = obj1.contentBounds.xMin <= obj2.contentBounds.xMin and obj1.contentBounds.xMax >= obj2.contentBounds.xMin
        local right = obj1.contentBounds.xMin >= obj2.contentBounds.xMin and obj1.contentBounds.xMin <= obj2.contentBounds.xMax
        local up = obj1.contentBounds.yMin <= obj2.contentBounds.yMin and obj1.contentBounds.yMax >= obj2.contentBounds.yMin
        local down = obj1.contentBounds.yMin >= obj2.contentBounds.yMin and obj1.contentBounds.yMin <= obj2.contentBounds.yMax
        return (left or right) and (up or down)

Step 13: Get Head Last Position

We capture the snake's head last position to update the parts position, each one replacing the x and y coordinates of the other.

function update()
	-- Capture head last position, this moves the first added piece
	local lastX = head.x
	local lastY = head.y
	local xPos = {}
	local yPos = {}

Step 14: Move Parts

This complements the behavior explained in the past step.

	for i = 1, parts.numChildren do
		-- Capture parts position to move them
		xPos[i] = parts[i].x
		yPos[i] = parts[i].y
		-- Move Parts
		if(parts[i-1] == nil) then
			parts[i].x = lastX
			parts[i].y = lastY
			parts[i].x = xPos[i-1]
			parts[i].y = yPos[i-1]

Step 15: Check for Game Over

The game ends when the snake's head touches another part of the snake. A sound is played as feedback.

-- Game over if head touches other part of snake
		if(hitTestObjects(headHitArea, parts[i])) then
			if(parts[i].name == '1') then
				timerSrc = nil

Step 16: Move Head

These lines move the head according to the direction stablished by the movePlayer function created in step 11.

-- Move Head & Hit Area

if(dir == 'up') then
    head.y = head.y - mConst
    headHitArea.x = headGFX.x
    headHitArea.y = headGFX.y - 7
elseif(dir == 'left') then
    head.x = head.x - mConst
    headHitArea.x = headGFX.x - 8
    headHitArea.y = headGFX.y
elseif(dir == 'down') then
    head.y = head.y + mConst
    headHitArea.x = headGFX.x
    headHitArea.y = headGFX.y + 8
elseif(dir == 'right') then
    head.x = head.x + mConst
    headHitArea.x = headGFX.x + 7
    headHitArea.y = headGFX.y

Step 17: Apple-Head Collision

Several actions occurr when the snake eats an apple we will go through them in the following steps.

First we remove the apple.

-- Detect apple-player collision

for i = 1, apples.numChildren do
    if(hitTestObjects(head, apples[i])) then
        -- Remove Apple

Step 18: Update Score

Next we update the score textfield and play a sound to indicate that the apple has been eaten.

-- Add Score

score.text = score.text + tostring(10)
score.x = 74

Step 19: Add the Snake Part

Then a new part is added to the snake. Its position is calculated by the last part added (the first time is the head) and then they are added to a group.

-- Add snake body

local part = display.newImage('bodyPart.png')

if(dir == 'up') then
    part.x = lastPart.x
    part.y = lastPart.y + mConst
elseif(dir == 'left') then
    part.x = lastPart.x + mConst
    part.y = lastPart.y
elseif(dir == 'down') then
    part.x = lastPart.x
    part.y = lastPart.y - mConst
elseif(dir == 'right') then
    part.x = lastPart.x - mConst
    part.y = lastPart.y

current = current + 1 = tostring(current)
lastPart = part

Step 20: New Apple

Lastly, we create a new apple and place it in a random position in the stage.

      -- Add new apple
      local randomX = 25 + math.floor(math.random() * 402)
      local randomY = 25 + math.floor(math.random() * 258)
      local apple = display.newImage('apple.png', randomX, randomY)
      apples = display.newGroup(apple)

Step 21: Screen Limits

This makes the snake appear on the other side of the stage.

	-- Screen Limits
	if(head.x < 0) then
		head.x = display.contentWidth - mConst
	elseif(head.x > display.contentWidth) then
		head.x = 0
	-- Vertical
	if(head.y < 0) then
		head.y = display.contentHeight - mConst
	elseif(head.y > display.contentHeight) then
		head.y = 0

Step 22: Call Main Function

In order to start the game, the Main function needs to be called. With the above code in place, we'll do that here:


Step 23: Loading Screen

The Default.png file is an image that will be displayed right when you start the application while the iOS loads the basic data to show the Main Screen. Add this image to your project source folder, it will be automatically added by the Corona compliler.

Step 24: Icon

Using the graphics you created before you can now create a nice and good looking icon. The icon size for the non-retina iPhone icon is 57x57px, but the retina version is 114x114px and the iTunes store requires a 512x512px version. I suggest creating the 512x512 version first and then scaling down for the other sizes.

It doesn't need to have the rounded corners or the transparent glare, iTunes and the iPhone will do that for you.

Step 25: Testing in Simulator

It's time to do the final test. Open the Corona Simulator, browse to your project folder, and then click open. If everything works as expected, you are ready for the final step!

Step 26: Build

In the Corona Simulator go to File > Build and select your target device. Fill the required data and click build. Wait a few seconds and your app will be ready for device testing and/or submission for distribution!


Experiment with the final result and try to make your custom version of the game!

I hope you liked this tutorial series and find it helpful. Thank you for reading!