您当前的位置: 首页 >  游戏

H5 游戏开发启蒙案例 04《辩色》

蔚1 发布时间:2020-04-22 23:30:46 ,浏览量:4

不用游戏引擎,怎么写游戏?

来跟着作者用 JavaScript 写一个 H5 游戏案例《辩色》

本 Chat 中包含以下内容:

  • 需求分析、数据结构设计
  • HSL 调色模式
  • MVC 设计模式
  • 完整的实现过程

适合人群: 对入门游戏开发有兴趣的人员

前言

本篇文章具体介绍:如何循序渐进用 200 行左右的代码,实现一个代码高可读性的“辩色”。

目录 准备工作
  • 运行环境:Web (Chrome 浏览器)
  • 开发语言:JavaScript
  • 开发工具:VS Code
需求分析

004

以上是已经完成的案例的演示图,需求拆解出来还是比较简单的。

  • 对象:格子(地图的基本单位)、地图(二位数组)
  • 属性:每个格子是一个对象,记录着当前的颜色,已经是否是那个位移的颜色标识
  • 行为:点击格子,加分数,减生命值
数据结构设计
  • 格子:地图的基本单位,有以下属性
{     color: {h:0,s:0,l:0},  //用 HSL 调色模式记录颜色    isOnly: true //记录是否是唯一的颜色}
  • 地图:格子的二维数组,

坐标 | x0 | x1-|-|-y0 | { color: {h:0,s:0,l:0} } | { color: {h:0,s:0,l:0} } |y1 | { color: {h:0,s:0,l:0} } | { color: {h:0,s:10,l:0},isOnly:true } |

完整的实现过程

打开 VS Code,新建一个 HTML,内容如下。body 里面只有一个 Canvas 的标签,表示全部显示都用 Canvas 的 API 绘制而成,脚本那边先把 MVC 的框架定好,接下来把 mvc 各个层级的数据和方法定好,顺着这个逻辑,理清楚思路,把方法补全。很容易就能实现这个案例了。

当然,这个案例只有 "辩色" 的核心玩法,读者完全可以根据自己的想象力,或者参考别的游戏的灵感,进行改造

        //======== 模型    let m = {}    //======== 视图    let v = {}    //======== 控制    let c = {        main () { },    }    c.main()
MVC 架构

通过 MVC 架构,可以把整个程序的核心思路理清楚,具体的编码可以在这之后。整个程序按照 MVC 分别进行介绍,最后再进行串联。

M 模型层: 负责存储数据、管理数据逻辑
    let m = {        //===常量===        //地图宽度        MapWidth: 6,        //地图高度        MapHeight: 6,        //单位格子大小(像素)        UnitSize: 30,        //最大生命值        MaxHp: 3,        //差异(难度 越小难度越大 1-20        Diff: 5,        //颜色        Color: {            grey: "#4a4a4a",            red: "#cc2222",            white: "#ffffff",        },        //===变量===        //地图        map: [[]],        //游戏状态 0 游戏中 1 失败        status: 0,        //生命值        hp: 0,        //分数        score: 0,        //鼠标移动到哪个坐标        mouseMoveVec: null,        //===逻辑===        //初始化模型层        init() {            //初始化游戏数据        },        //下一关        next() {            //执行下一关的数据生成转换        },        //鼠标移动到哪里        onMouseMove(offsetX, offsetY) {            //如果游戏结束的状态,不处理            //点击坐标转为转成地图坐标                //记录当前鼠标选择的坐标                //如果坐标点超出地图,移除当前鼠标选择的坐标        },        //点击了格子        onClick(offsetX, offsetY) {            //如果游戏结束的状态,则重新开始游戏            //点击坐标转为转成地图坐标                //选对了 得分                //选错了                    //扣血                    //游戏结束        },        //新的地图数据        newMapData() {            //根据配置生成一个对应的地图数据        },        //新的颜色        newColor(color) {            //如果有传入颜色,则生成一个和这个颜色有一定差异的颜色            //随机生成一个 HSL 模式的颜色        },
V 视图层: 将数据进行显示表达
    let v = {        //初始化        init() {        },        //执行绘制        onDraw() {            //1.绘制地图                   //绘制格子颜色                   //如果游戏失败                        //高亮显示唯一的颜色            //绘制鼠标选中的格子            //2.绘制游戏中、失败等显示状态                    //绘制得分                    //绘制生命值            //3.绘制游戏名        },
C 控制层: 负责从控制用户输入,并向模型发送数据,执行主循环
    let c = {        //入口函数        main() {            //各种初始化        },        //初始化输入监听        initInputListener() {            //鼠标点击事件            //鼠标移动事件            //鼠标移出事件        },    }

以上为整体流程的思路,按照 MVC 来设计,再设计好数据结构,把对象和行为这些都列好,接下来就非常容易实现了。文末附上所有完整源码。

结语

本文章对编程的基础其实要求并不高,仅需要一些 JavaScript 的基础语法。更多的是想传达一个思路,条条大路通罗马,"辩色"的实现可能有无数种方式,这是其中一种。作者认为有一定编程基础,找对方向可以做出很多有趣的东西。如果你对我的内容感兴趣,可以关注我的订阅号“怪诞编程”找到我。一起探索技术变成产品的有趣过程。

如果你的电脑一无所有,且对文章也看得不明白,没关系,右键新建一个文本文档 .txt,改名为 index.html。然后把以下代码复制进去,保存,再双击打开。就是这篇文章的全部内容了。

请收下这份 273 行的"辩色"源码:

  • 扣掉注释 58 行
  • 实际的代码行数 215 行

谢谢。

index.html

        //======== 模型    let m = {        //===常量===        //地图宽度        MapWidth: 6,        //地图高度        MapHeight: 6,        //单位格子大小(像素)        UnitSize: 30,        //最大生命值        MaxHp: 3,        //差异(难度 越小难度越大 1-20        Diff: 5,        //颜色        Color: {            grey: "#4a4a4a",            red: "#cc2222",            white: "#ffffff",        },        //===变量===        //地图        map: [[]],        //游戏状态 0 游戏中 1 失败        status: 0,        //生命值        hp: 0,        //分数        score: 0,        //鼠标移动到哪个坐标        mouseMoveVec: null,        //===逻辑===        //初始化模型层        init() {            //初始化游戏数据            m.status = 0            m.hp = m.MaxHp            m.score = 0            this.next()        },        //下一关        next() {            //执行下一关的数据生成转换            let list = this.newMapData()            let map = []            for (let y = 0; y < m.MapHeight; y++) {                map[y] = []                for (let x = 0; x < m.MapWidth; x++) {                    map[y][x] = list.pop()                }            }            m.map = map        },        //鼠标移动到哪里        onMouseMove(offsetX, offsetY) {            //如果游戏结束的状态,不处理            if (m.status == 1) {                return            }            //点击坐标转为转成地图坐标            let x = Math.floor(offsetX / m.UnitSize)            let y = Math.floor(offsetY / m.UnitSize)            if (m.map[y] && m.map[y][x]) {                //记录当前鼠标选择的坐标                m.mouseMoveVec = { x: x, y: y }            } else {                //如果坐标点超出地图,移除当前鼠标选择的坐标                m.mouseMoveVec = null            }        },        //点击了格子        onClick(offsetX, offsetY) {            //如果游戏结束的状态,则重新开始游戏            if (m.status == 1) {                m.init()                return            }            //点击坐标转为转成地图坐标            let x = Math.floor(offsetX / m.UnitSize)            let y = Math.floor(offsetY / m.UnitSize)            let bean = m.map[y][x]            if (bean && bean.isOnly) {                //选对了 得分                m.score++                m.next()            } else {                //选错了                if (m.hp > 1) {                    //扣血                    m.hp -= 1                } else {                    //游戏结束                    m.status = 1                    m.mouseMoveVec = null                }            }        },        //新的地图数据        newMapData() {            //根据配置生成一个对应的地图数据            var color = this.newColor()            var colorBadGuy = this.newColor(color)            var list = new Array(m.MapWidth * m.MapHeight)            var randomIndex = Math.floor(Math.random() * list.length)            for (var i = 0; i < list.length; i++) {                if (i == randomIndex) {                    list[i] = { color: colorBadGuy, isOnly: true }                } else {                    list[i] = { color: color }                }            }            return list        },        //新的颜色        newColor(color) {            if (color) {                //如果有传入颜色,则生成一个和这个颜色有一定差异的颜色                var randmoIndex = Math.floor(Math.random() * 3)                var h = color.h                var s = color.s                var l = color.l                if (s < m.Diff) {                    s += m.Diff                } else {                    s -= m.Diff                }                if (l < m.Diff) {                    l += m.Diff                } else {                    l -= m.Diff                }                return { h: h, s: s, l: l }            } else {                //随机生成一个 HSL 模式的颜色                var h = Math.floor(Math.random() * 360);                var s = Math.floor(Math.random() * 100);                var l = Math.floor(Math.random() * 100);                return { h: h, s: s, l: l }            }        },    }    //======== 视图    let v = {        //初始化        init() {            let canvas = document.getElementById("canvas")            v.canvas = canvas            //设置 canvas 的宽高            canvas.width = m.MapWidth * m.UnitSize            canvas.height = m.MapHeight * m.UnitSize + m.UnitSize * 1.5            //宽高变化重新调整 canvas 在页面中的位置,使居中            canvas.style.marginLeft = -canvas.width / 2            canvas.style.marginTop = -canvas.height / 2            //拿到 canvas 绘制的上下文,方便之后直接进行绘制            this.context = canvas.getContext("2d")        },        //执行绘制        onDraw() {            let context = this.context            let canvas = v.canvas            context.clearRect(0, 0, canvas.width, canvas.height)            context.lineWidth = 1            //1.绘制地图            context.fillStyle = m.Color.grey            for (let y = 0; y < m.map.length; y++) {                for (let x = 0; x < m.map[y].length; x++) {                    //绘制格子颜色                    let h = m.map[y][x].color.h                    let s = m.map[y][x].color.s                    let l = m.map[y][x].color.l                    context.fillStyle = `HSL(` + h + `,` + s + `%,` + l + `%)`                    context.beginPath()                    context.rect(                        x * m.UnitSize,                        y * m.UnitSize,                        m.UnitSize,                        m.UnitSize)                    context.fill()                    //如果游戏失败                    if (m.map[y][x].isOnly && m.status == 1) {                        //高亮显示唯一的颜色                        context.fillStyle = m.Color.red                        context.beginPath()                        context.arc(                            (x + 0.5) * m.UnitSize,                            (y + 0.5) * m.UnitSize,                            2, 0, 2 * Math.PI)                        context.fill()                    }                }            }            //绘制鼠标选中的格子            if (m.mouseMoveVec) {                context.strokeStyle = m.Color.white                context.beginPath()                context.rect(                    m.mouseMoveVec.x * m.UnitSize,                    m.mouseMoveVec.y * m.UnitSize,                    m.UnitSize,                    m.UnitSize)                context.stroke()            }            //2.绘制游戏中、失败等显示状态            context.fillStyle = m.Color.grey            context.font = 'bold ' + m.UnitSize / 2 + 'px arial'            context.textBaseline = "bottom"            if (m.status == 1) {                context.textAlign = "center"                context.fillText("结束 点击重来", canvas.width / 2, canvas.height - 5)            } else {                context.textAlign = "right"                //绘制得分                context.fillText(m.score + "分", canvas.width, canvas.height - 5)                for (let x = 0; x < m.hp; x++) {                    //绘制生命值                    context.fillStyle = m.Color.red                    context.beginPath()                    let unit = m.UnitSize / 5                    let centerX = (x + 0.5) * m.UnitSize                    let centerY = canvas.height - m.UnitSize / 2 - unit / 2                    context.moveTo(centerX, centerY);                    context.lineTo(centerX + unit, centerY - unit);                    context.lineTo(centerX + unit * 2.5, centerY);                    context.lineTo(centerX, centerY + unit * 2.5);                    context.lineTo(centerX - unit * 2.5, centerY);                    context.lineTo(centerX - unit, centerY - unit);                    context.closePath();                    context.fill()                }            }            //3.绘制游戏名            context.font = 'bold ' + m.UnitSize * 0.45 + 'px arial'            context.fillStyle = m.Color.grey            context.textAlign = "left"            context.fillText("辨色", canvas.width * 0.01, canvas.height - m.UnitSize)        },    }    //======== 控制    let c = {        //入口函数        main() {            c.initInputListener()            v.init()            m.init()            v.onDraw()        },        //初始化输入监听        initInputListener() {            //鼠标点击事件            document.getElementById("canvas").addEventListener('click', function (event) {                m.onClick(event.offsetX, event.offsetY)                v.onDraw()            }, false);            //鼠标移动事件            document.getElementById("canvas").addEventListener('mousemove', function (event) {                m.onMouseMove(event.offsetX, event.offsetY)                v.onDraw()            }, false);            //鼠标移出事件            document.getElementById("canvas").addEventListener('mouseout', function (event) {                m.onMouseMove(event.offsetX, event.offsetY)                v.onDraw()            }, false);        },    }    //=== 启动    c.main()

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5e9f1ac4df43a81990578dda

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

关注
打赏
1688896170
查看更多评论

蔚1

暂无认证

  • 4浏览

    0关注

    4645博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0708s