用到脚本语言
javascript es5 es6 es7
node
| models
| socket.io
mysql
先了解下目录结构
前端
css:游戏的一些样式
js:动态脚本 触碰系统等】
index.html 网页脚本
images 图片
后台
node_modules 模块
main.js 主启动
搭建服务
服务器部署
我使用的是nginx node部署到服务器 需要 反向代理接口
server服务
listen 监听端口号80
location/api 监听到/api的参数就将值代理给3000端口号
准备工作
brick砖块
explose爆炸图
my坦克图
bullet子弹图
分享图片
地图计算
所有素材 模型 大小 32 * 32
横向14 * 32
竖向18 * 32
<canvas id = 'ctx' width = '448' height = '576'></canvas>
首先我们先获取canvas
let c = document.getElementById("ctx");
var ctx=c.getContext("2d");
接下来创建绘制图片系统
drawCreate = async (url,x,y,w,h,id) =>{ //绘制图片
let image = new Image();
let app = await new Promise((resolve)=>{
image.src = url;
image.onload = function(){
resolve(image);
}
}).then((image)=>{
if(id == 0){
_1ps = {
w:w,
h:h,
x:x,
y:y
}
}
ctx.drawImage(image,x,y,w,h);
});
return image;
}
改绘制图片方法有6个参数 url为图片路径 x y 为坐标 w h 为图片 大小 id 0,1,2 0为1p玩家 1为2p玩家 2为地图绘制也就是素材
移动系统
//添加移动事件
let move = async (url,obj,type,value) =>{ //玩家移动方法
ctx.clearRect(obj.x,obj.y,obj.w,obj.h);
if(type == 1){
if(obj.up == undefined && value < 0){ //判断是否是首次加载图片
let item = await drawCreate(url,obj.x,obj.y + value,obj.w,obj.h,0);
return item;
}
if(obj.down == undefined && value > 0){
if(type == 1){
let item = await drawCreate(url,obj.x,obj.y + value,obj.w,obj.h,0);
return item;
}
}
}else{
if(obj.right == undefined && value > 0){
let item = await drawCreate(url,obj.x + value,obj.y,obj.w,obj.h,0);
return item;
}
if(obj.left == undefined && value < 0){
let item = await drawCreate(url,obj.x + value,obj.y,obj.w,obj.h,0);
return item;
}
}
if(type == 1){
ctx.drawImage(url,obj.x,obj.y += value ,obj.w,obj.h);
return url;
}else{
ctx.drawImage(url,obj.x += value,obj.y ,obj.w,obj.h);
return url;
}
}
document.body.onkeydown = async (event) =>{
let item = "";
switch(event.keyCode){
case 65: //a后
if(mapScope(_1ps.x - 4,0,32,28))return;
item = await move(_1ps.left || _1p.left,_1ps,0,-4);
_1ps.left = item;
break;
case 68: //d前
if(mapScope(_1ps.x + 4,0,32,28)))return;
item = await move(_1ps.right || _1p.right,_1ps,0,4);
_1ps.right = item;
break;
case 83: //s下
if(mapScope(_1ps.y + 4,1,32,28)))return;
item = await move(_1ps.down || _1p.down,_1ps,1,4);
_1ps.down = item;
break;
case 87: //w上
if(mapScope(_1ps.y - 4,1,32,28)))return;
item = await move(_1ps.up || _1p.up,_1ps,1,-4);
_1ps.up = item;
break;
}
}
范围限制
let mapScope = (value,type,w,h) => { //检测是否超出地图限制 type = 0 左右地图限制 type = 1 上下地图限制
if(type == 0){
if(value < 0 || value > 448 - w)return true;
}else{
if(value > 576 - h || value < 0)return true;
}
}
编辑一张地图
因为先前设置的地图高度是32*18 宽度是32*14 我们要创建一个 14*18的二维数组
像上面构造玩家一样构造没个建筑物 有些不合适 隐藏 需要一个地图构造器
let level = [ //关卡
[
[3,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,2,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,2,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,3,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,4,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,5,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,6,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
]
地图构造器
现在地图控制器设置一个规则 方便创建出来的对象样式 然后对该规则做一套适配
1p 0
2p 1
普通砖 2
刚砖 3
草坪 4
水池 5
冰路 6
修改上面我们改好的loadGame
let loadGame = async () =>{ //加载游戏
let item = await drawCreate(_1p.up,32,545,32,32,0);
mapCreate();
}
let mapCreate = async () => {//地图构造器
// 1p:1 普通砖:2 刚转:3 草坪:4 水池:5 冰路:6
let s = 0;
for(let i = 0;level[levels][i]!=undefined;i++){
for(let j = 0;level[levels][i][j]!=undefined;j++){
if(level[levels][i][j] > 0){
if(level[levels][i][j] >= 1){
let item = await drawCreate(matter[level[levels][i][j] - 2],j*32,i*32,32,32);
obj.push({ //将所有建筑物的坐标保存到obj
w:32,
h:32,
x:j*32,
y:i*32,
ev:level[levels][i][j],
index:s++,
el:item,
id:createId()
})
}
}
}
}
}
let createId = (length = 32)=>{ //创建id
let str = "";
let arr = "qwertyuiopasdfghjklmnbcvxzQWERTYUIOPLKJHGFDSAZXCBNM1234567890"
for(let i = 0;i<=length;i++){
str += arr[Math.round(Math.random() * 61)];
}
return str;
}
地图构造器原理其实很简单 就是遍历关卡的数组中以及对应的位置
当前现在虽然地图制作好了 但是由于没有触碰检测 触碰砖块时怪物就会消失 因此我们还要写个触碰检测
每次移动和子弹发射以及一系列的操作都需要通过触碰检测
触碰检测
let detection = (x,y,w,h,type) => { //检测触碰
for(let i = 0;obj[i]!=undefined;i++){
if(type == 0){
if((y < obj[i].y + 40 && y > obj[i].y) && (x >= obj[i].x - 28 && x <= obj[i].x + 28))return true;
}else if(type == 1){
if((y < obj[i].y + 28 && y > obj[i].y - 40) && (x >= obj[i].x - 28 && x <= obj[i].x + 28))return true;
}else if(type == 2){
if((y < obj[i].y + 32 && y > obj[i].y - 28) && (x >= obj[i].x - 28 && x <= obj[i].x + 28))return true;
}else if(type == 3){
if((y < obj[i].y + 32 && y > obj[i].y - 28) && (x >= obj[i].x - 28 && x <= obj[i].x + 28))return true;
}
}
}
将该检测放到移动时
document.body.onkeydown = async (event) => {
let item = "";
switch(event.keyCode){
case 65: //a后
if(mapScope(_1ps.x - 4,0,32,28)))return;
if(detection(_1ps.x - 4,_1ps.y,32,32,2))return;
item = await move(_1ps.left || _1p.left,_1ps,0,-4);
_1ps.left = item;
break;
case 68: //d前
if(mapScope(_1ps.x + 4,0,32,28)))return;
if(detection(_1ps.x + 4,_1ps.y,32,32,3))return;
item = await move(_1ps.right || _1p.right,_1ps,0,4);
_1ps.right = item;
break;
case 83: //s下
if(mapScope(_1ps.y + 4,1,32,28)))return;
if(detection(_1ps.x,_1ps.y - 4,32,32,1))return;
item = await move(_1ps.down || _1p.down,_1ps,1,4);
_1ps.down = item;
break;
case 87: //w上
if(mapScope(_1ps.y - 4,1,32,28)))return;
if(detection(_1ps.x,_1ps.y + 4,32,32,0))return;
item = await move(_1ps.up || _1p.up,_1ps,1,-4);
_1ps.up = item;
break;
case 74: //j子弹
createBullet();
break;
}
}
接下来我们构造子弹
构造子弹
let createBullet = async (x,y,direction) => { //创建子弹
if(disabled == true)return;
if(direction == 0){ //上
_bullet = await drawCreate(bullet,x + 11,y - 8,8,8);
bulletMove(0,x+11,y - 8,8,8);
}
if(direction == 1){ //下
_bullet = await drawCreate(bullet,x + 11,y + 24,8,8);
bulletMove(1,x+11,y+24,8,8)
}
if(direction == 2){ //右
_bullet = await drawCreate(bullet,x + 32,y + 4,8,8);
bulletMove(2,x+32,y+4,8,8);
}
if(direction == 3){ //左
_bullet = await drawCreate(bullet,x - 8,y+4,8,8);
bulletMove(3,x - 8,y+4,8,8);
}
disabled = true;
setTimeout(()=>{ //子弹设置0.5秒一发
disabled = false;
},500)
}
当然 现在这些子弹还是不会动的
稍后我们修改让其动起来
会动的子弹
function bulletMove(d,x,y,w,h){ //子弹移动 方向 坐标 大小
let value = d == 0 || d == 3?-4:4;
if(disabled == true)return;
let move = setInterval(()=>{
if(d == 0 || d == 1){
ctx.clearRect(x,y,w,h);
if(mapScope(y + value,1)){
clearInterval(move);
return false;
}
ctx.drawImage(_bullet,x,y += value,w,h);
}else{
ctx.clearRect(x,y,w,h);
if(mapScope(x + value,0)){
clearInterval(move);
return false;
}
ctx.drawImage(_bullet,x += value,y,w,h);
}
},20);
}
子弹触碰
let bulletDetection = (d,x,y,w,h) =>{ //子弹触碰检测
for(let i = 0;obj[i]!=undefined;i++){
if(d == 0){
if((y - 36 <= obj[i].y && y + 8 >= obj[i].y) && (x >= obj[i].x - 8 && x <= obj[i].x + 28))return true;
}else if(d == 1){
if((y >= obj[i].y - 8 && y <= obj[i].y + 8) && (x >= obj[i].x - 8 && x <= obj[i].x + 28))return true;
}else if(d == 2){
if((x >= obj[i].x - 8 && x <= obj[i].x + 32) && (y >= obj[i].y - 8 && y <= obj[i].y + 32))return true;
}else if(d == 3){
if((x >= obj[i].x - 8 && x <= obj[i].x + 32) && (y >= obj[i].y - 8 && y <= obj[i].y + 32))return true;
}
}
}
let bulletDetection = (d,x,y,w,h) =>{ //子弹触碰检测
for(let i = 0;obj[i]!=undefined;i++){
if(d == 0){
if((y - 36 <= obj[i].y && y + 8 >= obj[i].y) && (x >= obj[i].x - 8 && x <= obj[i].x + 28)){
if(touchBulletElement(obj[i]))return true;
}
}else if(d == 1){
if((y >= obj[i].y - 8 && y <= obj[i].y + 8) && (x >= obj[i].x - 8 && x <= obj[i].x + 28)){
let bullet = {
x:x,
y:y,
}
if(touchBulletElement(obj[i],bullet))return true;
}
}else if(d == 2){
if((x >= obj[i].x - 8 && x <= obj[i].x + 32) && (y >= obj[i].y - 8 && y <= obj[i].y + 32)){
if(touchBulletElement(obj[i],bullet))return true;
}
}else if(d == 3){
if((x >= obj[i].x - 8 && x <= obj[i].x + 32) && (y >= obj[i].y - 8 && y <= obj[i].y + 32)){
if(touchBulletElement(obj[i],bullet))return true;
}
}
}
}
判断当前打到的道具是什么
let touchBulletElement = (obj,bullets,d) =>{ //判断子弹触碰到的是什么元素
switch(obj.ev){
case 2:
attackDirection(obj,bullets,d)
break;
case 3: //刚转
attackDirection(obj,bullets,d)
break;
case 6: //草地
ctx.clearRect(obj.x,obj.y,obj.w,obj.h);
ctx.drawImage(obj.el,obj.x,obj.y,obj.w,obj.h);
return false;
case 5: //水池
ctx.clearRect(obj.x,obj.y,obj.w,obj.h);
ctx.drawImage(obj.el,obj.x,obj.y,obj.w,obj.h);
return false;
}
return true;
}
修改这些后需要将创建子弹套上触碰
接下来该判断子弹打中了什么元素了
在此我们来分析一下
每块砖都要一分为四 也就是说当前x ~ 16 y ~ 16为第一块砖 x + 16 ~ x + 32 ~ y ~ y + 16 第二块砖
x ~ 16 y + 16 ~ y + 32第三块砖 x + 16 y + 16 ~ y + 32 最后一块砖
在bulletDetection嵌套检测攻击子弹函数
打中后清空砖块
let attackDirection = (obj,bullets,d) =>{ //攻击砖块
ctx.clearRect(obj.x,obj.y,32,32);
removeObj(obj.id)
}
let removeObj = (id) =>{ //删除对象
for(let i = 0;obj[i]!=undefined;i++){
if(obj[i].id == id)obj.splice(i,1);
}
}