前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >原 ionic+js+html5 飞行射击

原 ionic+js+html5 飞行射击

作者头像
魂祭心
发布2018-05-17 17:36:49
1.5K0
发布2018-05-17 17:36:49
举报
文章被收录于专栏:魂祭心

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

代码地址:https://github.com/hunjixin/ShootGame

游戏对象设计

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

代码语言:javascript
复制
/**
 * 基类
 */
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事件隔离开,也方便了添加新的对象事件。

外部事件转内部事件:

代码语言:javascript
复制
  //移动事件
  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
  }

事件包装:

代码语言:javascript
复制
  //包装按键按下,抬起,移动事件
  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
  }

内部事件管理机制:

代码语言:javascript
复制
  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)
        }
      }
    }
  }

内部事件注册:

代码语言:javascript
复制
  //玩家开始移动
  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的触发间隔来控制系统的状态变化周期。由此带来的另一个好处是可以拉长不重要的模块触发间隔来节省资源(如对象清理,这个模块需要频繁的遍历,重建数组,慢)。

时间周期驱动

代码语言:javascript
复制
  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上绘制,最后再一次性绘制到主场景位置上。

代码语言:javascript
复制
/**
  * 绘图
  */
  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,生成爆炸效果等等。

代码语言:javascript
复制
 // 检测碰撞
  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
    }
  }

对象运动模块:

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

代码语言:javascript
复制
 //对象移动
  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]++
      }
    }
  }

对象清理模块:

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

代码语言:javascript
复制
  //对象清理
  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)
      }
    }
  }

效果:

使用

代码语言:javascript
复制
      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,安卓

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • js+html5写一个简单的飞行游戏引擎,游戏画面使用canvas绘图,引擎核心代码不到500行,原生js,没有依赖。
  • 游戏对象设计:
  • 事件设计:
    • 外部事件转内部事件:
      • 事件包装:
        • 内部事件管理机制:
          • 内部事件注册:
          • 引擎核心设计:绘图、碰撞检测、对象运动、对象清理
            • 时间周期驱动
              • 绘图模块:
                • 对象碰撞检测模块:
                  • 对象运动模块:
                    • 对象清理模块:
                    • 效果:
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档