主頁 > 移動端開發 > 【1024個人爆款文章】Android 安卓原生UI實作游戲《俄羅斯方塊》,演算法太多,把我寫崩潰了,附原始碼

【1024個人爆款文章】Android 安卓原生UI實作游戲《俄羅斯方塊》,演算法太多,把我寫崩潰了,附原始碼

2021-11-06 08:57:36 移動端開發

文章目錄

    • 效果圖
    • 游戲概念
    • 操作設計
    • 演算法規則
    • 整體演算法概述
    • 代碼實操
      • 操作設計
      • 可視化View
      • MainActivity
    • 原始碼下載


效果圖

廢話不多說,先干效果圖,原始碼在文章末尾

效果圖
在這里插入圖片描述


游戲概念

《俄羅斯方塊》是由七種方塊,開始時,一個 落下期間,玩家可以以90度為單位旋轉方塊,以格子為單位左右移動方塊,或讓方塊加速落下,當方塊下落到區域最下方或著落到其他方塊上無法再向下移動時,就會固定在該處,然后一個新的隨機的方塊會出現在區域上方開始落下,當區域中某一橫行(同時消除的行數越多,得分指數級上升,當固定的方塊堆到區域最頂端而無法消除層數時,游戲就會結束,


操作設計

起初我想的是在螢屏底部做按鍵操作的,可后來一想,這也太扯淡了
在這里插入圖片描述
現在是觸屏手機,做按鈕的話不好按,容易誤觸,所定以下的手勢:

  1. 向上滑動:順時針旋轉正在下落的方塊90°
  2. 向左滑動:正在下落的方塊向左移動一個方格單位
  3. 向右滑動,正在下落的方塊向右移動一個方格單位
  4. 向左持續滑動:正在下落的方塊向左持續移動
  5. 向右持續滑動:正在下落的方塊向右持續移動
  6. 向下滑動:方塊直落底部

說到扯淡但,來個更扯淡的操作吧,你可以設定成
向下滑動和向上滑動操作相反
向左滑動和向右滑動操作相反
向左持續滑動和向右持續滑動操作相反
(反正這個操作沒多大作業量,寫個if調換以下方法位置就行…)


演算法規則

當行滿足無空格時,消除當前行,有幾行會消除幾行,一次消除的越多,得分也就越多,咱就不定1分2分的了,這么玩著沒啥意思
在這里插入圖片描述

干脆消除1行100分
一次性消除2行是200分
一次性消除3行是400分
一次性消除4行是800分

方塊下落速度會越來越快,剛開始下落速度為750ms/格,逐漸累加,最快是150ms/格
按方塊下落的個數計算,每5個方塊為1個遞增,遞增20ms/格(這邊是我自己想的)


整體演算法概述

UI使用自定義View來實作,命名為GameView,背后的運行邏輯通過二維陣列來控制,10X20布局;
當方塊每下落一格和消除時,重繪整個GameView;
0表示可操作空間占位;
1表示已固定方塊型別一方塊占位,11表示正在下落的型別一方塊
2表示已固定方塊型別二方塊占位,12表示正在下落的型別二方塊
3表示已固定方塊型別三方塊占位,13表示正在下落的型別三方塊
以此類推共7種


代碼實操

“只說不練的把式,光練不說傻把式”,這是程式員的最大忌諱,昨天媳婦給力(做的飯好吃),今天快快地擼代碼!

操作設計

創建二維陣列,并允許可以靜態呼叫,其默認值為0,封裝多個公共呼叫方法!詳細看以下注釋

package com.blog.tetris.operation

import java.lang.Thread
import kotlin.math.pow

/**
 * 操作類,包含地圖、游戲的主運行邏輯、操作入口
 */
object Operation {

    //地圖
    //0表示可操作空間占位(默認值)
    //1表示已固定方塊占位
    //2表示正在下落方塊占位
    val gameMap = Array(20) { Array(10) { 0 } }

    //方塊下落速度
    //初始默認值750毫秒(0.75s)/格
    var speed = 750L

    var isInGame = false

    //當前旋轉度,默認是1
    private var rotate = 1

    //游戲分數
    var score = 0L

    //要生成的方塊
    var forecastType = 0

    //已經生成的方塊個數,根據此變數提升方塊下落速度
    var boxNum = 0

    //游戲執行緒
    private lateinit var thread: Thread

    /**
     * 開始游戲
     */
    fun startGame() {

        if (isInGame) return

        isInGame = true

        thread = Thread {

            while (isInGame) {

                Thread.sleep(speed)

                val downBox = downBox()
                //是否無法繼續下移
                if (!downBox) {

                    //固定模塊
                    fixedMoveBox()

                    //消除方塊
                    eliminate()

                    //生成一個新的模塊
                    createTopBox()

                }
                changeListener?.onChange()
            }
        }

        thread.start()

    }

    //消除行數量
    var fullLine = 0

    /**
     * 消除方塊
     * 從下到上遞回掃描方塊,一行一行消除
     */
    private fun eliminate(): Int {

        for (i in gameMap.size - 1 downTo 0) {

            //當前行是否滿足
            var isFull = true

            for (j in gameMap[i].indices) if (gameMap[i][j] == 0) isFull = false

            if (isFull) {

                fullLine++

                for (j in gameMap[i].indices) {
                    gameMap[i][j] = 0
                }

                for (k in i downTo 1) for (n in gameMap[k].indices)
                    gameMap[k][n] = gameMap[k - 1][n]

                eliminate()
            }
        }

        if (fullLine > 0) {

            //根據消除行算出分數
            score += (2.toDouble().pow((fullLine - 1).toDouble())).toLong() * 100

            changeListener?.onChange()
            Thread.sleep(200)
            fullLine = 0
        }

        return fullLine
    }

    /**
     * 變形-正在下落的模塊順時針旋轉90度
     */
    fun deformation() {

        //掃描正在下落的方塊
        val boxArr = mutableListOf<Coordinate>()

        /**
         * 重置置為空
         */
        fun reset() {
            for (coordinate in boxArr) gameMap[coordinate.x][coordinate.y] = 0
        }

        //當前下落模塊左上角坐標
        var boxMinX = 20
        var boxMinY = 10

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 12..17) {
            boxArr.add(Coordinate(i, j, gameMap[i][j]))
            if (i < boxMinX) boxMinX = i
            if (j < boxMinY) boxMinY = j
        }

        if (0 == boxArr.size) return

        val boxType = boxArr[0].value

        if (0 != boxArr.size) {

            //判斷是否符合旋轉標準
            //有足夠的空間變形(不觸碰左、右、下邊界,不觸碰其他固定方塊)

            //取出模塊型別
            when (boxType) {

                12 -> {
                    when (rotate) {
                        1 -> {
                            //判斷是否符合旋轉條件
                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX][boxMinY + 1] = 12
                                gameMap[boxMinX + 1][boxMinY + 1] = 12
                                gameMap[boxMinX + 2][boxMinY + 1] = 12
                                rotate = 2
                            }
                        }
                        2 -> {
                            if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX][boxMinY + 2] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX][boxMinY + 1] = 12
                                gameMap[boxMinX][boxMinY + 2] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                rotate = 3
                            }
                        }
                        3 -> {
                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                gameMap[boxMinX + 2][boxMinY] = 12
                                gameMap[boxMinX + 2][boxMinY + 1] = 12
                                rotate = 4
                            }
                        }
                        4 -> {
                            if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY + 2] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 2] = 12
                                gameMap[boxMinX + 1][boxMinY] = 12
                                gameMap[boxMinX + 1][boxMinY + 1] = 12
                                gameMap[boxMinX + 1][boxMinY + 2] = 12
                                rotate = 1
                            }
                        }
                    }
                }

                13 -> {
                    when (rotate) {
                        1 -> {
                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY] = 13
                                gameMap[boxMinX + 1][boxMinY + 1] = 13
                                gameMap[boxMinX + 2][boxMinY] = 13
                                rotate = 2
                            }
                        }
                        2 -> {
                            if (
                                boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 13
                                gameMap[boxMinX][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY + 1] = 13
                                gameMap[boxMinX + 1][boxMinY + 2] = 13
                                rotate = 1
                            }
                        }
                    }
                }

                14 -> {
                    when (rotate) {
                        1 -> {

                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY + 1] = 14
                                gameMap[boxMinX + 2][boxMinY + 1] = 14
                                rotate = 2
                            }
                        }
                        2 -> {

                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX][boxMinY + 2] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 14
                                gameMap[boxMinX][boxMinY + 2] = 14
                                gameMap[boxMinX + 1][boxMinY] = 14
                                gameMap[boxMinX + 1][boxMinY + 1] = 14
                                rotate = 1
                            }
                        }
                    }
                }

                15 -> {
                    when (rotate) {
                        1 -> {

                            if (boxMinX + 2 < 20
                                && boxMinY + 1 < 10
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY + 1] = 15
                                gameMap[boxMinX + 2][boxMinY] = 15
                                gameMap[boxMinX + 2][boxMinY + 1] = 15
                                rotate = 2
                            }
                        }

                        2 -> {
                            if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX][boxMinY + 2] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX][boxMinY + 2] = 15
                                gameMap[boxMinX + 1][boxMinY + 2] = 15
                                rotate = 3
                            }
                        }

                        3 -> {
                            if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 2][boxMinY] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY] = 15
                                gameMap[boxMinX + 2][boxMinY] = 15
                                rotate = 4
                            }
                        }

                        4 -> {
                            if (boxMinX + 1 < 20
                                && boxMinY + 2 < 10
                                && gameMap[boxMinX][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
                                && gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
                            ) {
                                reset()
                                gameMap[boxMinX][boxMinY] = 15
                                gameMap[boxMinX + 1][boxMinY] = 15
                                gameMap[boxMinX + 1][boxMinY + 1] = 15
                                gameMap[boxMinX + 1][boxMinY + 2] = 15
                                rotate = 1
                            }
                        }
                    }
                }

                16 -> {
                    when (rotate) {
                        1 -> {
                            val midpoint = boxArr[2]

                            if (midpoint.x - 1 >= 0
                                && midpoint.x + 1 < 20
                                && midpoint.y - 1 >= 0
                            ) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x + 1][midpoint.y] = 16
                                rotate = 2
                            }
                        }
                        2 -> {
                            val midpoint = boxArr[2]

                            if (midpoint.x - 1 >= 0
                                && midpoint.x + 1 < 20
                                && midpoint.y - 1 >= 0
                                && midpoint.y + 1 < 10
                            ) {
                                reset()
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y + 1] = 16
                                gameMap[midpoint.x + 1][midpoint.y] = 16
                                rotate = 3
                            }
                        }

                        3 -> {
                            val midpoint = boxArr[1]
                            reset()

                            gameMap[midpoint.x - 1][midpoint.y] = 16
                            gameMap[midpoint.x][midpoint.y] = 16
                            gameMap[midpoint.x][midpoint.y + 1] = 16
                            gameMap[midpoint.x + 1][midpoint.y] = 16

                            rotate = 4
                        }
                        4 -> {
                            val midpoint = boxArr[1]

                            if (midpoint.y - 1 >= 0) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y - 1] = 16
                                gameMap[midpoint.x][midpoint.y] = 16
                                gameMap[midpoint.x][midpoint.y + 1] = 16
                                rotate = 1
                            }
                        }
                    }
                }

                17 -> {

                    val midpoint = boxArr[1]

                    when (rotate) {

                        1 -> {
                            if (midpoint.x - 1 >= 0 && midpoint.x + 2 < 20) {
                                reset()
                                gameMap[midpoint.x - 1][midpoint.y] = 17
                                gameMap[midpoint.x][midpoint.y] = 17
                                gameMap[midpoint.x + 1][midpoint.y] = 17
                                gameMap[midpoint.x + 2][midpoint.y] = 17
                                rotate = 2
                            }

                        }
                        2 -> {
                            if (midpoint.y - 1 >= 0 && midpoint.y + 2 < 10) {
                                reset()
                                gameMap[midpoint.x][midpoint.y - 1] = 17
                                gameMap[midpoint.x][midpoint.y] = 17
                                gameMap[midpoint.x][midpoint.y + 1] = 17
                                gameMap[midpoint.x][midpoint.y + 2] = 17
                                rotate = 1
                            }
                        }
                    }
                }
            }

            changeListener?.onChange()
        }

    }

    /**
     * 向左移動正在下落的方塊
     */
    fun toLeft() {
        val leftArr = mutableListOf<Coordinate>()

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
            leftArr.add(Coordinate(i, j, gameMap[i][j]))

        if (0 == leftArr.size) return

        //是否允許向左移動
        var toLeftStatus = true

        //向左移動條件:符合移動方塊并且任何左側為空方塊
        for (coordinate in leftArr) {
            if (coordinate.y == 0 || (coordinate.y > 0 && gameMap[coordinate.x][coordinate.y - 1] in 1..7)) {
                toLeftStatus = false
                break
            }
        }

        //向左移動
        if (toLeftStatus) {
            for (i in 0 until leftArr.size) {
                gameMap[leftArr[i].x][leftArr[i].y - 1] = leftArr[0].value
                gameMap[leftArr[i].x][leftArr[i].y] = 0
            }
            changeListener?.onChange()
        }
    }

    /**
     * 向右移動正在下落的方塊
     */
    fun toRight() {

        val rightArr = mutableListOf<Coordinate>()

        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
            rightArr.add(Coordinate(i, j, gameMap[i][j]))

        if (0 == rightArr.size) return

        //是否允許向右移動
        var toRightStatus = true

        //向右移動條件:符合移動方塊并且任何右側為空方塊
        for (coordinate in rightArr) if (coordinate.y == 9 || (coordinate.y < 10 && gameMap[coordinate.x][coordinate.y + 1] in 1..7)) {
            toRightStatus = false
            break
        }

        //向右移動
        if (toRightStatus) {
            for (i in rightArr.size - 1 downTo 0) {
                gameMap[rightArr[i].x][rightArr[i].y + 1] = rightArr[0].value
                gameMap[rightArr[i].x][rightArr[i].y] = 0
            }
            changeListener?.onChange()
        }
    }

    /**
     * 直接下落到底部
     */
    fun downBottom() {
        val downArr = mutableListOf<Coordinate>()
        for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) {
            val coordinate = gameMap[i][j]
            if (coordinate in 11..17) downArr.add(Coordinate(i, j, coordinate))
        }

        if (0 != downArr.size) {

            var isFixed = false

            val valueTemp = downArr[0].value

            for (coordinate in downArr) if (coordinate.x + 1 >= 20 || gameMap[coordinate.x + 1][coordinate.y] in 1..7) return

            for (coordinate in downArr) {

                gameMap[coordinate.x][coordinate.y] = 0
                gameMap[coordinate.x + 1][coordinate.y] = valueTemp

                //判斷是否落到了最后
                if (coordinate.x + 1 >= 19 || gameMap[coordinate.x + 2][coordinate.y] in 1..7)
                    isFixed = true
            }

            if (!isFixed) downBottom() else changeListener?.onChange()

        } else changeListener?.onChange()
    }

    /**
     * 在頂部創建一個新的模塊
     */
    private fun createTopBox() {

        //判斷是否有預測,沒有預測則生成方塊
        if (0 == forecastType) forecastType = (1..7).random()

        //重置旋轉度數
        rotate = 1

        //每生成5個方塊增加一次方塊下落速度
        if (++boxNum % 5 == 0 && speed > 150) speed -= 20

        //游戲是否結束
        var gameOverStatus = false

        //生成下落方塊
        when (forecastType) {
            1 -> if (gameMap[0][4] !in 1..7
                && gameMap[0][5] !in 1..7
                && gameMap[1][4] !in 1..7
                && gameMap[1][5] !in 1..7
            ) {
                gameMap[0][4] = 11
                gameMap[0][5] = 11
                gameMap[1][4] = 11
                gameMap[1][5] = 11
            } else gameOverStatus = true

            2 -> if (gameMap[0][5] !in 1..7
                && gameMap[1][3] !in 1..7
                && gameMap[1][4] !in 1..7
                && gameMap[1][5] !in 1..7
            ) {
                gameMap[0][5] = 12
                gameMap[1][3] = 12
                gameMap[1][4] = 12
                gameMap[1][5] = 12
            } else gameOverStatus = true

            3 -> if (gameMap[0][3] !in 1..7
                && gameMap[0][4] !in 1..7
                && gameMap[1][4] !in 1..7
                && gameMap[1][5] !in 1..7
            ) {
                gameMap[0][3] = 13
                gameMap[0][4] = 13
                gameMap[1][4] = 13
                gameMap[1][5] = 13
            } else gameOverStatus = true

            4 -> if (gameMap[0][4] !in 1..7
                && gameMap[0][5] !in 1..7
                && gameMap[1][3] !in 1..7
                && gameMap[1][4] !in 1..7
            ) {
                gameMap[0][4] = 14
                gameMap[0][5] = 14
                gameMap[1][3] = 14
                gameMap[1][4] = 14
            } else gameOverStatus = true

            5 -> if (gameMap[0][3] !in 1..7
                && gameMap[1][3] !in 1..7
                && gameMap[1][4] !in 1..7
                && gameMap[1][5] !in 1..7
            ) {
                gameMap[0][3] = 15
                gameMap[1][3] = 15
                gameMap[1][4] = 15
                gameMap[1][5] = 15
            } else gameOverStatus = true

            6 -> if (gameMap[0][4] !in 1..7
                && gameMap[1][3] !in 1..7
                && gameMap[1][4] !in 1..7
                && gameMap[1][5] !in 1..7
            ) {
                gameMap[0][4] = 16
                gameMap[1][3] = 16
                gameMap[1][4] = 16
                gameMap[1][5] = 16
            } else gameOverStatus = true

            7 -> if (gameMap[0][3] !in 1..7
                && gameMap[0][4] !in 1..7
                && gameMap[0][5] !in 1..7
                && gameMap[0][6] !in 1..7
            ) {
                gameMap[0][3] = 17
                gameMap[0][4] = 17
                gameMap[0][5] = 17
                gameMap[0][6] = 17
            } else gameOverStatus = true
        }

        //生成預測方塊
        forecastType = (1..7).random()

        if (gameOverStatus) {

            //回呼游戲結束
            changeListener?.gameOver(score)

            //重置游戲資料
            speed = 750L
            isInGame = false
            rotate = 1
            score = 0L
            forecastType = 0
            boxNum = 0
            for (i in gameMap.indices) for (j in gameMap[i].indices) gameMap[i][j] = 0
        }
    }

    /**
     * 固定移動中的方塊方塊
     */
    private fun fixedMoveBox() {
        for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
            gameMap[i][j] = gameMap[i][j] - 10
    }

    /**
     * 方塊下落
     */
    private fun downBox(): Boolean {

        //掃描需要下落的方塊
        val downArr = mutableListOf<Coordinate>()
        for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) if (gameMap[i][j] in 11..17)
            downArr.add(Coordinate(i, j, gameMap[i][j]))

        //判斷是否無法繼續下移
        if (downArr.size == 0) return false

        //判斷是否還能繼續下移
        //判斷條件:任何一個正在移動的方塊是否達到了第20行或者觸碰到了固定方塊
        for (i in 0 until downArr.size) if (downArr[i].x + 1 > 19 || gameMap[downArr[i].x + 1][downArr[i].y] in 1..7) return false

        val coordinateTempValue = downArr[0]
        for (j in 0 until downArr.size) {
            val coordinateTemp = downArr[j]
            gameMap[coordinateTemp.x][coordinateTemp.y] = 0
            gameMap[coordinateTemp.x + 1][coordinateTemp.y] = coordinateTempValue.value
        }

        return true
    }

    //游戲實時變化回呼
    var changeListener: ChangeListener? = null

    /**
     * 游戲實時變化回呼
     */
    interface ChangeListener {
        fun onChange()
        fun gameOver(score: Long)
    }

    /**
     * 坐標物體類
     */
    data class Coordinate(
        var x: Int,
        var y: Int,
        var value: Int
    )

}

可視化View

資料邏輯寫完了,要讓用戶看的見呀,通過自定義View來實作,詳細看以下注釋

package com.blog.tetris.view

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.blog.tetris.operation.Operation

class GameView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    //方塊大小
    val boxSize = 70F

    //方塊邊框大小
    private val edge = boxSize / 4F

    //地圖繪制
    private var mapPaint: Paint = Paint()

    //方塊繪制
    private var boxPaint: Paint = Paint()

    //預測方塊繪制
    private var forecastPaint: Paint = Paint()

    //分數繪制
    private var scorePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

    private var leftMargin = 0
    private var topMargin = 5 * boxSize

    //方塊顏色
    private var color10 = Color.parseColor("#0302FF")
    private var color11 = Color.parseColor("#3437F9")
    private var color12 = Color.parseColor("#6568FC")
    private var color13 = Color.parseColor("#00089C")
    private var color14 = Color.parseColor("#00027B")

    private var color20 = Color.parseColor("#FEFF04")
    private var color21 = Color.parseColor("#FDFE3E")
    private var color22 = Color.parseColor("#FCFD65")
    private var color23 = Color.parseColor("#ACA706")
    private var color24 = Color.parseColor("#717804")

    private var color30 = Color.parseColor("#FB0101")
    private var color31 = Color.parseColor("#F33A36")
    private var color32 = Color.parseColor("#F66A64")
    private var color33 = Color.parseColor("#A80400")
    private var color34 = Color.parseColor("#7E0003")

    private var color40 = Color.parseColor("#06FD06")
    private var color41 = Color.parseColor("#37F936")
    private var color42 = Color.parseColor("#68FD67")
    private var color43 = Color.parseColor("#0AA109")
    private var color44 = Color.parseColor("#037407")

    private var color50 = Color.parseColor("#FF7F04")
    private var color51 = Color.parseColor("#F89A38")
    private var color52 = Color.parseColor("#F8B56A")
    private var color53 = Color.parseColor("#A75304")
    private var color54 = Color.parseColor("#723709")

    private var color60 = Color.parseColor("#F505F8")
    private var color61 = Color.parseColor("#FB36FB")
    private var color62 = Color.parseColor("#FF66F9")
    private var color63 = Color.parseColor("#A801A7")
    private var color64 = Color.parseColor("#780378")

    private var color70 = Color.parseColor("#01FFFD")
    private var color71 = Color.parseColor("#37FDFE")
    private var color72 = Color.parseColor("#69FAF9")
    private var color73 = Color.parseColor("#08A5AA")
    private var color74 = Color.parseColor("#01746F")

    init {
        scorePaint.color = Color.parseColor("#FFFFFF")
        mapPaint.color = Color.parseColor("#000000")
        scorePaint.textSize = boxSize / 1.8F
        boxPaint.isAntiAlias = true
        forecastPaint.isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas?) {

        canvas?.let {

            //算出地圖邊界距離螢屏左側距離(游戲地圖放置在View中間)
            leftMargin = width / 2 - boxSize.toInt() * 5

            //繪制地圖
            drawMap(it)

            //繪制方塊
            drawBoxByArr(it)

            //繪制分數
            drawScore(it)

            //繪制預測
            drawForecast(it)
        }
    }

    /**
     * 繪制分數
     */
    private fun drawScore(canvas: Canvas) {

        //繪制標題
        canvas.drawText(
            "得分",
            leftMargin.toFloat() + boxSize / 2,
            scorePaint.textSize + boxSize / 2,
            scorePaint
        )

        //繪制得分
        canvas.drawText(
            Operation.score.toString(),
            leftMargin.toFloat() + boxSize / 2,
            scorePaint.textSize + boxSize / 2 + boxSize,
            scorePaint
        )
    }

    /**
     * 繪制預測
     */
    private fun drawForecast(canvas: Canvas) {

        //繪制標題
        canvas.drawText(
            "下一個",
            width / 2F,
            scorePaint.textSize + boxSize / 2,
            scorePaint
        )

        //繪制方塊
        val forecastMap = Array(2) { Array(4) { 0 } }

        when (Operation.forecastType) {
            1 -> {
                forecastMap[0][0] = 1
                forecastMap[0][1] = 1
                forecastMap[1][0] = 1
                forecastMap[1][1] = 1
            }
            2 -> {
                forecastMap[0][2] = 2
                forecastMap[1][0] = 2
                forecastMap[1][1] = 2
                forecastMap[1][2] = 2
            }
            3 -> {
                forecastMap[0][0] = 3
                forecastMap[0][1] = 3
                forecastMap[1][1] = 3
                forecastMap[1][2] = 3
            }
            4 -> {
                forecastMap[0][1] = 4
                forecastMap[0][2] = 4
                forecastMap[1][0] = 4
                forecastMap[1][1] = 4
            }
            5 -> {
                forecastMap[0][0] = 5
                forecastMap[1][0] = 5
                forecastMap[1][1] = 5
                forecastMap[1][2] = 5
            }
            6 -> {
                forecastMap[0][1] = 6
                forecastMap[1][0] = 6
                forecastMap[1][1] = 6
                forecastMap[1][2] = 6
            }
            7 -> {
                forecastMap[0][0] = 7
                forecastMap[0][1] = 7
                forecastMap[0][2] = 7
                forecastMap[0][3] = 7
            }
        }

        //繪制預測方塊
        val boxColor = getBoxColor(Operation.forecastType)

        for (i in forecastMap.indices) {
            for (j in forecastMap[i].indices) {
                if (forecastMap[i][j] > 0) {

                    val left = width / 2F + boxSize * j
                    val top = i * boxSize + boxSize * 1.5F
                    val right = width / 2F + boxSize * j + boxSize
                    val bottom = (i + 1) * boxSize + boxSize * 1.5F

                    //繪制方塊
                    forecastPaint.color = boxColor[0]
                    canvas.drawRect(left, top, right, bottom, forecastPaint)

                    //繪制單個模塊四周梯形
                    val path1 = Path()
                    path1.moveTo(left, bottom)
                    path1.lineTo(left, bottom - boxSize)
                    path1.lineTo(left + edge, bottom - boxSize + edge)
                    path1.lineTo(left + edge, bottom - edge)
                    path1.close()

                    val path2 = Path()
                    path2.moveTo(left + edge, top + edge)
                    path2.lineTo(left, top)
                    path2.lineTo(right, top)
                    path2.lineTo(right - edge, top + edge)
                    path2.close()

                    val path3 = Path()
                    path3.moveTo(right - edge, bottom - edge)
                    path3.lineTo(right - edge, top + edge)
                    path3.lineTo(right, top)
                    path3.lineTo(right, bottom)
                    path3.close()

                    val path4 = Path()
                    path4.moveTo(left, bottom)
                    path4.lineTo(left + edge, bottom - edge)
                    path4.lineTo(right - edge, bottom - edge)
                    path4.lineTo(right, bottom)
                    path4.close()

                    boxPaint.color = boxColor[1]
                    canvas.drawPath(path1, boxPaint)
                    boxPaint.color = boxColor[2]
                    canvas.drawPath(path2, boxPaint)
                    boxPaint.color = boxColor[3]
                    canvas.drawPath(path3, boxPaint)
                    boxPaint.color = boxColor[4]
                    canvas.drawPath(path4, boxPaint)
                }
            }
        }

    }


    /**
     * 繪制地圖
     */
    private fun drawMap(canvas: Canvas) {

        //繪制地圖
        for (i in 0 until 20) for (j in 0 until 10) canvas.drawRect(
            j * boxSize + leftMargin,
            i * boxSize + topMargin,
            (j + 1) * boxSize + leftMargin,
            (i + 1) * boxSize + topMargin,
            mapPaint
        )

        //繪制頂部狀態圖
        for (i in 0 until 4) for (j in 0 until 10) canvas.drawRect(
            j * boxSize + leftMargin,
            i * boxSize,
            (j + 1) * boxSize + leftMargin,
            (i + 1) * boxSize,
            mapPaint
        )

    }

    /**
     * 根據陣列繪制圖形
     */
    private fun drawBoxByArr(canvas: Canvas) {

        val mapArr = Operation.gameMap
        for (i in mapArr.indices) {

            for (j in mapArr[i].indices) {
                if (mapArr[i][j] > 0) {

                    boxPaint.color = getBoxColor(mapArr[i][j])[0]

                    val left = j * boxSize + leftMargin
                    val top = i * boxSize + topMargin
                    val right = (j + 1) * boxSize + leftMargin
                    val bottom = (i + 1) * boxSize + topMargin
                    canvas.drawRect(left, top, right, bottom, boxPaint)

                    //繪制單個模塊四周梯形
                    val path1 = Path()
                    path1.moveTo(left, top)
                    path1.lineTo(left + edge, i * boxSize + edge + topMargin)
                    path1.lineTo(left + edge, bottom - edge)
                    path1.lineTo(left, bottom)
                    path1.close()

                    val path2 = Path()
                    path2.moveTo(left, top)
                    path2.lineTo(left + boxSize, top)
                    path2.lineTo(left + boxSize - edge, top + edge)
                    path2.lineTo(left + edge, top + edge)
                    path2.close()

                    val path3 = Path()
                    path3.moveTo(left + boxSize - edge, top + edge)
                    path3.lineTo(left + boxSize, top)
                    path3.lineTo(left + boxSize, bottom)
                    path3.lineTo(left + boxSize - edge, bottom - edge)
                    path3.close()

                    val path4 = Path()
                    path4.moveTo(left, bottom)
                    path4.lineTo(left + edge, bottom - edge)
                    path4.lineTo(left + boxSize - edge, bottom - edge)
                    path4.lineTo(left + boxSize, bottom)
                    path4.close()

                    boxPaint.color = getBoxColor(mapArr[i][j])[1]
                    canvas.drawPath(path1, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[2]
                    canvas.drawPath(path2, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[3]
                    canvas.drawPath(path3, boxPaint)
                    boxPaint.color = getBoxColor(mapArr[i][j])[4]
                    canvas.drawPath(path4, boxPaint)
                }
            }
        }
    }

    private fun getBoxColor(type: Int): Array<Int> {
        return when (type) {
            1, 11 -> arrayOf(color10, color11, color12, color13, color14)
            2, 12 -> arrayOf(color20, color21, color22, color23, color24)
            3, 13 -> arrayOf(color30, color31, color32, color33, color34)
            4, 14 -> arrayOf(color40, color41, color42, color43, color44)
            5, 15 -> arrayOf(color50, color51, color52, color53, color54)
            6, 16 -> arrayOf(color60, color61, color62, color63, color64)
            7, 17 -> arrayOf(color70, color71, color72, color73, color74)
            else -> arrayOf(color10, color11, color12, color13, color14)
        }
    }

    /**
     * 重繪
     */
    fun refresh() {
        postInvalidate()
    }


}

MainActivity

都到這里了,代碼也就貼全了

package com.blog.tetris

import android.annotation.SuppressLint
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.blog.tetris.operation.Operation
import com.blog.tetris.view.GameView
import com.blog.tetris.view.StatusBarUtils


class MainActivity : AppCompatActivity() {

    private lateinit var gameView: GameView
    private lateinit var playGame: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initView()
        initData()
        initListener()

    }

    private fun initView() {

        //設定狀態欄字體為黑色
        StatusBarUtils.darkMode(this)

        gameView = findViewById(R.id.gameView)
        playGame = findViewById(R.id.playGame)
    }

    private fun initData() {
    }


    @SuppressLint("ClickableViewAccessibility")
    private fun initListener() {

        var downX = 0F
        var downY = 0F
        var direction = 0

        gameView.setOnTouchListener { _, p1 ->
            when (p1?.action) {
                MotionEvent.ACTION_DOWN -> {
                    downX = p1.x
                    downY = p1.y
                }
                MotionEvent.ACTION_MOVE -> {

                    //鎖定方向
                    if (0 == direction) {
                        if (p1.y - downY > 200) direction = 4
                        if ((p1.x - downX < -80 || p1.x - downX > 80)) direction = 1
                        if (p1.y - downY < -200) direction = 2
                    } else {
                        when (direction) {

                            //橫向滑動
                            1 -> {
                                if (p1.x - downX > gameView.boxSize) {
                                    downX = p1.x
                                    Operation.toRight()
                                } else if (p1.x - downX < -gameView.boxSize) {
                                    downX = p1.x
                                    Operation.toLeft()
                                }
                            }

                            //向上滑動
                            2 -> {
                                direction = -1
                                Operation.deformation()
                            }

                            //向下滑動
                            4 -> {
                                direction = -1
                                Operation.downBottom()
                            }
                        }
                    }


                }
                MotionEvent.ACTION_UP -> {
                    downX = 0F
                    downY = 0F
                    direction = 0
                }
            }
            true
        }


        //游戲變化回呼
        Operation.changeListener = object : Operation.ChangeListener {
            override fun onChange() {
                gameView.refresh()
            }

            override fun gameOver(score: Long) {
                runOnUiThread {
                    val dialog = AlertDialog.Builder(this@MainActivity)
                    dialog.setTitle("提示")
                    dialog.setMessage("游戲結束! 得分:$score")
                    dialog.setPositiveButton(
                        "重新開始"
                    ) { _, _ -> Operation.startGame() }
                    dialog.setNegativeButton(
                        "取消"
                    ) { _, _ -> }
                    dialog.setCancelable(false)
                    dialog.show()
                }
            }
        }

        playGame.setOnClickListener {
            Operation.startGame()
        }
    }


}

原始碼下載

Github:https://github.com/ThirdGoddess/Tetris

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/349671.html

標籤:其他

上一篇:2021年了,來談談Flutter的未來

下一篇:While回圈功能

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more