1. 程式人生 > >壽司快賣:實現遊戲主流程--製作壽司和客戶顯示動畫特效

壽司快賣:實現遊戲主流程--製作壽司和客戶顯示動畫特效

上一節我們搭建了遊戲的基本框架。遊戲介面被分為若干個板塊,其中一個板塊顯示了各種製作壽司的材料,它的目的是用於玩家根據資訊組裝各種壽司,本節我們進入遊戲的主流程設計階段,這節我們要完成的是如何將讓玩家將各種材料組合成相應的壽司。

首先我們先新增一些輔助函式,在gamescenecomponent.vue中新增程式碼如下:

resizeCanvas () {
        // change 2
        var customerView = document.getElementById('customer-view')
        var w = this.getBorderView(customerView)
        this.canvas = document.getElementById('canvas')
        this.canvas.width = customerView.offsetWidth - w.left - w.right
        this.canvas.height = customerView.offsetHeight - w.top - w.bottom
      },

...

//change 1
      initDomElement () {
        ....
        this.others = document.getElementById('others')
        this.rices = document.getElementById('rice')
        this.seaweeds = document.getElementById('seaweed')

       // 設定相應壽司的材料組合
        this.recipes['sushiSalmonRoe'] = ['rice', 'seaweed', 'seaweed', 'salmon-roe'].sort()
        this.recipes['sushiOctopus'] = ['rice', 'octopus'].sort()
        this.recipes['sushiSalmon'] = ['rice', 'salmon'].sort()
        this.recipes['sushiEgg'] = ['rice', 'egg', 'seaweed'].sort()
      },   
      getBorderWidths (element) {
        var style = document.getComputedStyled(element)
        return {
          top: parseInt(style.borderTopWidth),
          right: parseInt(style.borderRightWidth),
          bottom: parseInt(style.borderBottomWidth),
          left: parseInt(style.borderLeftWidth)
        }
      },
// change 3
      arrayIsEqual (array1, array2) {
        if (array1.length !== array2.length) {
          return false
        }

        for (var i = 0, len=array1.length; i < len; i++) {
          if (array1[i] !== array2[i]) {
            return false
          }
        }

        return true
      },
      clearChild (node) {
        while (node.lastChild) {
          node.removeChild(node.lastChild)
        }
      },
      clearAllIngredients () {
        this.clearChild(others)
        this.clearChild(rices)
        this.clearChild(seaweeds)
      }
    }

上面程式碼用於計算可知Dom元素的大小位置,以及在Dom中新增或刪除各種元素。在製作壽司時,玩家通過選取相應材料組合起來形成所需要的壽司,相應程式碼如下:

initDOMElements () {
        // change 6
        var ingredients = document.querySelectorAll('.ingredient')
        for (var i = 0, len = ingredients.length; i < len; i++) {
          var element = ingredients[i]
          element.onclick = this.ingredentOnclick.bind(this)
        }

        var deleteButton = document.getElementById('delete-sushi-btn')
        deleteButton.onclick = this.deleteButtonOnclick.bind(this)

        this.ingredientsNode = document.getElementById('ingredient')
      },
      // change 7
      deleteButtonOnclick () {
        this.trashSushi()
      },

...

// change 5
      trashSushi () {
        this.sushiOnHand.length = 0
        this.clearAllIngredients()
      }

當玩家選取若干種製作壽司的材料後,介面要做相應變化,對應程式碼如下:

// change 8
      ingredentOnclick (ingredient) {
        console.log('ingredient click:', ingredient)
        var type = ingredient.toElement.dataset.type
        this.sushiOnHand = this.sushiOnHand.sort()
        this.addIngredientToScreen(type)
      },
      addIngredientToScreen (type) {
        var isEqualToAnySushi = false
        var sushiName = ''
        for (var key in this.recipes) {
          if (this.recipes.hasOwnProperty()) {
            // 當前選中的材料是不是屬於某個指定的壽司選單裡
            isEqualToAnySushi = this.arrayIsEqual(this.sushiOnHand, this.recipes[key])
            sushiName = key
            if (isEqualToAnySushi) {
              break
            }
          }
        }

        // 把所有選中的材料組合起來形成一個壽司
        if (isEqualToAnySushi) {
          this.clearAllIngredients()
          var sushi = document.createElement('div')
          sushi.classList.add(sushiName, 'sushi')
          this.others.appendChild(sushi)
        } else {
          // 把選擇材料拷貝到壽司板塊
          var node = this.ingredientsNode.querySelector('.ingredient[data-type=' + type + ']').cloneNode(true)
          node.style.height = '80px'
          if (type === 'rice') {
            console.log('append to rice:', this.rices)
            this.rices.appendChild(node)
          } else if (type === 'seaweed') {
            console.log('append to seaweeds:', this.seaweeds)
            this.seaweeds.appendChild(node)
          } else {
            console.log('append to others:', this.others)
            this.others.appendChild(node)
          }
        }

當上面程式碼完成後,玩家在壽司面板點選一個圖片代表的元素時,如果它屬於某個壽司組合菜單中的一部分,那麼它就會顯示在右邊面板上,如下圖所示:

螢幕快照 2018-10-23 下午5.31.27.png

當我們點選右上角的trash按鈕時,下面選中的元素會被刪除掉。接著我們繼續新增顧客動畫特效,客戶將隨機的出現在場景中央區域,根據一個隨機值它會出現在左上方或右下方,一開始客戶出現時它會顯示出愉快的表情,如下圖:

螢幕快照 2018-10-29 下午4.25.41.png

此時玩家應該根據客戶的要求,點選左下方的材料圖片組裝出客戶想要的壽司,如果時間過長沒能及時將壽司製作出了,客戶就會顯示出憤怒的表情,如下圖:

螢幕快照 2018-10-29 下午4.22.11.png

我們看看相應程式碼的實現:

data () {
      return {
        canvas: null,
        // change 4
        sushiOnHand: [],
        recipes: [],
        // change 10:
        view: {},
        queues: [],
        queueIndex: 0,
        leftPos: 0.40,
        rightPos: 0.8
      }
    },
....
 // change 9
      initCustomerView () {
        console.log('this.cjs: ', this.cjs)
        this.stage = new this.cjs.Stage(this.canvas)
        this.cjs.Ticker.setFPS(60)
        this.cjs.Ticker.addEventListener('tick', this.stage)
        this.cjs.Ticker.addEventListener('tick', this.tick)
        // 實現客戶佇列
        this.view.queueLeft = new this.cjs.Container()
        this.stage.addChild(this.view.queueLeft)
        this.view.queueRight = new this.cjs.Container()
        this.stage.addChild(this.view.queueRight)
      },

通過上面程式碼,為程式新增一個時鐘,我們將根據時鐘變化來設定遊戲的動畫效果,接著我們編寫構造客戶動畫的程式碼:

 // change 11 設定顧客物件
      Customer (number, leftOrRight) {
        var obj = new this.cjs.Container()
        obj.number = number
        // 隨機構造客戶想要吃的壽司
        obj.wants = this.randomWants()
        // 客戶是否吃到指定壽司
        obj.hasEaten = false
        // 是否把客戶放到佇列前頭
        obj.hasShowUp = false
        // 客戶等待了多久
        obj.hasWaitForTicks = 0
        // 在左佇列還是右佇列
        obj.queueIndex = 0
        if (leftOrRight === 'right') {
          obj.queueIndex = 1
        }

        return obj
      },
      randomWants () {
        var options = ['sushiSalmonRoe', 'sushiOctopus', 'sushiSalmon', 'sushiEgg']
        var index = Math.floor(Math.random() * options.length)
        return options[index]
      },
      customerTick (customer) {
       if (customer.hasShowUp === false) {
          return
        }
        customer.hasWaitForTicks += 1
        if (customer.hasShownUp === true && customer.hasWaitForTicks === 300) {
          // 顯示憤怒的顧客圖片
          console.log('customer angry')
          customer.graphics.gotoAndStop('angry')
        }
        // 如果等待太久,將顧客從畫面上刪除
        if (customer.hasWaitForTicks > 500) {
          this.removeCustomer(customer)
        }
        // 如果成功吃到壽司,也將客戶圖片從頁面刪除
        if (customer.hasEaten) {
          this.removeCustomer(customer)
        }
      },
      removeCustomer (customer) {
        if (customer.parent === null) {
          console.log('remove customer with null parent:', customer)
        }
        customer.parent.removeChild(customer)
        this.removeFromQueue(customer.queueIndex)
      },

上面程式碼構建客戶物件,並且初始化它相關資訊,customerTick用來根據時鐘變化調整客戶動畫的顯示,當經過一定時長,如果相關條件沒有滿足,那麼我們就將客戶的愉悅動畫,通過呼叫gotoAndStop(‘angry’)來時實現將客戶動畫轉變為憤怒表情,當時長超過500 tick後,我們將客戶動畫從頁面上刪除,客戶在頁面上的顯示需要執行下面程式碼:

 // 將客戶圖片顯示到頁面上
      customerShowUp (customer) {
        customer.graphics = new this.assetsLib['Customer' + customer.number]()
        customer.graphics.gotoAndStop('normal')
        customer.graphics.on('click', this.customerOnClick)
        customer.addChild(customer.graphics)

        var bubble = new this.assetsLib.Bubble()
        bubble.x = -40
        bubble.y = -120
        customer.addChild(bubble)
        bubble.sushiType.gotoAndStop(customer.wants)
        customer.hasShowUp = true

        this.customer = customer
      },
      customerOnClick () {
        var isEqual = this.arrayIsEqual(this.sushiOnHand,
        this.receipes[this.customer.wants])
        if (isEqual) {
          this.customer.hasEaten = true
        }

        this.trashSushi()
      },
      removeFromQueue (index) {
        this.queues[index].shift()
      },
      tick () {
        var durationForNewCustomer = 500
        if (this.cjs.Ticker.getTicks() % durationForNewCustomer === 0) {
          console.log('summon customer')
          this.summonNewCustomer()
          var customer = this.queues[0][0]
          if (customer && !customer.hasShowUp) {
            console.log('show up customer left')
            this.customerShowUp(customer)
          }
          customer = this.queues[1][0]
          if (customer && !customer.hasShowUp) {
            console.log('show up customer right')
            this.customerShowUp(customer)
          }
        }
        if (this.queues[0][0] !== undefined) {
          this.customerTick(this.queues[0][0])
        }
        if (this.queues[1][0] !== undefined) {
          this.customerTick(this.queues[1][0])
        }
      },
      summonNewCustomer () {
        var leftOrRight = 'left'
        var queueIndex = 0
        if (Math.random() >= 0.5) {
          leftOrRight = 'right'
          queueIndex = 1
        }

        var customer = this.Customer(1, leftOrRight)
        this.queues[queueIndex].push(customer)
        this.queueIndex = queueIndex
        if (leftOrRight === 'left') {
          this.view.queueLeft.addChild(customer)
          customer.parent = this.view.queueLeft
        } else {
          this.view.queueRight.addChild(customer)
          customer.parent = this.view.queueRight
        }
      },

summonNewCustomer函式用來建立一個客戶物件,並根據一個隨機數決定客戶是出現在頁面的左上角還是右下角,時鐘每次觸發時,函式tick會被呼叫,在裡面程式碼根據ticks來決定是否建立一個客戶物件,每500個ticks就建立一個客戶物件,當客戶物件出現300個tick後顯示憤怒表情,500個tick後自動從頁面上刪除,完成上面程式碼後,我們就可以看到前面所示的動畫特效了。

更詳細的講解和程式碼除錯演示過程,請點選連結

更多技術資訊,包括作業系統,編譯器,面試演算法,機器學習,人工智慧,請關照我的公眾號:
這裡寫圖片描述