シンプルで小さなゲーム制作、コードの量だけ2または300行という。ゲームは、拡張アプリケーションを拡張するのは自由です。
ソースコードは、ソースの入り口、小さな星のポイントのように、githubのに投稿されています。ゲーム-蛇
ゲームがリリースされている、ゲームの入り口:http://snake.game.yanjd.top
最初のステップ - アイデアを作ります
ゲームを実現する方法次のように私は考えていたここで、最初に考えています:
- 使用して、マップ(チェック柄のドレスを)描画するキャンバス。
- 使用して蛇を描画するキャンバス、マップグリッドによって占有されています。ヘビは、すなわち、移動するためにしてみましょう:更新ヘビ座標が再描画。
- 4つの方向ボタンを作成し、次のヘビの方向を制御します。
- 果実は、ランダムマップ上に描かれたときに長さを増加させ、そして、果物に「食べる」にモバイル蛇「移動速度」。
- 開始と終了キーの設定、スコア表示、歴史
第二段階 - 選択枠
最初の段階から見ると、私はこのゲームで達成したい、だけでなく、どのような物理エンジン、ない高度なUIの影響はありません、それは上に描画するために使用キャンバスに必要です。あなたは便利な操作のためのシンプルなポイント、描画キャンバスから選択することができます。選挙が慎重に選択された後EaselJSを、キャンバス、キャンバスとダイナミックなエフェクトを描画するため、比較的軽いです。
第三段階 - 開発
レディ
カタログと文書作成:
| - index.htmlを
| - JS
| - | - main.js
| - CSS
| - | - stylesheet.css
index.htmlを輸入関連の依存関係とスタイルファイルやスクリプトファイル。デザインは、エリアを描画キャンバスの画面の高さの80%、アクションバーの高さの20%であるとスコアエリアを示しています。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible"
content="ie=edge">
<title>贪吃蛇</title>
<link rel="stylesheet" href="css/stylesheet.css">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
</head>
<body>
<div id="app">
<div class="content-canvas">
<canvas></canvas>
</div>
<div class="control">
</div>
</div>
<script src="https://cdn.bootcss.com/EaselJS/1.0.2/easeljs.min.js"></script>
<!-- 载入jquery 方便dom操作 -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<!-- sweetalert 美化alert用的 -->
<script src="https://cdn.bootcss.com/sweetalert/2.1.2/sweetalert.min.js"></script>
<script src="js/main.js"></script>
</body>
</html>
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.描画グリッド
注意ポイント(遭遇した問題と解決策):
- ルートのキャンバスには幅がありませんが、ラインの幅があります。例えば:10pxの線幅を描画する(0、100)(0、0)から出発して、線の半分がない領域、可視外です。オフセット処理スキームは、例えば、出発点である:(0、0)から(0、100)(5,100)と(5,0)から変更さ10pxの線幅を、描画するために、オフセットは、線の幅の半分であります。
- スタイル定義を座標キャンバスの幅と高さに延伸され、処理溶液は、キャンバス要素の幅と高さのプロパティに提供され、現在の実際の値が高い幅です。
コード
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
スネーク点座標(アレイ)のシリーズとして想像することができ、アレイ、削除座標尾に新しい座標ヘッドを追加するために「移動」。同様に、キュー、FIFO。
コード
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を
...
<div class="control">
<div class="row">
<div class="btn">
<button id="UpBtn">上</button>
</div>
</div>
<div class="row clearfix">
<div class="btn half-width left">
<button id="LeftBtn">左</button>
</div>
<div class="btn half-width right">
<button id="RightBtn">右</button>
</div>
</div>
<div class="row">
<div class="btn">
<button id="DownBtn">下</button>
</div>
</div>
</div>
</div>
...
stylesheet.css
...
.control .row {
position: relative;
height: 33%;
text-align: center;
}
.control .btn {
box-sizing: border-box;
height: 100%;
padding: 4px;
}
.control button {
display: inline-block;
height: 100%;
background-color: white;
border: none;
padding: 3px 20px;
border-radius: 3px;
}
.half-width {
width: 50%;
}
.btn.left {
padding-right: 20px;
float: left;
text-align: right;
}
.btn.right {
padding-left: 20px;
float: right;
text-align: left;
}
.clearfix:after {
content: '';
display: block;
clear: both;
}
mian.js
...
/**
* 改变蛇行进方向
* @param dir
*/
function changeDirection(dir) {
/* 逆向及同向则不改变 */
if (directionNow + dir === 0 || directionNow === dir) return
directionNext = dir
}
/**
* 绑定相关元素点击事件
*/
function bindEvent() {
$('#UpBtn').click(function () { changeDirection(DIR_ENUM.UP) })
$('#LeftBtn').click(function () { changeDirection(DIR_ENUM.LEFT) })
$('#RightBtn').click(function () { changeDirection(DIR_ENUM.RIGHT) })
$('#DownBtn').click(function () { changeDirection(DIR_ENUM.DOWN) })
}
function init() {
bindEvent()
...
}
レンダリング
図の効果(GIF):
4.ドローフルーツ
尾は削除されていない「GET」場合の果実をプロットした二つのランダムに選択された座標のポイントは、それが決定されます。難易度を高めるためにインターバルタイマーを短くしてください。
注意ポイント(遭遇した問題と解決策):蛇の座標を占有することはできませんフルーツを追加し、座標が既に占有場合、それはランダムな座標を生成し続け、ランダムに生成された座標の始まりと考えられています。これは、問題全体のインターフェースが残りの二つの座標は(極端な場合には、画面全体のヘビを占め、彼はグリッドの2を送った)ときに使用可能であり、これが事実であることを、これらの最後の2に到達するために、ランダムに選択された座標を停止発見しました調整するために多くの時間を消費します。その後、ループの蛇が使用可能なランダムな座標の1、その後、座標除外1使用不能座標、すべての座標への方法、統計を変更しました。
コード
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 // 游戏状态
var scope = 0 // 分数
/**
* 绘制格子地图
* @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) {
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, fruits, direction, fruitGraphics) {
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 if (fruits.some(function (p) { // ‘吃’到水果
return newHead.x === p.x && newHead.y === p.y
})) {
scope++
snakes.push(newHead)
var temp = 0
fruits.forEach(function (p, i) {
if (newHead.x === p.x && newHead.y === p.y) {
temp = i
}
})
fruits.splice(temp, 1)
var newFruit = createFruit(snakes, fruits)
if (newFruit) {
fruits.push(newFruit)
drawFruit(fruitGraphics, fruits)
}
} else {
snakes.push(newHead)
snakes.shift()
}
}
/**
* 引擎
* @param graphics
* @param snakes
*/
function move(snakeGraphics, fruitGraphics, snakes, fruits, stage) {
clearTimeout(window._engine) // 重启时关停之前的引擎
run()
function run() {
directionNow = directionNext
updateSnake(snakes, fruits, directionNow, fruitGraphics) // 更新蛇坐标
if (gameState === GAME_STATE_ENUM.END) {
end()
} else {
drawSnake(snakeGraphics, snakes)
stage.update()
window._engine = setTimeout(run, 500 * Math.pow(0.9, scope))
}
}
}
/**
* 游戏结束回调
*/
function end() {
console.log('游戏结束')
}
/**
* 改变蛇行进方向
* @param dir
*/
function changeDirection(dir) {
/* 逆向及同向则不改变 */
if (directionNow + dir === 0 || directionNow === dir) return
directionNext = dir
}
/**
* 绑定相关元素点击事件
*/
function bindEvent() {
$('#UpBtn').click(function () { changeDirection(DIR_ENUM.UP) })
$('#LeftBtn').click(function () { changeDirection(DIR_ENUM.LEFT) })
$('#RightBtn').click(function () { changeDirection(DIR_ENUM.RIGHT) })
$('#DownBtn').click(function () { changeDirection(DIR_ENUM.DOWN) })
}
/**
* 创建水果坐标
* @returns Point
* @param snakes
* @param fruits
*/
function createFruit(snakes, fruits) {
var totals = {}
for (var x = 0; x < num.w; x++) {
for (var y = 0; y < num.h; y++) {
totals[x + '-' + y] = true
}
}
snakes.forEach(function (item) {
delete totals[item.x + '-' + item.y]
})
fruits.forEach(function (item) {
delete totals[item.x + '-' + item.y]
})
var keys = Object.keys(totals)
if (keys.length) {
var temp = Math.floor(keys.length * Math.random())
var key = keys[temp].split('-')
return new Point(Number(key[0]), Number(key[1]))
} else {
return null
}
}
/**
* 绘制水果
* @param graphics
* @param fruits 水果坐标集
*/
function drawFruit(graphics, fruits) {
graphics.clear()
graphics.beginFill("#16ff16")
for (var i = 0; i < fruits.length; i++) {
graphics.drawRect(
fruits[i].x * gridWidth + LINE_WIDTH / 2,
fruits[i].y * gridWidth + LINE_WIDTH / 2,
gridWidth, gridWidth)
}
}
function init() {
bindEvent()
$('canvas').attr('width', canvasWidth) // 给canvas设置宽高属性赋值上当前canvas的宽度和高度(单用样式配置宽高会被拉伸)
$('canvas').attr('height', canvasHeight)
directionNow = directionNext = DIR_ENUM.DOWN // 初始化蛇的移动方向
var snakes = initSnake()
var fruits = []
fruits.push(createFruit(snakes, fruits))
fruits.push(createFruit(snakes, fruits))
var stage = new createjs.Stage($('canvas')[0])
var grid = new createjs.Shape()
var snake = new createjs.Shape()
var fruit = new createjs.Shape()
drawGrid(grid.graphics) // 绘制格子
drawSnake(snake.graphics, snakes)
drawFruit(fruit.graphics, fruits)
stage.addChild(grid)
stage.addChild(snake)
stage.addChild(fruit)
stage.update()
move(snake.graphics, fruit.graphics, snakes, fruits, stage)
}
init()
})
レンダリング
図の効果(GIF):
スコアは、ゲームはヒント、ランキング上で表示します
この部分は比較的簡単です、あなたは次のデータ処理を表示することができます。コードのこの部分が出て表示されません。
レンダリング
エピローグ
インターフェイスは、メイン学習論理演算荒いです。いくつかのマイナーな問題の中で、しかし、すべて解決されます。createjsこのゲームエンジンは、学ぶことは非常に簡単であるだけで全体のAPIグラフィックレンダリング。