I like the idea of animating the combing of tiles. This shows the effect of game play graphically. In some situations a play will combine three tiles setting up a situation where three more tiles can be combined. This is covered in the recursive system from the last post. The game covers this but game play is greatly enhanced if the player can see some graphical representation of what is happening. The same applied to scoring points or other in game effects.
Graphically animating the cause an effect for things that could normally be invisible on the computer enhance the experience of using your apps. This is an important part of creating good user experience. Think of the animation as a little story telling a player about the points they just earned or the new power up they just received or anything that might have happened.
A good example of this can be applied to the tile matching. In the game placing a third tile combines the three tiles into the next color of tile. When this third tile appears it might appear next to another two tiles of the same color. At this point the we have another match and the computer needs to combine them again, and so on.
We have this covered. But the action applies in a single step, no matter how many tiles have been combined. In this system it is not exactly clear what has happened. Better would be to show each step animated sequentially. For example, show red tiles combining into a green, followed by three green tiles combining into a blue tile.
Animating combining tiles
Since the system described in the last post uses a recursive function call, creating this effect is easy to achieve. We only need to delay recursive call to check_tile_match(tile) the time of the animated transition.
Inside the check_tile_match(tile) function, there is a call to check_tile_match(tile). Instead of calling this directly, wrap it in a timer.perfromWithDelay(). check_tile_match(tile) requires that you include the tile you are looking for. Since this variable is local to the function we’ll to use a closure.
timer.performWithDelay( 500, function() check_tile_match( tile ) end, 1 )
Here I’ve added a function as the handler for timer.performWithDelay(). This function wraps, or encloses, check_tile_match(tile), effectively preserving the value of tile.
Adding some animated score
As long as we’re on the subject of providing information about game state and game actions, we should also provide some score information. The game currently does not have a score, then I’ll add a an animated point value that appears at the location where points are scored.
Adding the Score
Score is a big topic games. How many points should a play be worth? Great question that points to how your game progresses and how players will view escalation and game play. Rather than concentrating on the points scored I want to focus on how the score is displayed here.
The most common place to see the score is the upper left corner of the score. We’ll use that in this example. I first hid the status bar to make room for the score.
display.setStatusBar( display.HiddenStatusBar )
Create a variable to hold the actual score value.
local score = 0 -- Add a variable to hold the score
Next, create a text object to display the score.
-- Add a text object to display the score local score_text = display.newText( score, 0, 0, native.systemFont, 16 )
Due to the way that Corona handles text we will need to set the reference point and set the position of the text object each time we change the text it displays. I created a function to handle this.
This function receives a parameter equal to the number of points just scored. It adds these to score, sets the get in the score_text to the updated value then sets the reference point. I want the text to align with the upper right corner. Normally text objects are aligned with a center reference point. After changing the text of the object I need to set the reference point with object:setReferencePoint() and then set the x and y.
When the project loads, the last line below, calls this function once. This sets the initial position of the text field.
-- A function to update the score local function set_score( points ) score = score + points -- Add points to score score_text.text = score -- Display the new score -- These next three lines position the score based on the top left reference point score_text:setReferencePoint( display.TopLeftReferencePoint ) score_text.x = 10 score_text.y = 5 end set_score( 0 ) -- Call set score once to position the score text correctly
Next set the score when points are scored. Inside check_tile_match(tile) find the line that sets the point score for the last play. Right after this call set_score(points) passing the points scored.
-- Calculate points
local points = match_color_index * 100 * #matched_array
set_score( points ) -- Call set_score to add these points to the score
show_points( points, tile.x, tile.y ) -- Display the points at the location of the scoring tile
Show points scored at the point where they are scored with motion
Now we’ll add a text field that shows the points scored. This will show up at the tile that was last played. Then animate up the screen and fade out. This is another good spot for a factory function.
First we need a function to create a text field that will show the point value scored. This will be another factory function that returns a reference to the text object created. All that happens here, is a text field is created, and the text is set to the number of points scored. Then the text field is returned. You pass what to display to this function.
It seems like this function does’t do much. It’s job is to keep us organized. Here the only thing that happens is we make the text object. Any code that would set the color or other features of this text object would go here.
-- Add a function to create score field objects local function Score_Field( points ) local score_text = display.newText( points, 0, 0, native.systemFont, 12 ) return score_text end
Next we’ll make a factory function that creates the points and animates the text field. We’ll use a transition to animate the text and a function to remove the text when the transition is complete.
-- This function removes score fields local function remove_score_text( score_text ) display.remove( score_text ) end -- this function shows points in a score field on the game board. local function show_points( points, x, y ) local score_text = Score_Field( points ) score_text.x = x score_text.y = y transition.to( score_text, {y=y-100, time=300, delay=200, alpha=0, onComplete=remove_score_text} ) end
Last call this function from check_tile_match(tile) after the line that calculates the point value.
-- Calculate points local points = match_color_index * 100 * #matched_array set_score( points ) -- Call set_score to add these points to the score show_points( points, tile.x, tile.y ) -- Display the points at the location of the scoring tile
Here’s a full listing the code that I used. This combines the code from the last example with the new code discussed here.
----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- Your code here -- Hide the status bar display.setStatusBar( display.HiddenStatusBar ) local TILE_ROWS = 6 local TILE_COLS = 6 local TILE_SIZE = 50 local TILE_MARGIN = 1 local match_color_index = 0 local score = 0 -- Add a variable to hold the score local tile_array = {} local color_array = {} local matched_array = {} local game_group = display.newGroup() local function Color( red, green, blue ) return {r=red, g=green, b=blue} end table.insert( color_array, Color(255, 0, 0) ) table.insert( color_array, Color(0, 255, 0) ) table.insert( color_array, Color(0, 0, 255) ) table.insert( color_array, Color(255, 255, 0) ) table.insert( color_array, Color(255, 0, 255) ) table.insert( color_array, Color(0, 255, 255) ) -- Add a text object to display the score local score_text = display.newText( score, 0, 0, native.systemFont, 16 ) -- A function to update the score local function set_score( points ) score = score + points -- Add points to score score_text.text = score -- Display the new score -- These next three lines position the score based on the top left reference point score_text:setReferencePoint( display.TopLeftReferencePoint ) score_text.x = 10 score_text.y = 5 end set_score( 0 ) -- Call set score once to position the score text correctly -- Add a function to create score field objects local function Score_Field( points ) local score_text = display.newText( points, 0, 0, native.systemFont, 12 ) return score_text end local function Tile() local tile = display.newRect( 0, 0, TILE_SIZE, TILE_SIZE ) tile:setFillColor( 255, 255, 255, 100 ) return tile end -- This function removes score fields local function remove_score_text( score_text ) display.remove( score_text ) end -- this function shows points in a score field on the game board. local function show_points( points, x, y ) local score_text = Score_Field( points ) score_text.x = x score_text.y = y transition.to( score_text, {y=y-100, time=300, delay=200, alpha=0, onComplete=remove_score_text} ) end local function remove_tile( tile ) print( "removing tile", tile ) display.remove( tile ) end local function animated_match() local color = color_array[match_color_index] local t = {time=500, x=matched_array[1].x, y=matched_array[1].y, alpha=0, onComplete=remove_tile} for i = 1, #matched_array, 1 do local tile = Tile() tile:setFillColor( color.r, color.g, color.b ) tile.x = matched_array[i].x tile.y = matched_array[i].y transition.to( tile, t ) end end local check_neighbors local check_tile_match local function get_tile_frame_at_col_row( col, row ) if col >= 1 and col <= TILE_COLS and row >= 1 and row <= TILE_ROWS then return tile_array[row][col] else return false end end function check_neighbors( tile ) -- Get the row and col of this tile local row = tile.row local col = tile.col -- Define the coords of Left, Top, Right, and Bottom local ltrb_array = { {row=0, col=-1}, { row=-1, col=0 }, { row=0, col=1 }, { row=1, col=0 } } -- print("Check from tile:".. tile.row .. tile.col .. " " .. tile.alien.currentFrame ) -- Loop through left, top, right and bottom for i = 1, #ltrb_array, 1 do local check_row = row + ltrb_array[i].row local check_col = col + ltrb_array[i].col -- Check that the row and col are on the board local n_tile = get_tile_frame_at_col_row( check_col, check_row ) if n_tile then -- on board if n_tile.color_index == match_color_index then -- matches -- Check that this tile doesn't exist in matched_array local index = table.indexOf( matched_array, n_tile ) if index == nil then -- tile hasn't been found yet! print( "match at:" .. n_tile.row .. n_tile.col ) table.insert( matched_array, n_tile ) -- add to array check_neighbors( n_tile ) -- recur this function with new tile end end end end end function check_tile_match( tile ) matched_array = {tile} -- Add the first tile to the array match_color_index = tile.color_index -- Get the index to match check_neighbors( tile ) -- Start looking for matching tiles -- Time to clear the tiles, if there are more than 2 matches if #matched_array > 2 then -- If more than two tiles match for i = 2, #matched_array, 1 do -- Loop through all but the first tile local tile = matched_array[i] -- Clear all these tiles tile.color_index = 0 tile.is_empty = true tile:setFillColor( 255, 255, 255, 100 ) end local tile = matched_array[1] -- Get the first tile and local color_index = match_color_index + 1 -- advance it's color local color = color_array[ color_index ] tile.color_index = color_index tile:setFillColor( color.r, color.g, color.b ) tile.is_empty = false animated_match() -- Calculate points local points = match_color_index * 100 * #matched_array set_score( points ) -- Call set_score to add these points to the score show_points( points, tile.x, tile.y ) -- Display the points at the location of the scoring tile -- Wait for the animation to complete then check for another match set timer.performWithDelay( 500, function() check_tile_match( tile ) end, 1 ) end end ---------------------------------------------------------------------------------------- local function touch_tile( event ) local phase = event.phase if phase == "ended" then local tile = event.target if tile.is_empty then tile.is_empty = false tile.color_index = 1 local r = color_array[ tile.color_index ].r local g = color_array[ tile.color_index ].g local b = color_array[ tile.color_index ].b tile:setFillColor( r, g, b ) end -- Matching logic logic starts here. check_tile_match( tile ) end end local function make_grid() local tile_spacing = TILE_SIZE + TILE_MARGIN local offset_x = ( display.contentWidth - ( tile_spacing * TILE_COLS ) ) / 2 offset_x = TILE_SIZE / 2 - offset_x for row = 1, TILE_ROWS, 1 do local row_array = {} for col = 1, TILE_COLS, 1 do local tile = Tile() game_group:insert( tile ) tile.x = ( col * tile_spacing ) - offset_x tile.y = row * tile_spacing tile.row = row tile.col = col tile.is_empty = true -- set this tile to empty tile.color_index = 0 tile:addEventListener( "touch", touch_tile ) table.insert( row_array, tile ) end table.insert( tile_array, row_array ) end end make_grid()