今天呢,主要和小伙伴们分享一下一个贪吃蛇游戏从构思到实现的过程~因为我不是很喜欢直接PO代码,所以只copy代码的童鞋们请出门左转不谢。

按理说canvas与其应用是老生常谈了,可我在准备阶段却搜索不到有用的资料(不是代码!),所以说呢,只能自力更生 -_-

首先是大致要考虑的东西:

1.要有蛇(没蛇怎么叫贪吃蛇)。

2.然后要有地图(蛇是不能上天的)。

3.不能水平\垂直掉头(如果想掉头,需要至少变换方位并且至少移动一格才可)。

4.食物(不然怎么贪吃)。

5.吃了食物要变长(这才是精髓)。

PS:~现在我回想起来,当时的确只想到这么多(⊙﹏⊙)

构思完毕,开工!

怎么做呢?从大到小,先画个矩形作地图,可我觉得太丑,于是花了一张图出来:

        context.beginPath();
var bgImg = new Image();
bgImg.src = "img/background.png";
context.drawImage(bgImg, 0, 0, 600, 600);
context.closePath();

现在我们有地图了

地图上好像缺点什么……没错就是礼物,所以我们现在生成礼物,那么问题来了:礼物最多有几个、生成位置、何时生成。

我这里暂时定义为:最多2个、随机位置生成、当礼物个数小于2时生成至2个。

接下来就很简单了,上图中,允许蛇活动的范围是14颗树(周围两颗树是墙),然后16颗树=600px,很容易我们得到每格多宽~

所以呢,我们只需要定义一个随机生成1-14整数的方法就可以很轻松找到应该生成的位置:

 //随机数
 function selectfrom() {
  return Math.floor(Math.random() * 14 + 1);
 }

然后再用求出的数乘以每一格子的宽度,即可求出生成的具体X坐标,因为是正方形,所以Y也一样:

 var x = selectfrom() * (600/16);
 var y = selectfrom() * (600/16);

并且每得到一组礼物坐标后,都需要存储在一个数组内(一会儿有大用处),至于画矩形太基础我就不说了。

And Now,我们有了礼物,有了地图,就差蛇了,那么问题又来了:出生的蛇多长、出生地、死亡方式、移动方式、转弯方式、如何判断吃掉了礼物、吃掉了礼物变长到哪里。

出生蛇长度:实际编写过程中,我发现默认长度1和2都不能够很好的体现“蛇的转弯”,所以定义为3,并且需将蛇身所有坐标记录在数组内。

出生地:地图中央或者自己定一个位置(按照格子来分),XY坐标求取方式上面已经说过不再赘述。

死亡方式:碰到障碍,或者(吃到自己)蛇头碰到蛇身。

移动方式:通过定义一个全局变量记录当前方向(0、1、2、3,默认1),并且使用计时器驱动蛇运动。

转弯方式:加入键盘按键检测事件,当方向键按下的时候修改-记录方向的全部变量即可。

如何判断吃掉了礼物:每次蛇头移动时,都要遍历下礼物集合(上面有说过),如果蛇头将要移动到的下个坐标与之重合了,则视为吃掉了礼物。

吃掉了礼物变长到哪里:直接加在头部可能会导致意外的死亡,所以我决定吃到礼物后的下一次移动不消除蛇尾(最后一个元素)。

有了上面的构思,我们可以着手定义一些可能会用到的公共变量:

var canvas = document.getElementById("mycanvas");//画布主体
var context = canvas.getContext("2d");
var timer;//计时器
const WIDTH = canvas.width;//画布宽
const HEIGHT = canvas.height;//画布高
const XSUM = 16; //画布宽分为几格
const YSUM = 15; //画布高分为几格
const MAXFFOD = 2; //最大食物数量
var score = 0;//定义记录游戏得分
var xsplit = WIDTH / XSUM; //x每一格子的宽度
var ysplit = HEIGHT / YSUM; //y每一格子的高度
var foodcount = 0; //当前食物数量
var sinak = []; //贪吃蛇坐标集
var get = []; //礼物坐标集
var MoveTo = 1; //移动方向 默认1(右)

有了这些变量,是不是发现很多东西都通了呢?

我们先来画蛇:

//画贪吃蛇
function drawsinak(sl) { //sl默认长度
context.beginPath();
context.fillStyle = "#000";
var ling = 0; //贪吃蛇被打印长度
for (var r = 0; r < sinak.length; r++) {
context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
ling++;
}
if (ling == 0) {
for (var i = 0; i < sl; i++) {
context.fillRect(xsplit * (7 - i), ysplit * 6, xsplit, ysplit); //默认出生点:7,6默认中心点
sinak.push(xsplit * (7 - i) + ',' + ysplit * 6);
}
}
context.fill();
context.closePath();
}

可以看到我将生成的蛇的坐标都计入了数组内,生成的礼物自然也要计入:

  context.beginPath();
var x = selectfrom(XSUM - 2) * xsplit;
var y = selectfrom(YSUM - 2) * ysplit;
context.fillStyle = "red";
for (var i = 0; i < get.length; i++) {
context.fillRect(get[i].split(',')[0], get[i].split(',')[1], xsplit, ysplit);
context.fill();
foodcount++;
}
if (MAXFFOD > foodcount) {
context.fillRect(x, y, xsplit, ysplit);
context.fill();
foodcount++;
get.push(x + ',' + y);
}
context.closePath();

接下来比较重要了,蛇的移动,以及吃到礼物和触发死亡判断:

//移动方法
//[c]移动方向 上右下左 0123
function sinakMove(c) {
context.beginPath(); //默认右侧为头
var tou = sinak[0]; //头
var weiba = sinak[sinak.length - 1]; //尾巴 var oldX = tou.split(',')[0]; //头部旧X坐标
var oldY = tou.split(',')[1]; //头部旧Y坐标 var newX = 0; //头部最新X坐标
var newY = 0; //头部最新Y坐标 //计算头部最新XY坐标
switch (c) {
case 0:
newX = oldX;
newY = oldY - ysplit;
break;
case 1:
newX = (oldX - 0) + xsplit;
newY = oldY;
break;
case 2:
newX = oldX;
newY = (oldY - 0) + ysplit;
break;
case 3:
newX = oldX - xsplit;
newY = oldY;
break;
} var flag = 0; //有沒有吃到礼物 0沒有1有 //如果吃到了礼物,则不消减尾部最后元素
for (var i = 0; i < get.length; i++) {
if (newX == get[i].split(',')[0] && newY == get[i].split(',')[1]) {
sinak.unshift(newX + ',' + newY);
foodcount--; //礼物计数减少1个
get.splice(i, 1); //清空礼物
flag = 1;
}
}
//如果沒有吃到礼物,则判断是否碰到障碍或吃到自己
if (flag == 0) {
for (var i = 0; i < sinak.length; i++) {
if (newX == sinak[i].split(',')[0] && newY == sinak[i].split(',')[1]) {
if (confirm('吃掉了自己,游戏失败!是否重新开始?')) {
location.reload(true);
} else {
context.clearRect(0, 0, WIDTH, HEIGHT);
}
}
}
if (xsplit * (XSUM - 2) < newX || ysplit * (YSUM - 2) < newY || newX == 0 || newY == 0) {
if (confirm('撞墙了,游戏失败!是否重新开始?')) {
location.reload(true);
}
}
} //如果没有吃到礼物,那么进行普通移动
if (flag == 0) {
sinak.unshift(newX + ',' + newY);
sinak.splice(sinak.length - 1, 1);
} //画蛇
for (var r = 0; r < sinak.length; r++) {
context.fillRect(sinak[r].split(',')[0], sinak[r].split(',')[1], xsplit, ysplit);
}
context.closePath();
}

控制蛇的方向:

//键盘事件
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var move = 0; //移动方向
if (e && e.keyCode == 37) { //左
move = (MoveTo == 1 ? 1 : 3);
} else if (e && e.keyCode == 38) { //上
move = (MoveTo == 2 ? 2 : 0);
} else if (e && e.keyCode == 39) { //右
move = (MoveTo == 3 ? 3 : 1);
} else if (e && e.keyCode == 40) { //下
move = (MoveTo == 0 ? 0 : 2);
} else if (e && e.keyCode == 32) {//暂停游戏
clearInterval(timer);
}
MoveTo = move; //修改当前移动方向
};

这里做了防误操作,当蛇正在朝向某方向移动时,直接输入反方向是无效的。如:蛇正向右走,这时直接按←键是无效的,仍然往右走。

一路跟着做到这里,相信大家的贪吃蛇已经可以正常游戏了,不过我这个做的很糙,大家可以加入一些自己的想法,比如:

计分通关,通关之后通过加快蛇的移动速度来增加难度。

随机生成多种果实,比如加速果实,双倍成长果实等。

加入WebSocket,实现网络版贪吃蛇。

我写过关于WebSocket的实现,有兴趣的也可以去看看,下面是链接:

基于SuperSocket实现的WebSocket(后端)

基于SuperSocket实现的WebSocket(前端)

欢迎大家讨论、提问~

Canvas进阶——制作小游戏【贪吃蛇】的更多相关文章

  1. 用Canvas制作小游戏——贪吃蛇

    今天呢,主要和小伙伴们分享一下一个贪吃蛇游戏从构思到实现的过程~因为我不是很喜欢直接PO代码,所以只copy代码的童鞋们请出门左转不谢. 按理说canvas与其应用是老生常谈了,可我在准备阶段却搜索不 ...

  2. 使用JS制作小游戏贪吃蛇

    先看效果图: 过程如下: 1.首先创建一张画布地图<div class="map"> </div>: 2.创建食物的自调用函数 (function (){ ...

  3. 第一个windows 小游戏 贪吃蛇

    最近用dx尝试做了一个小的贪吃蛇游戏,代码放到github上面:https://github.com/nightwolf-chen/MyFreakout 说一下自己实现的过程: 首先,我把蛇这个抽象成 ...

  4. JavaScript面向对象编程小游戏---贪吃蛇

    1 面向对象编程思想在程序项目中有着非常明显的优势: 1- 1 代码可读性高.由于继承的存在,即使改变需求,那么维护也只是在局部模块 1- 2 维护非常方便并且成本较低. ​ 2 这个demo是采用了 ...

  5. 以前写的canvas 小游戏 贪吃蛇代码

    效果如图,完成了贪吃蛇的基本的功能 代码地址 :https://github.com/my-new-git-hub/canvasSnake.git 预览地址:https://www.kzc275.to ...

  6. 手把手教学h5小游戏 - 贪吃蛇

    简单的小游戏制作,代码量只有两三百行.游戏可自行扩展延申. 源码已发布至github,喜欢的点个小星星,源码入口:game-snake 游戏已发布,游戏入口:http://snake.game.yan ...

  7. Win32小游戏--贪吃蛇

    近日里学习了关于win32编程的相关知识,利用这些知识制作了一款贪吃蛇小游戏,具体细节还是分模块来叙述 前期准备:在网上找到一些贪吃蛇的游戏素材图片,以及具体的逻辑框图 在正式写功能之前,先把一系列环 ...

  8. Java小游戏贪吃蛇

    package snake; import java.awt.BorderLayout;import java.awt.Canvas;import java.awt.Color;import java ...

  9. 使用JavaScript实现简单的小游戏-贪吃蛇

    最近初学JavaScript,在这里分享贪吃蛇小游戏的实现过程, 希望能看到的前辈们能指出这个程序的不足之处. 大致思路 首先要解决的问题 随着蛇头的前进,尾巴也要前进. 用键盘控制蛇的运动方向. 初 ...

随机推荐

  1. Java Socket TCP编程

    package com; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * Socket Se ...

  2. HTML5 Web Notifications 桌面推送小记

    目录 简介 常用API 手动设置权限 简介 Web Notifications目前在w3c的协议中已经是"推荐"(REC:Recommendation)阶段,除了iE外,各大现代浏 ...

  3. 使用OAuth2.0协议的github、QQ、weibo第三方登录接入总结

    目录 第三方接入总结 OAuth2.0介绍 github OAuth2.0登录接入 国内第三方应用商SDK使用 微博SDK 腾讯QQ SDK passport.js插件使用 安装 相关中间件.路由 返 ...

  4. [DeeplearningAI笔记]序列模型2.1-2.2词嵌入word embedding

    5.2自然语言处理 觉得有用的话,欢迎一起讨论相互学习~Follow Me 2.1词汇表征 Word representation 原先都是使用词汇表来表示词汇,并且使用1-hot编码的方式来表示词汇 ...

  5. Java设计模式の责任链模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...

  6. [Luogu 2486] SDOI2011 染色

    [Luogu 2486] SDOI2011 染色 树剖水题,线段树维护. 详细题解不写了. 我只想说我写的线段树又变漂亮了qwq #include <algorithm> #include ...

  7. 【BZOJ】2154: Crash的数字表格 莫比乌斯反演

    [题意]给定n,m,求Σlcm(i,j),1<=i<=n,1<=j<=m,n,m<=10^7. [算法]数论(莫比乌斯反演) [题解] $$ans=\sum_{i\leq ...

  8. 【leetcode 简单】第二十七题 二叉树的最小深度

    给定一个二叉树,找出其最小深度. 最小深度是从根节点到最近叶子节点的最短路径上的节点数量. 说明: 叶子节点是指没有子节点的节点. 示例: 给定二叉树 [3,9,20,null,null,15,7], ...

  9. Linux mint 18.1 / Ubuntu 16.04 安装steam

    这里以Limit Mint 18.1为例: 安装steam: sudo dpkg -i steam.deb 运行后会有如下错误: 直接运行如下命令修复, 并自动启动steam: LD_PRELOAD= ...

  10. tp5 r3 一个简单的SQL语句调试实例

    tp5 r3 一个简单的SQL语句调试实例先看效果核心代码 public function index() { if (IS_AJAX && session("uid&quo ...