日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

H5 六边形消除游戏开发

發(fā)布時間:2025/3/20 编程问答 63 豆豆
生活随笔 收集整理的這篇文章主要介紹了 H5 六边形消除游戏开发 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

試玩地址,目前只適配pc端。源碼

六邊形游戲的鼻祖應該是這個 hex-frvr,原作者開發(fā)用的是 pixi 游戲引擎,本著快速開發(fā)的理念,本游戲采用 cocos creator,UI 延用 hex-frvr。學習過程中,有借鑒各路實現(xiàn)。此源碼僅供學習使用,謝謝。

預覽

功能介紹

六邊形游戲本質(zhì)是俄羅斯方塊,理解這個對接下來的開發(fā)會有很大的幫助。

本游戲?qū)崿F(xiàn)功能如下:

  • [x] 六邊形棋盤繪制、方塊隨機生成
  • [x] 方塊能否落入棋盤的判定
  • [x] 方塊消除與游戲結(jié)束的判定
  • [x] 各種動畫效果
  • [x] 游戲計分

cocos creator

在講游戲開發(fā)思路前,建議先了解 cocos creator

  • 文檔
  • API

必須了解的 API 有:

  • Game
  • Canvas
  • Scene
  • Node
  • Component
  • Sprite
  • Texture2D
  • Director
  • loader
  • Event
  • Touch
  • Action
  • Vec2
  • Animation
  • AnimationClip
  • Prefab
  • sys

其中,Node、Event、Vec2,是此游戲開發(fā)的重點。

開發(fā)思路

下面從功能逐一介紹開發(fā)思路。

棋盤繪制

棋盤用的是六角網(wǎng)格布局,電子游戲中六角網(wǎng)格的運用沒有方形網(wǎng)格那樣常見,先來簡單了解下六角網(wǎng)格。

六角網(wǎng)格

本文中討論的六角網(wǎng)格使用的都是正六邊形。六角網(wǎng)格最典型的朝向有兩種:水平方向( 頂點朝上 )與豎直方形( 邊線朝上 )。本游戲用的是,頂點朝上的朝向。

細心的同學會發(fā)現(xiàn),圖中有類似坐標系的東西,稱之為軸坐標。

軸坐標

軸坐標系,有時也叫做“梯形坐標系”,是從立方坐標系的三個坐標中取兩個建立的坐標系。由于我們有約束條件 x + y + z = 0,因此第三個坐標其實是多余的。軸坐標適合用于地圖數(shù)據(jù)儲存,也適合用于作為面向玩家的顯示坐標。類似立方坐標,你也可以使用笛卡爾坐標系中的加,減,乘,除等基本運算。

有許多種立方坐標系,因此,也自然有許多種由其衍生的軸坐標系。本游戲,選用的是 q = x 以及 r = z 的情況。這里 q 代表列而 r 表示行。

偏移坐標是人們最先會想到的坐標系,因為它能夠直接使用方形網(wǎng)格的笛卡爾坐標。但不幸的是,偏移坐標系中的一個軸總會顯得格格不入,并且最終會把問題變得復雜化。立方坐標和軸坐標則顯得相得益彰,算法也更簡單明了,只是地圖存儲方面會略微變得復雜一點。所以,使用立方/軸坐標系是較為簡單的。

從六角網(wǎng)格到像素

大致了解了什么是六角網(wǎng)格,接下來了解如何把六角網(wǎng)格轉(zhuǎn)換為像素。

如果使用的軸坐標,那么可以先觀察下圖中示意的單位矢量。在下圖中,箭頭 A→Q 表示的是 q 軸的單位矢量而 A→R 是 r 軸的單位矢量。像素坐標即 q_basis _ q + r_basis _ r。例如,B 點位于 (1, 1),等于 q 與 r 的單位矢量之和。

在網(wǎng)格為 水平 朝向時,六邊形的 高度 為 高度 = size * 2. 相鄰六邊形的 豎直 距離則為 豎直 = 高度 * 3/4。

六邊形的 寬度 為 寬度 = sqrt(3)/2 * 高度。相鄰六邊形的 水平 距離為 水平 = 寬度。

對于本游戲中,取棋盤中心點為,(0,0)。從已知的六角網(wǎng)格坐標(正六邊形)以及六邊形的高度,就可以得到每個正六邊形的坐標。可以得到如下像素轉(zhuǎn)換代碼:

hex2pixel(hex, h) {let size = h / 2;let x = size * Math.sqrt(3) * (hex.q + hex.r / 2);let y = ((size * 3) / 2) * hex.r;return cc.p(x, y);} 復制代碼

網(wǎng)格坐標系生成

坐標系轉(zhuǎn)像素問題解決了,接下來,需要獲得本游戲中六角網(wǎng)格布局相應的坐標系。

這個問題,本質(zhì)是軸坐標系統(tǒng)的地圖存儲。 8)

對半徑為 N 的六邊形布局,當N = max(abs(x), abs(y), abs(z),有 first_column[r] == -N - min(0, r)。最后你訪問的會是 array[r][q + N + min(0, r)]。然而,由于我們可能會把一些 r < 0 的位置作為起點,因此我們也必須偏移行,有 array[r + N][q + N + min(0, r)]。

如本游戲中,棋盤為邊界六邊形個數(shù)為 5 的六角網(wǎng)格布局,生成的坐標系存儲代碼如下:

setHexagonGrid() {this.hexSide = 5;this.hexSide--;for (let q = -this.hexSide; q <= this.hexSide; q++) {let r1 = Math.max(-this.hexSide, -q - this.hexSide);let r2 = Math.min(this.hexSide, -q + this.hexSide);for (let r = r1; r <= r2; r++) {let col = q + this.hexSide;let row = r - r1;if (!this.hexes[col]) {this.hexes[col] = [];}this.hexes[col][row] = this.hex2pixel({ q, r }, this.tileH);}}} 復制代碼

邊界個數(shù)為 6 的六角網(wǎng)格布局,六邊形總數(shù)為 61。接著,只需要遍歷添加背景即可完成棋盤的繪制。

setSpriteFrame(hexes) {for (let index = 0; index < hexes.length; index++) {let node = new cc.Node('frame');let sprite = node.addComponent(cc.Sprite);sprite.spriteFrame = this.tilePic;node.x = hexes[index].x;node.y = hexes[index].y;node.parent = this.node;hexes[index].spriteFrame = node;this.setShadowNode(node);this.setFillNode(node);this.boardFrameList.push(node);}} 復制代碼

至此,棋盤繪制結(jié)束。

方塊隨機生成

方塊的形狀可以千變?nèi)f化,先來看下本游戲事先約定的 23 種形狀。

在前面六角網(wǎng)格的知識基礎上,實現(xiàn)這 23 種形狀并不難。只需要約定好每個形狀對應的軸坐標。

代碼配置如下:

const Tiles = [{type: 1,list: [[[0, 0]]]},{type: 2,list: [[[1, -1], [0, 0], [1, 0], [0, 1]],[[0, 0], [1, 0], [-1, 1], [0, 1]],[[0, 0], [1, 0], [0, 1], [1, 1]]]},{type: 3,list: [[[0, -1], [0, 0], [0, 1], [0, 2]],[[0, 0], [1, -1], [-1, 1], [-2, 2]],[[-1, 0], [0, 0], [1, 0], [2, 0]]]},{type: 4,list: [[[0, 0], [0, 1], [0, -1], [-1, 0]],[[0, 0], [0, -1], [1, -1], [-1, 1]],[[0, 0], [0, 1], [0, -1], [1, 0]],[[0, 0], [1, 0], [-1, 0], [1, -1]],[[0, 0], [1, 0], [-1, 0], [-1, 1]]]},{type: 5,list: [[[0, 0], [0, 1], [0, -1], [1, -1]],[[0, 0], [1, -1], [-1, 1], [-1, 0]],[[0, 0], [1, -1], [-1, 1], [1, 0]],[[0, 0], [1, 0], [-1, 0], [0, -1]],[[0, 0], [1, 0], [-1, 0], [0, 1]]]},{type: 6,list: [[[0, -1], [-1, 0], [-1, 1], [0, 1]],[[-1, 0], [0, -1], [1, -1], [1, 0]],[[0, -1], [1, -1], [1, 0], [0, 1]],[[-1, 1], [0, 1], [1, 0], [1, -1]],[[-1, 0], [-1, 1], [0, -1], [1, -1]],[[-1, 0], [-1, 1], [0, 1], [1, 0]]]} ]; 復制代碼

由于沒有涉及方塊出現(xiàn)的概率,這里就簡單粗暴地用 random 來實現(xiàn)方塊隨機生成。

const getRandomInt = function(min, max) {let ratio = cc.random0To1();return min + Math.floor((max - min) * ratio); }; 復制代碼

網(wǎng)格和方塊都搞定了,蠻喜歡這種簡單的 UI 風格,非常適合游戲開發(fā)的入門學習。接下來處理游戲交互邏輯。

方塊落入棋盤邏輯

方塊與棋盤之間的交互關系是 Drag 與 Drop ,在 cocos creator 中暫時沒發(fā)現(xiàn)有 Drag 相關的組件,目前是通過 touch 事件來模擬。在方塊 touchmove 的過程,需要處理兩件事,第一,檢測拖拽過程中方塊是否與棋盤有交叉,就是游戲里所謂的 碰撞檢測,cc 有提供相應的碰撞組件,但不夠靈活,因為我們要得到的是方塊與棋盤重合關系(ps:并不需要完全重合),所以還是用腳本來模擬實現(xiàn),cc 為此提供了很多 API,主要都與 vec2 有關。第二,檢測方塊是否可以落入棋盤。

碰撞檢測 (重合判定)

方塊與棋盤其實都是由正六邊形組合而成,這里有種比較簡單地方式來判斷兩者是否有重合部分,即判斷兩個六邊形圓心的距離,當小于設定值,則認為有重合。

這邊簡單起見,特意將棋盤與方塊的父節(jié)點的坐標系原點設為同一個(中心點)。cocos 坐標系可參考這篇

由于方塊是相對于它的父級中心點定位,而它的父級是相對于 Canvas 定位,因此可以通過 cc.pAdd(this.node.position, tile.position) 來獲取方塊相對于棋盤原點的坐標值。接著遍歷棋盤內(nèi)六邊形坐標值,來檢查拖拽進入的六邊形與棋盤哪些存在重合關系。相關代碼如下:

checkCollision(event) {const tiles = this.node.children; // this.node 為 方塊的父級,拖拽改變的是這個節(jié)點的坐標this.boardTiles = []; // 保存棋盤與方塊重合部分。this.fillTiles = []; // 保存方塊當前重合的部分。for (let i = 0; i < tiles.length; i++) {const tile = tiles[i];const pos = cc.pAdd(this.node.position, tile.position); // pAdd 是cc早期提供的 api,可用 vec2 中向量相加替換const boardTile = this.checkDistance(pos);if (boardTile) {this.fillTiles.push(tile);this.boardTiles.push(boardTile);}}},checkDistance(pos) {const distance = 50;const boardFrameList = this.board.boardFrameList;for (let i = 0; i < boardFrameList.length; i++) {const frameNode = boardFrameList[i];const nodeDistance = cc.pDistance(frameNode.position, pos);if (nodeDistance <= distance) {return frameNode;}}}, 復制代碼

在拖拽過程,實時保存棋盤有重合關系的六邊形,用于判定方塊是否可以落入棋盤

落子判定

只要方塊的個數(shù)與棋盤所在區(qū)域可填充部分(棋盤里面沒有方塊)數(shù)目一致,則認為可以落子。

checkCanDrop() {const boardTiles = this.boardTiles; // 當前棋盤與方塊重合部分。const fillTiles = this.node.children; // 當前拖拽的方塊總數(shù)const boardTilesLength = boardTiles.length;const fillTilesLength = fillTiles.length;// 如果當前棋盤與方塊重合部分為零以及與方塊數(shù)目不一致,則判定為不能落子。if (boardTilesLength === 0 || boardTilesLength != fillTilesLength) {return false;}// 如果方塊內(nèi)以及存在方塊,則判定為不能落子。for (let i = 0; i < boardTilesLength; i++) {if (this.boardTiles[i].isFulled) {return false;}}return true;}, 復制代碼

落子提示

得到落入與否的判定值后,需要給用戶可以落子的提示。這邊的一個做法是,在生成棋盤之前就給每個棋盤格子節(jié)點新建一個 name 為 shadowNode 的子節(jié)點。接著只需要修改符合條件的節(jié)點的spriteFrame為當前拖拽方塊的spriteFrame,同時降低透明度即可。代碼如下:

dropPrompt(canDrop) {const boardTiles = this.boardTiles;const boardTilesLength = boardTiles.length;const fillTiles = this.fillTiles;this.resetBoardFrames();if (canDrop) {for (let i = 0; i < boardTilesLength; i++) {const shadowNode = boardTiles[i].getChildByName('shadowNode');shadowNode.opacity = 100;const spriteFrame = fillTiles[i].getComponent(cc.Sprite).spriteFrame;shadowNode.getComponent(cc.Sprite).spriteFrame = spriteFrame;}}} 復制代碼

落入邏輯

至此,方塊的 touchmove 事件添加完畢。接下來,需要做的是,拖拽結(jié)束后的相關邏輯處理。

兩種情況,方塊可以落入,與方塊不能落入。前面已經(jīng)獲取了是否可以落入的判定。那接下來就是添加相應的處理。

可以落入的情況需要做的是在棋盤添加對應方塊,方塊添加結(jié)束后重新隨機生成新的方塊。不可以落入則讓拖拽的方塊返回原位置。

在添加方塊上用了跟之前說到的落入提示類似的方法,給棋盤內(nèi)每個格子節(jié)點下新增一個名為 fillNode 的節(jié)點,方塊落入都跟這個節(jié)點有關。

tileDrop() {this.resetBoardFrames();if (this.checkCanDrop()) {const boardTiles = this.boardTiles;const fillTiles = this.fillTiles;const fillTilesLength = fillTiles.length;for (let i = 0; i < fillTilesLength; i++) {const boardTile = boardTiles[i];const fillTile = fillTiles[i];const fillNode = boardTile.getChildByName('fillNode');const spriteFrame = fillTile.getComponent(cc.Sprite).spriteFrame;boardTile.isFulled = true;fillNode.getComponent(cc.Sprite).spriteFrame = spriteFrame;this.resetTile();}this.board.curTileLength = fillTiles.length;this.board.node.emit('dropSuccess');} else {this.backSourcePos();}this.board.checkLose();} 復制代碼

消除邏輯

棋盤有了,也可以判斷方塊是否可以落入棋盤。接下來要做的就是消除邏輯的處理,之前說,六邊形消除游戲就是俄羅斯方塊的衍生版,其實就是多了幾個消除方向,來看張圖:

如果把這個棋盤看成數(shù)組,即從左斜方向依次添加 [0,1,2.....],最終可以得到如下消除規(guī)則:

const DelRules = [//左斜角[0, 1, 2, 3, 4],[5, 6, 7, 8, 9, 10],[11, 12, 13, 14, 15, 16, 17],[18, 19, 20, 21, 22, 23, 24, 25],[26, 27, 28, 29, 30, 31, 32, 33, 34],[35, 36, 37, 38, 39, 40, 41, 42],[43, 44, 45, 46, 47, 48, 49],[50, 51, 52, 53, 54, 55],[56, 57, 58, 59, 60],//右斜角[26, 35, 43, 50, 56],[18, 27, 36, 44, 51, 57],[11, 19, 28, 37, 45, 52, 58],[5, 12, 20, 29, 38, 46, 53, 59],[0, 6, 13, 21, 30, 39, 47, 54, 60],[1, 7, 14, 22, 31, 40, 48, 55],[2, 8, 15, 23, 32, 41, 49],[3, 9, 16, 24, 33, 42],[4, 10, 17, 25, 34],//水平[0, 5, 11, 18, 26],[1, 6, 12, 19, 27, 35],[2, 7, 13, 20, 28, 36, 43],[3, 8, 14, 21, 29, 37, 44, 50],[4, 9, 15, 22, 30, 38, 45, 51, 56],[10, 16, 23, 31, 39, 46, 52, 57],[17, 24, 32, 40, 47, 53, 58],[25, 33, 41, 48, 54, 59],[34, 42, 49, 55, 60] ]; 復制代碼

規(guī)則有了,接著添加消除邏輯,直接看代碼:

deleteTile() {let fulledTilesIndex = []; // 存儲棋盤內(nèi)有方塊的的索引let readyDelTiles = []; // 存儲待消除方塊const boardFrameList = this.boardFrameList;this.isDeleting = true; // 方塊正在消除的標識,用于后期添加動畫時,充當異步狀態(tài)鎖this.addScore(this.curTileLength, true);// 首先獲取棋盤內(nèi)存在方塊的格子信息for (let i = 0; i < boardFrameList.length; i++) {const boardFrame = boardFrameList[i];if (boardFrame.isFulled) {fulledTilesIndex.push(i);}}for (let i = 0; i < DelRules.length; i++) {const delRule = DelRules[i]; // 消除規(guī)則獲取// 逐一獲取規(guī)則數(shù)組與存在方塊格子數(shù)組的交集let intersectArr = _.arrIntersect(fulledTilesIndex, delRule);if (intersectArr.length > 0) {// 判斷兩數(shù)組是否相同,相同則將方塊添加到待消除數(shù)組里const isReadyDel = _.checkArrIsEqual(delRule, intersectArr);if (isReadyDel) {readyDelTiles.push(delRule);}}}// 開始消除let count = 0;for (let i = 0; i < readyDelTiles.length; i++) {const readyDelTile = readyDelTiles[i];for (let j = 0; j < readyDelTile.length; j++) {const delTileIndex = readyDelTile[j];const boardFrame = this.boardFrameList[delTileIndex];const delNode = boardFrame.getChildByName('fillNode');boardFrame.isFulled = false;// 這里可以添加相應消除動畫const finished = cc.callFunc(() => {delNode.getComponent(cc.Sprite).spriteFrame = null;delNode.opacity = 255;count++;}, this);delNode.runAction(cc.sequence(cc.fadeOut(0.3), finished));}}if (count !== 0) {this.addScore(count);this.checkLose();}this.isDeleting = false;} 復制代碼

游戲結(jié)束邏輯

三個方塊都無法放入棋盤,則認為游戲結(jié)束。

首先得到未填充的棋盤格子信息,再將三個方塊逐一放入未填充區(qū)域判斷是否可以放入。代碼如下:

checkLose() {let canDropCount = 0;const tiles = this.node.children;const tilesLength = tiles.length;const boardFrameList = this.board.boardFrameList;const boardFrameListLength = boardFrameList.length;// TODO: 存在無效檢測的情況,可優(yōu)化for (let i = 0; i < boardFrameListLength; i++) {const boardNode = boardFrameList[i];let srcPos = cc.p(boardNode.x, boardNode.y);let count = 0;if (!boardNode.isFulled) {// 過濾出未填充的棋盤格子for (let j = 0; j < tilesLength; j++) {let len = 27; // 設定重合判定最小間距// 將方塊移到未填充的棋盤格子原點,并獲取當前各方塊坐標值let tilePos = cc.pAdd(srcPos, cc.p(tiles[j].x, tiles[j].y));// 遍歷棋盤格子,判斷方塊中各六邊形是否可以放入for (let k = 0; k < boardFrameListLength; k++) {const boardNode = boardFrameList[k];let dis = cc.pDistance(cc.p(boardNode.x, boardNode.y), tilePos);if (dis <= len && !boardNode.isFulled) {count++;}}}if (count === tilesLength) {canDropCount++;}}}if (canDropCount === 0) {return true;} else {return false;}} 復制代碼

計分制

計分規(guī)則千變?nèi)f化,看你需求。一般方塊放入與消除均可加分。

scoreRule(count, isDropAdd) {let x = count + 1;let addScoreCount = isDropAdd ? x : 2 * x * x;return addScoreCount;} 復制代碼

致謝

項目屬于入門級別,初次接觸 cocos creator 游戲開發(fā),多數(shù)參考了網(wǎng)上一些六邊形開源游戲。在此感謝開源,項目有融入自己的一些方法,比如處理六角網(wǎng)格那塊,但是消除規(guī)則,還需要接觸更多知識后才能完善。先寫這么一篇入門級的,后續(xù)再深入,希望對一些像我一樣剛接觸游戲開發(fā)的人能有一些幫助。后續(xù)可能會結(jié)合適當?shù)睦?#xff0c;講一些,cocos creator 動畫,粒子系統(tǒng),物理系統(tǒng),webgl等。

源碼

參考

  • hexagons
  • 六角網(wǎng)格
  • LBXGame

總結(jié)

以上是生活随笔為你收集整理的H5 六边形消除游戏开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。