PiMuseum-游戏开发入门级教程-中国象棋-Chapter-1

导语

《头号玩家》的问世,足以彰显游戏的魅力,当你在自己的“绿洲”里驰骋风云的时候,可否思考过这个异世界是如何构建的,在当前维度,必然是想象力和代码的完美结合,高喊“为了部落”的魔兽世界,打出“我很抱歉”的炉石传说,寻找“大师之剑”的塞尔达传说等等,在这些具有史诗感的游戏背后,从玩游戏到做游戏,游戏的本质到底是什么,以中国象棋为例,跟我一起走进这游戏开发的领地,给那些想入门游戏开发的头号玩家们一个纯粹的引导。

游戏本质

游戏的本质即是数据的建模后通过媒介给我们以感官上的互动,万物皆可数据,数据皆可包装成我们直接接触的形式,一个游戏被创造的过程简而言之就是: 定义规则 → 数据建模 → 引擎渲染,这三个步骤都是比较广义的,游戏开发肯定离不开游戏引擎,引擎的存在是帮助我们搭建了数据到视图(视觉感官为例)的桥梁,其实游戏引擎也是:定义规则 → 数据建模→渲染 过程,只是这个规则和建模基本都是一些2维或3维的实际物理意义上(碰撞,移动等)的包装,我们通过这些引擎可以快速并更稳定地构建一个具有基础物理意义的环境。

中国象棋-数据建模

游戏的灵魂必须是其规则,规则的定义是游戏乐趣的一大衡量指标,因为我们是以中国象棋为例,三大开发步骤之首的定义规则就全盘依赖于象棋规则本身了。
我们直接从数据建模开始,编写数据模型类的集成开发环境自选即可,我这里推荐JetBrains旗下的IDE,当然,你甚至可以用记事本写 @。@!因为最近写Kotlin比较多,代码实现的语言选择的是Kotlin,游戏开发语言的选择和使用游戏引擎有必然关联,后面会讲到。
在这里插入图片描述

棋盘数据模型

如下图所示,是一张标准中国象棋棋盘开局图:
ChineseChess
因为象棋规则本质是关于棋子的落子位置的变换,所以棋盘映射成数学模型为二维坐标系,又因为存在棋谱记法(炮二平五,马四进六),所以以红色方为己方正对视角,我们定义棋盘左下角红車的坐标为(x=1,y=1)建立直角坐标系,自下而上为y轴正方向,自左向右为x轴正方向,棋盘上的二维坐标集信息为映射为一个二维数组,我们新建一个ChessHelper的单例类,棋盘的相关信息和之后的一些对棋盘、棋子数据的操作方法都放在该类下:

object ChessHelper {

    //十行九列一共90个坐标点 ,这里为 10 * 11 表示 左下角的第一个点并非从 (0,0) 开始,而是从(1,1) 开始
    const val ColumnCapacity : Int = 10
    const val RowCapacity : Int = 11

    //棋盘上的二维坐标集信息,对应的索引取值是空值或者是棋子对象
    private var chessboardInfo : Array<Array<Chessman?>>
            = Array(RowCapacity ){Array<Chessman?>(ColumnCapacity) { null } }
   ...
}

棋子数据模型

棋子的数据模型无非就是放在上述棋盘信息中的对象,棋子应该包括的基本信息有:坐标信息,颜色信息。然后不同的棋子都有自己的下棋规则,对于棋子自身的落点也有要求,比如你的士要出宫,象要过河,那我就要报警了,所以棋子需要有一种契约精神,一种约束力来保证符合规则,我把棋子自身的走法定义为自我约束(車只能直走),棋盘环境对棋子走法的进一步限制定义为棋盘约束(马蹩脚),棋子主要的逻辑方法就是上述两方面的约束方法外加一个更新自我坐标信息的方法,所需额外信息(即方法参数)为棋盘全局信息和将要下棋落子的坐标,所以我们先可以定义棋子的抽象类如下:

abstract class Chessman(var chessType: ChessType, var position: Position) {

	//不同种类棋子规则,即棋子自我约束
    abstract fun chessmanRule(nextPosition: Position) : Boolean

    //与棋盘上产生的联动效应,即棋盘及其他棋子约束
    abstract fun chessboardRule(chessboardInfo : Array<Array<Chessman?>>, nextPosition: Position) : Boolean

    //棋子名
    abstract fun chessmanName() : String

    //更新棋子自己的坐标信息
    fun updateChessmanPosition(row : Int,column : Int) {
        position.row = row
        position.column = column
    }
}

伴随坐标类:

class Position(
    var row : Int = 1,
    var column : Int = 1)

因此不同棋子根据自己各自的规则实现 chessmanRule(nextPosition: Position)chessboardRule(chessboardInfo : Array<Array<Chessman?>>, nextPosition: Position) 两个主要的抽象方法,来完成不同的棋子的数据建模。

棋子-車

車的走法是横冲直撞,嗯~路霸的感觉…
在这里插入图片描述
实现 chessmanRule(nextPosition: Position) 时候,我们首先判断落点坐标nextPosition 和当前車棋子是否处于同一条直线上,即当前車的X坐标值或者Y坐标值 与 nextPosition 是否相等,满足条件则为符合自我走法约束。符合该单一条件的下棋是独立于棋盘信息的,因此我们需要添加上棋盘信息对这次下棋移动棋子的约束,有两个约束,第一个约束是落点需要判断是否有同色棋子,因为不能吃掉自己的棋子,第二个月约束是落点和車棋子之间不能存在其他棋子,所以我们接着实现 chessboardRule(chessboardInfo : Array<Array<Chessman?>>, nextPosition: Position),综上所诉,車的实现类如下:

class ChariotChessman(chessType: ChessType, position: Position) : Chessman(chessType, position) {

    override fun chessmanRule(nextPosition: Position): Boolean {
        //车列行直走
        return ((nextPosition.column == position.column && nextPosition.row != position.row)
                || (nextPosition.column != position.column && nextPosition.row == position.row))
    }
    
    override fun chessboardRule(chessboardInfo: Array<Array<Chessman?>>, nextPosition: Position): Boolean {

        ChessLogic.isExistChessman(chessboardInfo,nextPosition)?.let { chessman->
            if (chessman.chessType == this@ChariotChessman.chessType) return false//同色棋子不能被吃
        }

        if (ChessLogic.numberBetween2Positions(chessboardInfo,this@ChariotChessman.position,nextPosition) > 0) {
            //两棋子之间有其他棋子则不符合 车的走法
            return false
        }
        return true
    }
    
    override fun chessmanName(): String {
        return "車"
    }
}

其实每个棋子都需要判断落子点是否存在同色棋子,可以抽到父类做公共处理,这里为了显示逻辑判断的思路流程就还是放在各自的棋子实现类中做一个逻辑判断。ChessLogic 是一个棋盘信息逻辑判断的辅助类,主要用来判断两点之间存在几个棋子等一些公用逻辑判断。如此一来,車棋子的数据模型对象就按照游戏规则实现了。

棋子-兵/卒

兵的走法是低配版横冲直撞,以車为例分析了一些实现思路,那么其他棋子也类似,其实也就是对之前分析的两次约束做更多的逻辑判断而已,兵/卒的自我走法约束要少多,要考虑到过河问题,而过河的这一操作的数据抽象无非是对棋盘落点坐标的y轴坐标值划分为2个区间罢了,我们来看看 兵/卒棋子是如何实现自己的数据类的:

class SoldierChessman(chessType: ChessType, position: Position) : Chessman(chessType, position) {

    override fun chessmanRule(nextPosition: Position): Boolean {

        //兵卒移动距离为1
       if ((Math.abs(nextPosition.column - position.column) + Math.abs(nextPosition.row - position.row)) != 1) return false

        //兵卒只能向前走和平移,过河以前不能横向移动
        return if (chessType == ChessType.Red) {//红棋子
            if (position.row <= 5) {
                nextPosition.row >= position.row && nextPosition.column == position.column
            } else {
                nextPosition.row >= position.row
            }
        } else {//黑棋子
            if (position.row >= 6) {
                nextPosition.row <= position.row && nextPosition.column == position.column
            } else {
                nextPosition.row <= position.row
            }
        }
    }

    override fun chessboardRule(chessboardInfo: Array<Array<Chessman?>>, nextPosition: Position): Boolean {

        ChessLogic.isExistChessman(chessboardInfo,nextPosition)?.let { chessman->
            if (chessman.chessType == this@SoldierChessman.chessType) return false//同色棋子不能被吃
        }
        return true
    }

    override fun chessmanName(): String {

        return when(chessType) {
            ChessType.Red -> "兵"
            ChessType.Black -> "卒"
        }
    }
}

当然,抽象数据的思路有很多,你也可以根据自己的思路去定义更好结合代码和游戏规则的方式,本章大致建立游戏开发流程和中国象棋游戏数据的基本抽象,下一章中国象棋 - Chapter 2中将分析下马棋子和对棋盘操作的逻辑流程。

源码链接

中国象棋 数据抽象模块在该项目 : Source Code
core/src/com/pimuseum/game/chinesechess/model 路径下

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值