安卓小游戏:俄罗斯方块

前语

最近用安卓自界说view写了下飞机大战、贪吃蛇、小板弹球三个游戏,仍是比较简略的,这几天又把俄罗斯方块还原了一下,写了一天,又摸鱼调试了两天,逻辑不是很难,可是要理清、处理对仍是有点东西的。

需求

这儿的需求玩过的都知道,简略说便是操控四种砖块将底部填满,砖块能够进行旋转,当砖块超越顶部就游戏完毕了。中心思维如下:

  • 1,用一个二维数组保存地图信息,显现固定的砖块
  • 2,每次只呈现一个砖块,能够左右移动,手指向上移动进行旋转,手指向下移动快速掉落
  • 3,砖块和地图信息有交互,地图信息约束砖块移动和旋转,抵达底部或许底部被阻挡会触发固定
  • 4,固定之后,依据砖块更新地图信息,并进行下一轮砖块

效果图

这儿网上找的GIF转化工具,只能生成30秒的内容,不过游戏的内容现已显现得差不多了。

安卓小游戏:俄罗斯方块

代码

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.silencefly96.module_views.R
import java.lang.ref.WeakReference
import kotlin.math.abs
import kotlin.collections.indices as indices1
/**
 * 俄罗斯方块view
 */
class TetrisGameView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {
    companion object {
        // 游戏更新距离,一秒5次
        const val GAME_FLUSH_TIME = 200L
        // 砖块移动距离,一秒2.5次
        const val TETRIS_MOVE_TIME = 400L
        // 快速形式把更新时间等分
        const val FAST_MOD_TIMES = 10
        // 四个方向
        const val DIR_NULL = -1
        const val DIR_UP = 0
        const val DIR_RIGHT = 1
        const val DIR_DOWN = 2
        const val DIR_LEFT = 3
        // 四种砖块对应的装备,是一个2 * 8的数组, 这儿用二进制来保存
        // 极点左上角,二行四列,默认朝右,方向改换咦左上角旋转
        private const val CONFIG_TYPE_L = 0b1110_1000
        private const val CONFIG_TYPE_T = 0b1110_0100
        private const val CONFIG_TYPE_I = 0b1111_0000
        private const val CONFIG_TYPE_O = 0b0110_0110
        // 砖块类型数组,用于随机生成
        val sTypeArray = intArrayOf(CONFIG_TYPE_L, CONFIG_TYPE_T, CONFIG_TYPE_I, CONFIG_TYPE_O)
    }
    // 屏幕划分数量及等分长度
    private val mRowNumb: Int
    private var mRowDelta: Int = 0
    private val mColNumb: Int
    private var mColDelta: Int = 0
    // 节点掩图
    private val mTetrisMask: Bitmap?
    // 游戏地图是个二维数组
    private val mGameMap: Array<IntArray>
    // 当前操作的方块
    private val mTetris = Tetris(0, 0, 0, 0)
    // 不要在onDraw中创立目标, 在onDraw中暂时核算制作方位
    private val mPositions = ArrayList<MutableTriple<Int, Int, Boolean>>(8).apply {
        for (i in 0..7) add(MutableTriple(0, 0, false))
    }
    // 游戏操控器
    private val mGameController = GameController(this)
    // 画笔
    private val mPaint = Paint().apply {
        color = Color.LTGRAY
        strokeWidth = 1f
        style = Paint.Style.STROKE
        flags = Paint.ANTI_ALIAS_FLAG
    }
    // 上一个接触点X、Y的坐标
    private var mLastX = 0f
    private var mLastY = 0f
    init {
        // 读取装备
        val typedArray =
            context.obtainStyledAttributes(attrs, R.styleable.TetrisGameView)
        // 反正划分
        mRowNumb = typedArray.getInteger(R.styleable.TetrisGameView_rowNumber, 30)
        mColNumb = typedArray.getInteger(R.styleable.TetrisGameView_colNumber, 20)
        // 依据行数和列数生成地图
        mGameMap = Array(mRowNumb){ IntArray(mColNumb)}
        // 节点掩图
        val drawable = typedArray.getDrawable(R.styleable.TetrisGameView_tetrisMask)
        mTetrisMask = if (drawable != null) drawableToBitmap(drawable) else null
        typedArray.recycle()
    }
    private fun drawableToBitmap(drawable: Drawable): Bitmap? {
        val w = drawable.intrinsicWidth
        val h = drawable.intrinsicHeight
        val config = Bitmap.Config.ARGB_8888
        val bitmap = Bitmap.createBitmap(w, h, config)
        //留意,下面三行代码要用到,否则在View或许SurfaceView里的canvas.drawBitmap会看不到图
        val canvas = Canvas(bitmap)
        drawable.setBounds(0, 0, w, h)
        drawable.draw(canvas)
        return bitmap
    }
    // 完结测量开端游戏
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mRowDelta = h / mRowNumb
        mColDelta = w / mColNumb
        // 开端游戏
        load()
    }
    // 加载
    private fun load() {
        mGameController.removeMessages(0)
        mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
    }
    // 重新加载
    private fun reload() {
        mGameController.removeMessages(0)
        // 清空界面
        for (array in mGameMap) {
            array.fill(0)
        }
        mGameController.isNewTurn = true
        mGameController.isGameOver = false
        mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
    }
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
            getDefaultSize(0, heightMeasureSpec))
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 制作网格,mColDelta * mColNumb.toFloat() != width
        for (i in 0..mRowNumb) {
            canvas.drawLine(0f, mRowDelta * i.toFloat(),
                mColDelta * mColNumb.toFloat(), mRowDelta * i.toFloat(), mPaint)
        }
        for (i in 0..mColNumb) {
            canvas.drawLine(mColDelta * i.toFloat(), 0f,
                mColDelta * i.toFloat(), mRowDelta * mRowNumb.toFloat(), mPaint)
        }
        // 制作地图元素, (i, j)表明第i行,第j列
        for (i in mGameMap.indices1) {
            val array = mGameMap[i]
            for (j in array.indices1) {
                if (mGameMap[i][j] > 0) {
                    canvas.drawBitmap(mTetrisMask!!,
                        j * mColDelta.toFloat() + mColDelta / 2 - mTetrisMask.width / 2,
                        i * mRowDelta.toFloat() + mRowDelta / 2 - mTetrisMask.height / 2,
                        mPaint)
                }
            }
        }
        // 制作当前砖块,仅制作,碰撞、旋转由GameController操控
        for (pos in mPositions) {
            if (pos.third) {
                canvas.drawBitmap(mTetrisMask!!,
                    pos.second * mColDelta.toFloat() + mColDelta / 2 - mTetrisMask.width / 2,
                    pos.first * mRowDelta.toFloat() + mRowDelta / 2 - mTetrisMask.height / 2,
                    mPaint)
            }
        }
    }
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when(event.action) {
            MotionEvent.ACTION_DOWN -> {
                mLastX = event.x
                mLastY = event.y
            }
            MotionEvent.ACTION_MOVE -> {}
            MotionEvent.ACTION_UP -> {
                val lenX = event.x - mLastX
                val lenY = event.y - mLastY
                // 只更改方向,逻辑由GameController处理,方向更改成功与否需求承认
                if (abs(lenX) > abs(lenY)) {
                    // 左右移动
                    // val delta = (lenX / mColDelta).toInt()
                    // mGameController.colDelta = delta
                    mGameController.colDelta = if (lenX > 0) 1 else -1
                }else {
                    if (lenY >= 0) {
                        // 往下滑动加速
                        mTetris.fastMode = true
                    }else {
                        // 往上移动切换形态
                        mGameController.newDirection = mTetris.dir + 1
                        if (mGameController.newDirection > 3) {
                            mGameController.newDirection = 0
                        }
                    }
                }
            }
        }
        return true
    }
    private fun gameOver() {
        AlertDialog.Builder(context)
            .setTitle("持续游戏")
            .setMessage("请点击承认持续游戏")
            .setPositiveButton("承认") { _, _ -> reload() }
            .setNegativeButton("取消", null)
            .create()
            .show()
    }
    // kotlin主动编译为Java静态类,控件引证使用弱引证
    class GameController(view: TetrisGameView): Handler(Looper.getMainLooper()){
        // 控件引证
        private val mRef: WeakReference<TetrisGameView> = WeakReference(view)
        // 避免大量生成目标
        private val mTempPositions =
            ArrayList<MutableTriple<Int, Int, Boolean>>(8).apply {
            for (i in 0..7) add(MutableTriple(0, 0, false))
        }
        // 新砖块
        internal var isNewTurn = true
        // 左右移动
        internal var colDelta = 0
        // 更改的新方向
        internal var newDirection = DIR_NULL
        // 游戏完毕标志
        internal var isGameOver = false
        // 砖块移动操控变量,让左右移动能比向下移动快一步
        private var mMoveCounter = 0
        override fun handleMessage(msg: Message) {
            mRef.get()?.let { gameView ->
                // 新一轮砖块
                startNewTurn(gameView)
                // 移动前先校验旋转和左右移动
                val movable = preMoveCheck(gameView)
                if (movable) {
                    // 移动砖块
                    moveTetris(gameView)
                }else {
                    // 固定砖块
                    settleTetris(gameView)
                    // 查看消除底层
                    checkRemove(gameView)
                }
                // 循环发送音讯,改写页面
                gameView.invalidate()
                if (!isGameOver) {
                    if (gameView.mTetris.fastMode) {
                        gameView.mGameController.sendEmptyMessageDelayed(0,
                            GAME_FLUSH_TIME / FAST_MOD_TIMES)
                    }else {
                        gameView.mGameController.sendEmptyMessageDelayed(0, GAME_FLUSH_TIME)
                    }
                }else {
                    gameView.gameOver()
                }
            }
        }
        private fun startNewTurn(gameView: TetrisGameView) {
            if (isNewTurn) {
                // 保留旋转空余
                val col = (3 + Math.random() * (gameView.mColNumb - 6)).toInt()
                val type = sTypeArray[(Math.random() * 4).toInt()]
                gameView.mTetris.dir = (Math.random() * 4).toInt()
                // 由于旋转所以要确保在界面内
                gameView.mTetris.posRow = 0 + when(gameView.mTetris.dir) {
                    DIR_LEFT -> 1
                    DIR_UP -> 2
                    else -> 0
                }
                gameView.mTetris.posCol = col
                gameView.mTetris.config = type
                gameView.mTetris.fastMode = false
                isNewTurn = false
            }
        }
        private fun preMoveCheck(gameView: TetrisGameView): Boolean {
            // 一个一个校验,罗嗦了可是结构明晰
            val tetris = gameView.mTetris
            // 方向猜测
            if (newDirection != DIR_NULL) {
                getPositions(mTempPositions, tetris, tetris.posRow, tetris.posCol, newDirection)
                val flag = checkOutAndOverlap(mTempPositions, gameView.mRowNumb, gameView.mColNumb,
                    gameView.mGameMap)
                if (flag) {
                    tetris.dir = newDirection
                }
                newDirection = DIR_NULL
            }
            // 左右猜测
            if (colDelta != 0) {
                getPositions(mTempPositions, tetris, tetris.posRow, tetris.posCol + colDelta, tetris.dir)
                val flag = checkOutAndOverlap(mTempPositions, gameView.mRowNumb, gameView.mColNumb,
                    gameView.mGameMap)
                if (flag) {
                    tetris.posCol += colDelta
                }
                colDelta = 0
            }
            // 向下移动猜测
            getPositions(mTempPositions, tetris, tetris.posRow + 1, tetris.posCol, tetris.dir)
            return checkOutAndOverlap(mTempPositions, gameView.mRowNumb, gameView.mColNumb,
                gameView.mGameMap)
        }
        // 依据条件获得positions,直接界说不同方向的dir_type会更好吗?其实也要承认锚点,相同的
        private fun getPositions(positions: ArrayList<MutableTriple<Int, Int, Boolean>>,
            tetris: Tetris, posRow: Int, posCol: Int, dir: Int) {
            for (i in 0..1) for (j in 0..3) {
                val index = i * 4 + j
                // 按位获得装备
                val mask = 1 shl (7 - index)
                val flag = tetris.config and mask == mask
                val triple = positions[index]
                // 将不同方向对应的方位转化到config的顺序,并保存该方位是否制作的flag
                triple.third = flag
                var optimizedDir = dir
                // 对方块和条形类型特别优化
                if (tetris.config == CONFIG_TYPE_O) optimizedDir = DIR_RIGHT
                if (tetris.config == CONFIG_TYPE_I && dir >= DIR_DOWN) {
                    optimizedDir = dir - 2
                }
                when(optimizedDir) {
                    // 以o为锚点旋转,再优化,左边为旋转后,右边为优化后,目的:减小影响规模,约束在矩形内
                    // 一开端以右向左上角旋转,规模是7*7,可经过取值的改换,改换为5*5或许4*4的矩阵
                    // - x x - -
                    // - x x x -      - x x -
                    // x x o x x      x o o x
                    // x x x x x      x o o x
                    // - - x x -  =>  - x x -
                    // 右向,根底型
                    // o x x x      x o x x
                    // x x x x  ->  x x x x
                    DIR_RIGHT -> {
                        triple.first = posRow + i
                        triple.second = posCol + j - 1
                    }
                    // 下向
                    // x o      x x
                    // x x      x o
                    // x x      x x
                    // x x  ->  x x
                    DIR_DOWN -> {
                        triple.first = posRow + j - 1
                        triple.second = posCol - i
                    }
                    // 左向
                    // x x x x      x x x x
                    // x x x o  ->  x x o x
                    DIR_LEFT -> {
                        triple.first = posRow - i
                        triple.second = posCol - j + 1
                    }
                    // 上向
                    // x x      x x
                    // x x      x x
                    // x x      o x
                    // o x  ->  x x
                    DIR_UP -> {
                        triple.first = posRow - j + 1
                        triple.second = posCol + i
                    }
                    else -> {}
                }
            }
        }
        // 检测出界和堆叠,下边和左右
        private fun checkOutAndOverlap(positions: ArrayList<MutableTriple<Int, Int, Boolean>>,
            rowNumb: Int, colNumb: Int, gameMap: Array<IntArray>): Boolean {
            var flag = true
            for (pos in positions) {
                // 只对有值的方位进行验证
                if(!pos.third) continue
                // 出下界
                if (pos.first >= rowNumb) {
                    flag = false
                    break
                }
                // 左右出界
                if (pos.second >= colNumb || pos.second < 0) {
                    flag = false
                    break
                }
                // 旋转后有冲突,暂时疏忽上边之外的状况
                if (pos.first < 0) continue
                if (pos.third && gameMap[pos.first][pos.second] > 0) {
                    flag = false
                    break
                }
            }
            return flag
        }
        private fun moveTetris(gameView: TetrisGameView) {
            val tetris = gameView.mTetris
            // 对向下移动操控,左右移动、旋转不约束
            mMoveCounter++
            if (mMoveCounter == (TETRIS_MOVE_TIME / GAME_FLUSH_TIME).toInt()) {
                tetris.posRow += 1
                mMoveCounter = 0
            }
            getPositions(gameView.mPositions, tetris, tetris.posRow, tetris.posCol, tetris.dir)
        }
        private fun settleTetris(gameView: TetrisGameView) {
            // 固定砖块的方位,moveTetris现已将方位放到了gameView.mPositions中
            for (pos in gameView.mPositions) {
                if (pos.third) {
                    // 留意这儿方位超出屏幕上方便是游戏完毕
                    if (pos.first < 0) {
                        isGameOver
                    }else {
                        gameView.mGameMap[pos.first][pos.second] = 1
                    }
                }
            }
            isNewTurn = true
        }
        private fun checkRemove(gameView: TetrisGameView) {
            // 应该从顶层到底层查看,这样消除后的移动逻辑才没错,便是杂乱了点
            val gameMap = gameView.mGameMap
            for (i in gameMap.indices1) {
                val array = gameMap[i]
                var isFull = true
                for (peer in array) {
                    if (peer <= 0) {
                        isFull = false
                    }
                }
                // 消除,数组移位就行了
                if (isFull) {
                    for (j in (i - 1) downTo 0) {
                        // 把上面一层的数据填到当前层即可,最终会填到空层
                        val cur = gameMap[j + 1]
                        val another = gameMap[j]
                        for (k in cur.indices1) {
                            cur[k] = another[k]
                        }
                    }
                    // 最顶上填空
                    gameMap[0].fill(0)
                }
            }
        }
    }
    /**
     * 供外部收回资源
     */
    fun recycle()  {
        mTetrisMask?.recycle()
        mGameController.removeMessages(0)
    }
    // 砖块,以左上角为旋转中心旋转
    data class Tetris(
        var posRow: Int,
        var posCol: Int,
        var dir: Int,
        var config: Int,
        var fastMode: Boolean = false)
    data class MutableTriple<T, V, R>(var first: T, var second: V, var third: R)
}

对应style装备

res -> values -> tetris_game_view_style.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name ="TetrisGameView">
        <attr name="rowNumber" format="integer"/>
        <attr name="colNumber" format="integer"/>
        <attr name="tetrisMask" format="reference"/>
    </declare-styleable>
</resources>

砖块掩图

res -> drawable -> ic_tetris.xml

<vector android:height="24dp" android:tint="#6F6A6A"
    android:viewportHeight="24" android:viewportWidth="24"
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
    <path android:fillColor="@android:color/white" android:pathData="M18,4L6,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,6c0,-1.1 -0.9,-2 -2,-2zM18,18L6,18L6,6h12v12z"/>
</vector>

layout布局

    <com.silencefly96.module_views.game.TetrisGameView
        android:id="@+id/gamaView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/black"
        app:rowNumber="30"
        app:colNumber="20"
        app:tetrisMask="@drawable/ic_tetris"
        />

主要问题

我这儿的代码主要以逻辑模块为主,可能会冗余,可是力求逻辑明晰,下面简略讲讲吧。

资源加载、守时改写逻辑

老生常谈的问题了,有兴趣的能够看看我前面三个游戏的资源加载、守时改写逻辑,这儿并没有新事物可言,也比较简略。

砖块的类型

这儿用八位二进制数来表明砖块,一共四种砖块,前面四位表明第一行,后面四位表明第二行,仍是很好理解的。

private const val CONFIG_TYPE_L = 0b1110_1000
private const val CONFIG_TYPE_T = 0b1110_0100
private const val CONFIG_TYPE_I = 0b1111_0000
private const val CONFIG_TYPE_O = 0b0110_0110
// 四种类型
// o o o x   o o o x     o o o o     x o o x
// x x o x   x o x x     x x x x     x o o x

这儿还有一个砖块的旋转问题,我这儿并没有额外的界说一种砖块对应的四种状况,我这儿用四个方历来表明四个状况,后面取值的时分改换下就行了:

const val DIR_NULL = -1
const val DIR_UP = 0
const val DIR_RIGHT = 1
const val DIR_DOWN = 2
const val DIR_LEFT = 3

坐标方式及转化

不同于二维的垂直坐标系,这儿用的坐标是row和col的数,即第几行第几列的哪个方位。比如,在30*20的地图里,第一个方块是第0行第0列,最终一个方块是第29行第19列。

能够简略的以为row便是Y坐标,col便是横坐标:

for (i in mGameMap.indices1) {
    val array = mGameMap[i]
    for (j in array.indices1) {
        x = j * mColDelta
        y = i * mRowDelta
    }
}

界面元素制作

页面上要制作的东西就三种,网格、现已固定的砖块、能够移动的砖块(仅一个)。

网格制作的时分要留意下面这个问题,即对width等分之后取Int型有偏差。

mColDelta * mColNumb.toFloat() != width

地图的制作就依据mGameMap存的值制作就行了,有值就制作,无值空着。可是要留意下drawBitmap取的是bitmap的左边和上边,可是地图小方块的宽高和bitmap的宽高不一定一致,即:

mColDelta != mTetrisMask.width; mRowDelta != mTetrisMask.height

所以,这儿要进行一下处理,将bitmap摆放到地图小方块的中间去:

canvas.drawBitmap(mTetrisMask!!,
    j * mColDelta.toFloat() + mColDelta / 2 - mTetrisMask.width / 2,
    i * mRowDelta.toFloat() + mRowDelta / 2 - mTetrisMask.height / 2,
    mPaint)

至于能够移动的砖块,上面用了八位二进制数来表明类型,这儿也用size为8的mPositions来保存受移动砖块所影响的八个坐标的信息,onDraw中只要考虑制作这八个坐标的信息,至于逻辑会在GameController中处理。

方块的操控

在中心思维里边,现已设计了四种操控方式,即左右移动,向上改换,向下加速,只要在onTouchEvent中识别这四个方向,设置好操控变量,剩下的也交给GameController去处理。

GameController

将和游戏逻辑无关的制作、交互分发出去后,GameController的职责就很清楚了,大致便是一下几个:

  1. 生成新砖块
  2. 查看交互逻辑
  3. 移动
  4. 固定
  5. 消除

新砖块生成

这儿用了一个操控变量来操控是否新生成砖块(isNewTurn),当砖块固定后就会触发isNewTurn为true,进行新一轮。

新砖块从上面生成,左右随机,类型及方向随机,这儿并没有创立新的目标,由于砖块就一个,更改mTetris的特点就行。

查看交互逻辑

这儿的交互便是上面的几个操控,旋转及移动不能出界,假如可能出界就不应该旋转或许移动。这儿专门写了一个getPositions函数来获得对应方位、方向被移动砖块影响的坐标列表,传入猜测后的方位及方向,得到坐标列表,对这些坐标再进行校验,看看是否出界或许堆叠,再回来承认旋转或移动操作是否能进行,能进行才对可移动砖块特点做修正,进入到下一步的移动。

这儿专门写了getPositions和checkOutAndOverlap来获取被影响坐标和校验出界或许堆叠,checkOutAndOverlap比较简略,下面重点讲下getPositions,这个是这个游戏里边的中心。

游戏中心:getPositions

说白了整个游戏就一个难点,如何承认移动砖块的方位,两种砖块四种状况,八种状况。上面讲到了,砖块的类型是经过8bit来表明的,方式如下:

// 四种类型
// o o o x   o o o x     o o o o     x o o x
// x x o x   x o x x     x x x x     x o o x

上面代表着八个点,核算的时分在方块的坐标(锚点)处将八个点映射到地图上(下面o为锚点):

// o x x x
// x x x x

上面这种状况是对应左向状况的状况,剩下的四种状况是经过旋转来得到的,这儿以o为旋转点,能够得到四种状况:

//              x o                 x x
//              x x                 x x
// o x x x      x x     x x x x     x x
// x x x x      x x     x x x o     o x

而实际状况下,我们并不想旋转影响太大的规模,这儿就要改一下锚点的方位:

//              x x                 x x
//              x o                 x x
// x o x x      x x     x x x x     o x
// x x x x      x x     x x o x     x x

由同一个锚点展开四种状况的影响方位,得到下面规模,在一个5*5的规模内(或许更进一步到4*4):

// - x x - -
// - x x x -      - x x -
// x x o x x      x o x x
// x x x x x      x x x x
// - - x x -  =>  - x x -

理解清楚原理,就很好写代码了,这儿还有两个问题要留意下。第一个是掩码的取值,要从前往后取:

val mask = 1 shl (7 - index)

另一个便是最好对长条和方块特别优化下:

// 对方块和条形类型特别优化
if (tetris.config == CONFIG_TYPE_O) optimizedDir = DIR_RIGHT
if (tetris.config == CONFIG_TYPE_I && dir >= DIR_DOWN) {
    optimizedDir = dir - 2
}

移动砖块

在校验里边现已对向下移动进行了校验,假如能向下移动,只需求调用getPositions把得到的坐标存入gameView.mPositions里边就行了,在onDraw里边会对砖块进行制作。

固定砖块

假如preMoveCheck里边得到不能再向下移动了,那就应该对砖块进行固定,并开启新一轮砖块。固定的时分只要把砖块影响方位赋值到地图二维数组里就行了。

查看消除

每次固定好砖块,都应该承认下是否需求消除。这儿由于涉及到移动地图二维数组,所以应该先从顶层查看,遍历一下。消除的时分把上面的一切数组向下移动,最顶层添加空的array就行了。

快速形式和距离向下

这儿在GameController引入了变量来实现了快速形式和距离向下,快速形式便是下降handler的发送延时,距离向下便是经过操控变量让moveTetris推迟向下的移动,留出时间来左右移动或许旋转,愈加人性话点。

结语

这儿写得有点多了,写一个游戏仍是挺有意思的,朋友说这东西没有技术性,我仍是觉得只要你做过,你才知道你有没有学到东西,不去做,永远停留在纸面上。