Dynamine!

What Is it?

 

Dynamine is a match-3 game where you explore a dangerous mine shaft in search for precious ore and gems. Your goal is to top your high score and collect the ore to upgrade your equipment for your next attempt. Tap your way to success and see how far you can get.

 

The game was developed in 3 weeks with no prior knowledge of LUA or Defold.

Platform: Android/IOS

Tools Used: Defold

Duration: 3 Weeks

Team Size: 4

Role: Scripter

Responsibilities: Gameplay Scripting, System Scripting

Game Mode

 

The game spawns blocks in 4 columns within an interval based on how far into the game it is. The player has to tap each block to break them. As a block is destroyed, that row will fall down as if it was affected by gravity.

  1. function SpawnBlocks(self)
  2.     local filledrows = boardWidth
  3.     local tries = 0
  4.     
  5.     while filledrows > 0 and tries < boardWidth * 5 do
  6.         tries = tries + 1
  7.         for x = 0, boardWidth - 1 do
  8.         
  9.             if filledrows <= 0 then
  10.                 return
  11.             end
  12.             
  13.             -- Find which Y value the block is going to have
  14.             local y = FindLowestEmptySlot(self, x)
  15.             
  16.             local pos = go.get_world_position() + vmath.vector3(leftEdge + x * blockSize + offset,bottomEdge + y * blockSize + offset + 500, 0)
  17.             if self.board[x][y] == nil then
  18.                 
  19.                 local shouldPlace = false
  20.                 local min
  21.                 local ore
  22.                 
  23.                 -- This loop manipulated the RNG of the blocks so that
  24.                 -- ores doesnt spawn multiple in a row
  25.                 repeat
  26.                     -- Pick a random sprite for the block
  27.                     min = DifficultyList[level + math.random(levelScale)]
  28.                     
  29.                     -- Does the block have a ore inside of it?
  30.                     if math.random(100) < oreSpawnChance then
  31.                         theore = RandomOre(self)
  32.                         ore = ores[RandomOre(self)]
  33.                     else
  34.                         ore = nil
  35.                     end
  36.                     shouldPlace, min, ore = ManipulateRNG(self, min, ore, x, y)
  37.                 until shouldPlace
  38.                 
  39.                 local mineralOre
  40.                 if ore == nil then
  41.                     mineralOre = hash("none")
  42.                 else
  43.                     mineralOre = ore.tile
  44.                 end
  45.                 
  46.                 if math.random() < gemTile.gemchance then
  47.                     min = gemTile
  48.                     mineralOre = hash("none")
  49.                 end
  50.                 
  51.                 -- Set the visuals for the tile to the first in its list of sprites
  52.                 -- This is used for when blocks are cracked open slowly
  53.                 local starttile = (min.tile[1])
  54.                 
  55.                 -- Create a new block
  56.                 local obj = factory.create("#blockFactory", pos, null, { mineral = starttile , health = min.health, ore = mineralOre })    
  57.                 local objectURL = msg.url(nil, obj, "sprite")
  58.                 
  59.                 -- Send the sprite information to the newly created block
  60.                 msg.post(obj, "SetSprites", {sprites = min.tile})
  61.                 
  62.                 -- Set the scale so that it fits the columns regardless of sprite resolution
  63.                 local scale = blockSize / go.get(objectURL, "size").x
  64.                 go.set_scale(vmath.vector3(scale, scale, scale), obj)
  65.                 
  66.                 -- Assign the block in the matrix of blocks
  67.                 self.board[x][y] = { id = obj, mineral = min, ore = ore, x = x, y = y , worldLocation = pos, moving = true}        
  68.                 filledrows = filledrows - 1
  69.                 
  70.                 MoveBlocksDownRow(self , x)
  71.             end
  72.         end
  73.     end
  74. end

The game manipulates the blocks to always have variety by checking the block below. If the block below is the same, then the manipulated block is changed into another block.

Without Manipulation

With Manipulation

If the player matches 3 or 4 blocks in a row, then they will all be destroyed. The player gains more score and resources the more blocks that are matched.

  1. local function CheckForLine(self)
  2.     -- Go through the entire row of blocks
  3.     for i = 0, boardWidth - 1 do
  4.     
  5.         -- Count the amount of the same block in the row
  6.         -- This counts the blocks after the current block (i)
  7.         local chain = CheckEntireRow(self, i)
  8.         
  9.         -- If the amount is more than a threshold
  10.         if #chain >= minimumMatchRows then
  11.         
  12.             local destroyRow = true
  13.             
  14.             -- Check if none of the tiles in the row is moving
  15.             -- If they are standing still, destroy them
  16.             -- Otherwise do nothing
  17.             for q = 1, #chain do
  18.                 if self.board[chain[q].x][chain[q].y].moving then
  19.                     destroyRow = false
  20.                     break
  21.                 end
  22.             end
  23.         
  24.             -- If we destroy the row, give score and remove from the matrix of blocks
  25.             if destroyRow then
  26.                 for j = 1, #chain do
  27.                     msg.post(self.board[chain[j].x][chain[j].y].id, "DamageAnimation")
  28.                     local xvalue = self.board[chain[j].x][chain[j].y].x
  29.                     local yvalue = self.board[chain[j].x][chain[j].y].y
  30.                     
  31.                     -- Add a base score of 10
  32.                     local score = 10
  33.                     gamescore = gamescore + score
  34.                     
  35.                     -- Give materials to the player
  36.                     -- It also calculate if it should or should not give materials
  37.                     GiveOreToPlayer(self, self.board[chain[j].x][chain[j].y].ore, 2)
  38.                     
  39.                     -- Do the same with gems (best currency)
  40.                     ChanceToGiveGems(self, chain)
  41.                     OnBlockDestroyed(self)
  42.                     
  43.                     -- Count the total amount of blocks destroyed
  44.                     Achievements["Blocks"] = Achievements["Blocks"] + 1
  45.                     
  46.                     -- If we have ores, create an ore object in the world
  47.                     -- at the location of the block
  48.                     if self.board[chain[j].x][chain[j].y].ore ~= nil then
  49.                         VisualizeOres(self, self.board[chain[j].x][chain[j].y])
  50.                     end
  51.                     
  52.                     -- Remove the block from the matrix
  53.                     go.delete(self.board[chain[j].x][chain[j].y].id)
  54.                     self.board[chain[j].x][chain[j].y] = nil
  55.                     
  56.                     -- Move down the row
  57.                     MoveBlocksDownRow(self , xvalue)
  58.                     msg.post("GUI#gui", "UpdateScore", {score = score, x = xvalue, y = yvalue})
  59.                     
  60.                     -- Add value to the on fire system
  61.                     OnFire(self, 2)
  62.                 end
  63.                 
  64.                 -- Count some achievements depending on if its
  65.                 -- a line of 3 or 4
  66.                 if #chain == 3 then
  67.                     Achievements["3-row"] = Achievements["3-row"] + 1
  68.                 elseif #chain == 4 then
  69.                     Achievements["4-row"] = Achievements["4-row"] + 1
  70.                 end
  71.                 
  72.                 ShakeCamera(self, 0.1)
  73.             end
  74.         end
  75.     end
  76. end

Since the game is quite small in terms of mechanics, we needed to make the game feel good. The second to second gameplay is tapping blocks which means that it needs to feel good to tap them.

To make the tapping feel better, I added a small animation to the blocks and a camera shake if the player matches 3 or 4 in a row. I also implemented some particles every time you tap on a block to add another small detail.

  1. -- If we should shake the block or not
  2. if self.shaketime > 0 then
  3.     -- Find a random rotation
  4.     local rot = vmath.quat_rotation_z(math.rad(math.random(-shakestrength, shakestrength)))
  5.     local currentRotation = go.get_rotation()
  6.     
  7.     -- Lerp between the current rotation and the randomed rotation
  8.     local lrot = vmath.lerp(smoothness * dt, currentRotation, rot)
  9.     go.set_rotation(lrot)
  10.     self.shaketime = self.shaketime - dt
  11. else
  12.     go.set_rotation(self.startRotation)
  13. end

If the player loses the game by filling the entire board, the game will save statistics of how well he or she performed. If it is better than previous, then that will be a new record. The game saves amount of blocks destroyed, number of rows matched, score and highest stage. This feature reinforces the retention of the player.

Crafting

As the player gather resources, he or she can craft additional pickaxes. These pickaxes allows the player to progress further into the game by being stronger. Each pickaxe has a durability which is broken after a certain amount of taps.

fredrik.tumlin@Gmail.com

+46 76 191 96 57