博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ionic+js+html5 飞行射击游戏
阅读量:6214 次
发布时间:2019-06-21

本文共 11738 字,大约阅读时间需要 39 分钟。

hot3.png

js+html5写一个简单的飞行游戏引擎,游戏画面使用canvas绘图,引擎核心代码不到500行,原生js,没有依赖。

代码地址:

游戏对象设计

飞机(包括玩家和敌人)、子弹、击中效果。具体属性见代码注释

/** * 基类 */function EObject (isShot) {  this.Oid = -1 // id  this.AllHp = 1 // 总HP  this.Hp = 1 // 当前Hp  this.icon // 图片  this.width = 0 // 宽度  this.height = 0 // 高度  this.speedY = 5 // Y速度  this.speedX = 5 // X速度  this.position = {x: 0,y: 0} // 位置  this.isDie = false // 是否死亡  this.isShot = false // 是否处于发射状态  this.shotInterVal = 500 // 发射周期  this.enableShot = isShot // 是否发射  var that = this  this.interval // 发射器  this.setShot = function (time) {    if (! this.enableShot) return false    this.shotInterVal = time    clearTimeout(this.interval)    this.interval = setInterval(function () {      that.isShot = true    }, time)  }}/** * 敌军 * @param {*是否发射} isShot  */function Enemy (isShot) {  this.enableShot = isShot  this.type = 'common'  EObject.call(this, isShot)}/** * 爆炸 */function Bullet () {  EObject.call(this,false)}/** * 子弹 */function Shot () {  this.type = 'common'  this.Attact = 1 // 攻击力  belong = 0  EObject.call(this,false)}/** *  * @param {*玩家} isShot  */function Player (isShot) {  this.enableShot = isShot  EObject.call(this, isShot)}

事件设计:

玩家左右移动,飞机位置,涉及到的事件包括click,mousedown,mousemove,mouseup。当玩家点击屏幕时,直接触发的是canvas,然而需要触发的是在canvas上画出的对象,所以引擎内部需要实现一套以游戏对象为中心的事件机制。

事件包装:包装事件对象从中抽取需要的数据,封装成一个统一的内部事件对象

事件注册:按照object-action-callback的形式注册。

事件触发:玩家点击屏幕时,在外部事件中进行事件包装,再按照action-eventinfo的方式触发内部事件,内部事件管理者检索之前注册的对象,如果有效就调用注册的callback执行特定的对象操作。

这样设计主要是考虑如果直接使用dom事件,那么每个事件对每个需要触发的事件都要独立的有效性检查,代码重合和扩炸性都很差。通过这个方式可以将游戏引擎事件和dom事件隔离开,也方便了添加新的对象事件。

外部事件转内部事件:

//移动事件  var moveFunc = (function () {    return function () {      eventRelative.triggerEvent('mouseMove', pacakgeEvent(arguments[0]))    }  })() //按下事件  var moveDownFunc = (function () {    return function () {      eventRelative.triggerEvent('mouseDown', pacakgeEvent(arguments[0]))    }  })()  //抬起事件  var moveUpFunc = (function () {    return function () {      eventRelative.triggerEvent('mouseUp', pacakgeEvent(arguments[0]))    }  })()  //点击事件  var clickFunc = (function () {    return function () {      eventRelative.triggerEvent('click', pacakgeClick(arguments[0]))    }  })()  //事件输入  this.EventInput = {    mouseDown: moveDownFunc,    mouseUp: moveUpFunc,    click: clickFunc,    move: moveFunc  }

事件包装:

//包装按键按下,抬起,移动事件  var pacakgeEvent = function (event) {    var evnetInfo = {      position: {x: 0,y: 0}    }    if (option.isAndroid) {      evnetInfo.position.x = event.gesture.center.pageX - player.width / 2 - event.gesture.target.offsetLeft      evnetInfo.position.y = Util.sceneYTransform(event.gesture.center.pageY) - player.height / 2    }else {      evnetInfo.position.x = event.offsetX - player.width / 2      evnetInfo.position.y = Util.sceneYTransform(event.offsetY) - player.height / 2    }    return evnetInfo  }  //包装单击事件  var pacakgeClick = function (event) {    var evnetInfo = {      position: {x: 0,y: 0}    }    if (option.isAndroid) {      evnetInfo.position.x = event.pageX - event.target.offsetLeft      evnetInfo.position.y = Util.sceneYTransform(event.pageY)    }else {      evnetInfo.position.x = event.offsetX      evnetInfo.position.y = Util.sceneYTransform(event.offsetY)    }    return evnetInfo  }

内部事件管理机制:

var eventRelative = {    click: [],            mouseDown: [],         mouseUp: [],    mouseMove: [],    //附加事件中 object-action-callback    attachEvet: function (target, action, callback) {      var eventMsg = {target: target,callback: callback}      var funcs = this[action]      if (!funcs) throw new Error('not support event')      funcs.push(eventMsg)    },    //触发事件中 action-eventInfo    triggerEvent: function (action, eventInfo) {      var funcs = this[action]      if (!funcs) throw new Error('not support event')      for (var i = 0;i < funcs.length;i++) {        if (Util.isEffect(funcs[i].target, action, eventInfo)) {          funcs[i].callback(funcs[i].target, eventInfo)        }      }    }  }

内部事件注册:

//玩家开始移动  eventRelative.attachEvet(player, 'mouseDown', function (obj, eventInfo) {    plainMoveState.isMouseDown = true  })  //玩家停止移动  eventRelative.attachEvet(player, 'mouseUp', function (obj, eventInfo) {    plainMoveState.isMouseDown = false  })  //重置事件  eventRelative.attachEvet(scene, 'click', function (obj, eventInfo) {    if (!isRunning && !plainMoveState.isMouseDown) {      isRunning = true      reset()    }  })  //玩家移动中  eventRelative.attachEvet(scene, 'mouseMove', function (obj, eventInfo) {    if (plainMoveState.isMouseDown === true) {      plainMoveState.position.x = eventInfo.position.x      plainMoveState.position.y = Util.sceneYTransform(eventInfo.position.y)    }  })

 

引擎核心设计:绘图、碰撞检测、对象运动、对象清理

鉴于js单线程问题,如果将所有的逻辑写在一条线上会导致单一流程过长,很可能无法保证画面的顺畅(要保证最低的24帧,那么两次渲染之间的事件间隔不到50ms)。

为了避开这个坑,一条核心原则是将4个模块完全隔离,每个模块的依赖仅仅是特定对象的状态,每个模块产生的影响也仅仅是修改特定对象的状态。设计类似于一个状态机。如子弹发射,对象会上挂一个time,每隔一段时间将自身的发射状态修改成可发射,对象运动模块会检查每个对象的发射状态,如果是可以发射的状态就为它创建子弹对象,再把状态修改成不可发射状态,玩家飞机移动的也采用了类似的机制。

实现方法是通过js的time定时触发模块的运行,通过调整time的触发间隔来控制系统的状态变化周期。由此带来的另一个好处是可以拉长不重要的模块触发间隔来节省资源(如对象清理,这个模块需要频繁的遍历,重建数组,慢)。

时间周期驱动

this.Start = function () {    // 拦截作用 必要时可以扩展出去    var before = function (callback) {      return function () {        if (!isRunning) return        callback()      }    }    drawTm = setInterval(before(draw), 50)    drawTm = setInterval(before(checkCollection), 50)    moveTm = setInterval(before(objectMove), 50)    clearTm = setInterval(before(clearObject), 5000)  }

绘图模块:

绘图分为两个部分,一个是顶部的hp横条,一个是下方游戏主场景。为了避免频繁的绘制canvas,使用了双内存的技术,主场景先在一个内存canvas上绘制,最后再一次性绘制到主场景位置上。

/**  * 绘图  */  function drawBuffer () {    var canvas = document.createElement('canvas')    var tempContext = canvas.getContext('2d')    canvas.height = option.ctxHeight    canvas.width = option.ctxWidth    function drawEobject (eobj, rotateValue) {      tempContext.drawImage(eobj.icon,        eobj.position.x , eobj.position.y,        eobj.width, eobj.height)    }    // 背景    tempContext.drawImage(option.resources.bg, 0, 0,      option.ctxWidth,      option.ctxHeight)    // 子弹    for (var index in shots) {      var shot = shots[index]      drawEobject(shot)    }    // 飞机    drawEobject(player)    // 敌军    for (var index in enemies) {      var enemy = enemies[index]      drawEobject(enemy)    }    // 死亡    for (var index in bullets) {      var bullet = bullets[index]      drawEobject(bullet)    }    // 绘制文本    if (option.isDebug) {      var arr = statInfo.getDebugArray()      for (var index = 0;index < arr.length;index++) {        tempContext.strokeText(arr[index], 10, 10 * (index + 1))      }    }    // head    context.drawImage(option.resources.head, -5, 0, option.ctxWidth + 10, headOffset)    // hp    for (var index = 0;index < player.Hp;index++) {      var width = (option.resources.hp.width + 5) * index + 5      context.drawImage(option.resources.hp, width, 0, 20, headOffset)    }    // scene    context.drawImage(canvas, // 绘制      0, 0, canvas.width, canvas.height,      0, headOffset, option.ctxWidth, option.ctxHeight - headOffset)  }

对象碰撞检测模块:

检查玩家和敌军,玩家和子弹,敌军和子弹之间的碰撞,减hp,生成爆炸效果等等。

// 检测碰撞  var checkCollection = function () {    var plainRect = {      x: player.position.x,      y: player.position.y,      width: player.width,      height: player.height    }    for (var i = enemies.length - 1;i > -1;i--) {      var enemy = enemies[i]      if (enemy.isDie) continue      var enemyRect = {        x: enemy.position.x,        y: enemy.position.y,        width: enemy.width,        height: enemy.height      }      // 检查子弹和飞机的碰撞      for (var j = shots.length - 1;j > -1;j--) {        var oneShot = shots[j]        if (oneShot.isDie) continue        if (player.Oid == oneShot.belong && Util.inArea({x: oneShot.position.x + oneShot.width / 2,y: oneShot.position.y}, enemyRect)) {          enemy.Hp--          oneShot.Hp--          if (enemy.Hp <= 0) {            statInfo.kill[enemy.type]++            enemy.isDie = true            var bullet = new Bullet()            bullet.isDie = false            bullet.icon = option.resources.bullet            bullet.width = 8            bullet.height = 8            bullet.position.x = oneShot.position.x + oneShot.width / 2            bullet.position.y = oneShot.position.y            bullets.push(bullet)            setTimeout((function (enemy, bullet) {              return function () {                Util.removeArr(enemies, enemy)                Util.removeArr(bullets, bullet)              }            })(enemy, bullet), 500)          }          // 子弹生命  穿甲弹          if (oneShot.Hp <= 0) {            oneShot.isDie = true            setTimeout((function (shot) {              return function () {                Util.removeArr(shots, shot)              }            })(oneShot), 500)          }        }      }      // 检查玩家和飞机的碰撞      if (Util.isChonghe(plainRect, enemyRect)) {        enemy.Hp--        player.Hp--        if (enemy.Hp <= 0) {          enemy.isDie = true          setTimeout(function () {            enemies = enemies.slice(0, i).concat(enemies.slice(i + 1, enemies.length))          }, 100)        }      }    }    // 检查玩家是否被击中    for (var j = shots.length - 1;j > -1;j--) {      var oneShot = shots[j]      if (oneShot.isDie) continue      if (player.Oid != oneShot.belong && Util.inArea({x: oneShot.position.x + oneShot.width / 2,y: oneShot.position.y}, plainRect)) {        player.Hp--        oneShot.Hp--        if (oneShot.Hp <= 0) {          oneShot.isDie = true          setTimeout((function (shot) {            return function () {              Util.removeArr(shots, shot)            }          })(oneShot), 500)        }      }    }    if (player.Hp <= 0) {      isRunning = false    }  }

对象运动模块:

控制子弹发射,位置,敌军生成,位置。

//对象移动  var objectMove = function () {    // 生成新的个体    if (player.isShot) {      var shot = Util.createShot(player, 0)      shots.push(shot)      player.isShot = false      statInfo.emitShot[shot.type]++    }    if (plainMoveState.isMouseDown) {      player.position = plainMoveState.position    }    if (Math.random() < 0.07) // 百分之七生成敌军    {      var rad = Math.random() * 3 + ''      statInfo.allEnemy++      Util.createEnemy(parseInt(rad.charAt(0)) + 2)    }    if (Math.random() < 0.01) // 百分之一生成强力敌军    {      statInfo.allEnemy++      Util.createEnemy(1)    }    for (var index in shots) {      if (shots[index].isDie) continue      var shot = shots[index]      shot.position.y -= shot.speedY    }    for (var index in enemies) {      if (enemies[index].isDie) continue      var enemy = enemies[index]      enemy.position.y += enemy.speedY      if (enemy.isShot) {        var shot = Util.createShot(enemy, 1)        shots.push(shot)        enemy.isShot = false        statInfo.emitShot[shot.type]++      }    }  }

对象清理模块:

清理一些飞出边界的子弹,敌军。

//对象清理  var clearObject = function (that) {    // 删除越界的对象      for (var i = shots.length - 1;i > -1;i--) {      var oneShot = shots[i]      if (!Util.inArea(oneShot.position, {x: -10,y: -10,width: option.ctxWidth + 10,height: option.ctxHeight + 10})) {        Util.removeArr(shots, oneShot)      }    }    for (var i = enemies.length - 1;i > -1;i--) {      var enemy = enemies[i]      if (enemy.isDie) {        Util.removeArr(enemies, enemy)        continue      }      if (!Util.inArea(enemy.position, {x: -100,y: -100,width: option.ctxWidth + 100,height: option.ctxHeight + 100})) {        Util.removeArr(enemies, enemy)      }    }  }

效果:

使用

var en = new Engine()      en.Create({        id: 'myCanvas',       // isAndroid: true,        resources: {          shot: shot,          bullet: bullet,          bg: bg,          hp: hp,          eshot: eshot,          plainImg: plain,          head: head,          enes: [ene1, ene2, ene3, ene4]        },        attachEvent: $scope      })      en.Start()

 

测试环境ionic,安卓

 

 

转载于:https://my.oschina.net/hunjixin/blog/979309

你可能感兴趣的文章
Android中加解密算法大全
查看>>
leetCode刷题(找出数组里的两项相加等于定值)
查看>>
python学习-python webdriver API(转载)
查看>>
array.fliter无法正确过滤出我想要的数组
查看>>
Spsite.OpenWeb()
查看>>
一文看懂大数据的技术生态Hadoop, hive,spark都有了[转]
查看>>
每天一道LeetCode--342. Power of Four
查看>>
浅谈C#浅拷贝和深拷贝
查看>>
windows8.1 windows defender service无法启动解决方案
查看>>
python+redis测试环境搭建
查看>>
并发web静态服务器的实现
查看>>
NameError: name 'pip' is not defined
查看>>
设备树学习之环境搭建
查看>>
局域网ping Linux主机名
查看>>
python
查看>>
sql server 表索引碎片处理
查看>>
centos6.4 ceph安装部署之ceph block device
查看>>
ssh -CT -o BatchMode=yes 用户名@主机名
查看>>
Qt 5.7 > Qt Applications
查看>>
Android 9.png图片的制作方法
查看>>