简单的小游戏制作,代码量只有两三百行。游戏可自行扩展延申。 源码已发布至github,喜欢的点个小星星,源码入口:game-snake 游戏已发布,游戏入口:http://snake.game.yanjd.top 第一步 - 制作想法 游戏如何实现是首要想的,这里我的想法如下: 利用canvas进行绘制地图(格子装)。 利用canvas绘制蛇,就是占用地图格子。让蛇移动,即:更新蛇坐标,重新绘制。 创建四个方向按钮,控制蛇接下来的方向。 随机在地图上绘制出果子,蛇移动时“吃”到果子,增加长度和“移速”。 开始键和结束键配置,分数显示、历史记录 第二步 - 框架选型 从第一步可知,我想实现这个游戏,只需要用到canvas绘制就可以了,没有物理引擎啥的,也没有高级的UI特效。可以选个简单点的,用来方便操作canvas绘制。精挑细选后选的是EaselJS,比较轻量,用于绘制canvas,以及canvas的动态效果。 第三步 - 开发 准备 目录和文件准备: | - index.html | - js | - | - main.js | - css | - | - stylesheet.css index.html 导入相关的依赖,以及样式文件和脚本文件。设计是屏幕80%高度为canvas绘制区域,20%高度是操作栏以及展示分数区域. 贪吃蛇
stylesheet.css * { padding: 0; margin: 0; } body { position: fixed; width: 100%; height: 100%; } #app { max-width: 768px; margin-left: auto; margin-right: auto; } /* canvas绘制区域 */ .content-canvas { width: 100%; max-width: 768px; height: 80%; position: fixed; overflow: hidden; } .content-canvas canvas { position: absolute; width: 100%; height: 100%; } /* 操作区域 */ .control { position: fixed; width: 100%; max-width: 768px; height: 20%; bottom: 0; background-color: #aeff5d; } main.js $(function() { // 主代码编写区域 }) 1.绘制格子 注意的点(遇到的问题以及解决方案): canvas绘制的路线是无宽度的,但线条是有宽度的。比如:从(0, 0)到(0, 100)绘制一条宽度为10px的线,则线条一半是在区域外看不见的。处理方案是起点偏移,比如:从(0, 0)到(0, 100)绘制一条宽度为10px的线,改为从(5,0)到(5,100),偏移量为线条宽度的一半。 用样式定义canvas的宽高坐标会被拉伸,处理方案是给canvas元素设置宽高属性,值为它当前的实际宽高。 代码 main.js $(function () { var LINE_WIDTH = 1 // 线条宽度 var LINE_MAX_NUM = 32 // 一行格子数量 var canvasHeight = $('canvas').height() // 获取canvas的高度 var canvasWidth = $('canvas').width() // 获取canvas的宽度 var gridWidth = (canvasWidth - LINE_WIDTH) / LINE_MAX_NUM // 格子宽度,按一行32个格子计算 var num = { w: LINE_MAX_NUM, h: Math.floor((canvasHeight - LINE_WIDTH) / gridWidth) } // 计算横向和纵向多少个格子,即:横坐标的最大值和纵坐标的最大值 /** * 绘制格子地图 * @param graphics */ function drawGrid(graphics) { var wNum = num.w var hNum = num.h graphics.setStrokeStyle(LINE_WIDTH).beginStroke('#ffac52') // 画横向的线条 for (var i = 0; i <= hNum; i++) { if (i === hNum || i === 0) graphics.setStrokeStyle(LINE_WIDTH) if (i === 1) graphics.setStrokeStyle(0.1) graphics.moveTo(LINE_WIDTH / 2, i * gridWidth + LINE_WIDTH / 2) .lineTo(gridWidth * wNum + LINE_WIDTH / 2, i * gridWidth + LINE_WIDTH / 2) } graphics.setStrokeStyle(LINE_WIDTH) // 画纵向的线条 for (i = 0; i <= wNum; i++) { if (i === wNum || i === 0) graphics.setStrokeStyle(LINE_WIDTH) if (i === 1) graphics.setStrokeStyle(.1) graphics.moveTo(i * gridWidth + LINE_WIDTH / 2, LINE_WIDTH / 2) .lineTo(i * gridWidth + LINE_WIDTH / 2, gridWidth * hNum + LINE_WIDTH / 2) } } function init() { $('canvas').attr('width', canvasWidth) // 给canvas设置宽高属性赋值上当前canvas的宽度和高度(单用样式配置宽高会被拉伸) $('canvas').attr('height', canvasHeight) var stage = new createjs.Stage($('canvas')[0]) var grid = new createjs.Shape() drawGrid(grid.graphics) stage.addChild(grid) stage.update() } init() }) 效果图 浏览器打开index.html,可以看到效果: 2.绘制蛇 蛇可以想象成一串坐标点(数组),“移动时”在数组头部添加新的坐标,去除尾部的坐标。类似队列,先进先出。 代码 main.js $(function () { var LINE_WIDTH = 1 // 线条宽度 var LINE_MAX_NUM = 32 // 一行格子数量 var SNAKE_START_POINT = [[0, 3], [1, 3], [2, 3], [3, 3]] // 初始蛇坐标 var DIR_ENUM = { UP: 1, DOWN: -1, LEFT: 2, RIGHT: -2 } // 移动的四个方向枚举值,两个对立方向相加等于0 var GAME_STATE_ENUM = { END: 1, READY: 2 } // 游戏状态枚举 var canvasHeight = $('canvas').height() // 获取canvas的高度 var canvasWidth = $('canvas').width() // 获取canvas的宽度 var gridWidth = (canvasWidth - LINE_WIDTH) / LINE_MAX_NUM // 格子宽度,按一行32个格子计算 var num = { w: LINE_MAX_NUM, h: Math.floor((canvasHeight - LINE_WIDTH) / gridWidth) } // 计算横向和纵向多少个格子,即:横坐标的最大值和纵坐标的最大值 var directionNow = null // 当前移动移动方向 var directionNext = null // 下一步移动方向 var gameState = null // 游戏状态 /** * 绘制格子地图 * @param graphics */ function drawGrid(graphics) { var wNum = num.w var hNum = num.h graphics.setStrokeStyle(LINE_WIDTH).beginStroke('#ffac52') // 画横向的线条 for (var i = 0; i <= hNum; i++) { if (i === hNum || i === 0) graphics.setStrokeStyle(LINE_WIDTH) if (i === 1) graphics.setStrokeStyle(0.1) graphics.moveTo(LINE_WIDTH / 2, i * gridWidth + LINE_WIDTH / 2) .lineTo(gridWidth * wNum + LINE_WIDTH / 2, i * gridWidth + LINE_WIDTH / 2) } graphics.setStrokeStyle(LINE_WIDTH) // 画纵向的线条 for (i = 0; i <= wNum; i++) { if (i === wNum || i === 0) graphics.setStrokeStyle(LINE_WIDTH) if (i === 1) graphics.setStrokeStyle(.1) graphics.moveTo(i * gridWidth + LINE_WIDTH / 2, LINE_WIDTH / 2) .lineTo(i * gridWidth + LINE_WIDTH / 2, gridWidth * hNum + LINE_WIDTH / 2) } } /** * 坐标类 */ function Point(x, y) { this.x = x this.y = y } /** * 根据移动的方向,获取当前坐标的下一个坐标 * @param direction 移动的方向 */ Point.prototype.nextPoint = function nextPoint(direction) { debugger var point = new Point(this.x, this.y) switch (direction) { case DIR_ENUM.UP: point.y -= 1 break case DIR_ENUM.DOWN: point.y += 1 break case DIR_ENUM.LEFT: point.x -= 1 break case DIR_ENUM.RIGHT: point.x += 1 break } return point } /** * 初始化蛇的坐标 * @returns {[Point,Point,Point,Point,Point ...]} * @private */ function initSnake() { return SNAKE_START_POINT.map(function (item) { return new Point(item[0], item[1]) }) } /** * 绘制蛇 * @param graphics * @param snakes // 蛇坐标 */ function drawSnake(graphics, snakes) { graphics.clear() graphics.beginFill("#a088ff") var len = snakes.length for (var i = 0; i < len; i++) { if (i === len - 1) graphics.beginFill("#ff6ff9") graphics.drawRect( snakes[i].x * gridWidth + LINE_WIDTH / 2, snakes[i].y * gridWidth + LINE_WIDTH / 2, gridWidth, gridWidth) } } /** * 改变蛇身坐标 * @param snakes 蛇坐标集 * @param direction 方向 */ function updateSnake(snakes, direction) { var oldHead = snakes[snakes.length - 1] var newHead = oldHead.nextPoint(direction) // 超出边界 游戏结束 if (newHead.x < 0 || newHead.x >= num.w || newHead.y < 0 || newHead.y >= num.h) { gameState = GAME_STATE_ENUM.END } else if (snakes.some(function (p) { // ‘吃’到自己 游戏结束 return newHead.x === p.x && newHead.y === p.y })) { gameState = GAME_STATE_ENUM.END } else { snakes.push(newHead) snakes.shift() } } /** * 引擎 * @param graphics * @param snakes */ function move(graphics, snakes, stage) { clearTimeout(window._engine) // 重启时关停之前的引擎 run() function run() { directionNow = directionNext updateSnake(snakes, directionNow) // 更新蛇坐标 if (gameState === GAME_STATE_ENUM.END) { end() } else { drawSnake(graphics, snakes) stage.update() window._engine = setTimeout(run, 500) } } } /** * 游戏结束回调 */ function end() { console.log('游戏结束') } function init() { $('canvas').attr('width', canvasWidth) // 给canvas设置宽高属性赋值上当前canvas的宽度和高度(单用样式配置宽高会被拉伸) $('canvas').attr('height', canvasHeight) directionNow = directionNext = DIR_ENUM.DOWN // 初始化蛇的移动方向 var snakes = initSnake() var stage = new createjs.Stage($('canvas')[0]) var grid = new createjs.Shape() var snake = new createjs.Shape() drawGrid(grid.graphics) // 绘制格子 drawSnake(snake.graphics, snakes) stage.addChild(grid) stage.addChild(snake) stage.update() move(snake.graphics, snakes, stage) } init() }) 效果图 效果图(gif): 3.移动蛇 制作4个按钮,控制移动方向 代码 index.html ...