微信射击小游戏实现/Phaser引擎

最近在家闲着无事利用Phaser引擎是开发了一款弓箭射击小游戏。废话少说先看效果:

微信射击小游戏实现/Phaser引擎_第1张图片

 

在线体验:

微信射击小游戏实现/Phaser引擎_第2张图片

1、游戏的主要逻辑实现:

GameState.js:

export default class GameState extends Phaser.State {

  init() {
    this.eagle = null; // 雄鹰,每1000分出现一次
    this.birds = []; // 飞鸟集合
    this.score = 0; // 得分
    this.arrows = []; // arrow集合
    this.scoreText = null; // 得分
    this.bowArrow = null; // 底部弓箭
    this.backButton = null; // 返回按钮

    // 计算arrow飞行速度
    this.arrowSpeed = windowHeight / 1600;

    this.createBirdsIntervalId = 0;
    this.recoverBirdsIntervalId = 0;
    this.durationChangeIntervalId = 0;
    this.collisionDetecIntervalId = 0;
  },
  preload(){
  },
  create(){
  },
  render(){},
  update(){}
}

2、在preload中添加背景图片、返回按钮和弓箭图片:

preload() {
    let that = this;
    let game = this.game;

    // 创建背景图图片
    let gameBgImg = game.add.image(0, 0, "gamebg1");
    gameBgImg.width = windowWidth;
    gameBgImg.height = windowHeight;

    // 弓箭图片
    var bowArrow = game.add.image(windowWidth / 2, windowHeight - 80, "bowarrow");
    this.bowArrow = bowArrow;
    bowArrow.anchor.set(0.5);
    bowArrow.scale.set(0.38);
    bowArrow.inputEnabled = true;

    var backImg = game.add.image(25, 25, "back");
    backImg.anchor.set(0.5);
    backImg.scale.set(0.8);
    this.backButton = backImg;
    // 总分数
    var scoreText = game.add.text(windowWidth - 140, 25, "得分:0", {
      font: "20px",
      fill: "#f7db04",
      align: "center",
      stroke: "#f8abc5",
      strokeThickness: 0.5
    })
    this.scoreText = scoreText;
    this.scoreText.anchor.set(0,0.5)
    // 将得分和返回按钮置顶
    setInterval(() => {
      backImg.bringToTop();
      scoreText.bringToTop();
    }, 1);
}

3、通过setInterval向屏幕中添加飞鸟(这里的所有动画都未使用物理引擎,还待进一步研究,下面所有代码同理):

this.createBirdsIntervalId = setInterval(() => {
      // 添加飞鸟
      for (var i = 0; i < MAX_BIRD_NO - this.birds.length; i++) {
        var birdKind = BIRD_KINDS[Math.floor(Math.random() * MAX_BIRD_NO)];
        var kindName = birdKind["name"];
        var from = birdKind["from"];
        var scale = birdKind["scale"];
        var price = birdKind["price"];

        var bird = null;
        var duration = DURATIONS[Math.floor(Math.random() * 3)] + Math.random() * 2000;
        // 从左到右
        if (from == "left") {
          bird = this.game.add.sprite(-80 - Math.random() * windowWidth, 40 + Math.ceil(Math.random() * windowHeight * 0.5), kindName);
          bird.animations.add('fly', null, 8, true);
          bird.animations.play('fly', 8, true, true);
          this.game.add.tween(bird).to({
            x: windowWidth + 80
          }, duration, "Linear", true, 0, -1, false);
        } else { // 从右到左
          bird = this.game.add.sprite(windowWidth * (1 + Math.random()) + 80, 40 + Math.ceil(Math.random() * windowHeight * 0.45), kindName);
          bird.animations.add('fly', null, 8, true);
          bird.animations.play('fly', 8, true, true);
          this.game.add.tween(bird).to({
            x: -80
          }, duration, "Linear", true, 0, -1, false);
        }
        bird.anchor.set(0.5);
        bird.scale.set(scale);
        bird.zoom = scale;
        bird.name = kindName;
        bird.price = price;
        bird.from = from;
        bird.diedPic = birdKind["diedPic"];

        this.birds.push(bird);
      }
    }, 0);

4、飞鸟飞出屏幕回收飞鸟对象:

this.recoverBirdsIntervalId = setInterval(() => {
      // 如果飞鸟飞出屏幕销毁飞鸟对象
      for (var i = 0; i < this.birds.length; i++) {
        var bird = this.birds[i];
        if (this.birdHasFlyAway(bird)) {
          this.birds.splice(i, 1)
          i--;
          bird.destroy();
        }
      }
    }, 0);

5、碰撞检测:

this.collisionDetecIntervalId = setInterval(() => {
      // 检测碰撞
      for (var i = 0; i < this.arrows.length; i++) {
        var arrow = this.arrows[i];

        // 如果箭矢超出屏幕,销毁
        if (arrow.x - windowWidth - arrow.height / 4 > 0 ||
          arrow.x < -arrow.height / 2 + 10 ||
          arrow.y < -arrow.height / 2 + 10) {
          this.arrows.splice(i, 1);
          i--;
          arrow.destroy();
          continue;
        }

        if (this.eagle && this.collisionDetec(this.eagle, arrow)) {
          this.eagle.shoted++;
          this.arrows.splice(i, 1);
          i--;
          
          arrow.destroy();
          if (this.eagle.shoted == 1) { // 雄鹰第一次被射中开始发起攻击
            utils.playAudio("assets/audio/eagle.ogg");
            this.attack();
          } else if (this.eagle.shoted == 3) { // 如果被射中三次雄鹰被击杀
            this.eagle.diedPic = this.eagle.x < windowWidth / 2 ? "eagle003" : "eagleback003";
            this.eagle.name = this.eagle.x < windowWidth / 2 ? "eagle" : "eagleback";
            this.eagle.zoom = EAGLE_SCALE;
            utils.playAudio("assets/audio/eagledied.ogg");
            this.createDiedStatus(this.eagle, 100, 5);
            this.eagle.destroy();
            this.eagle = null;

            this.createShowComboText("击杀雄鹰 +120");
            this.changeScoreText(EAGLE_SCORE);
          }
          continue;
        }

        for (var j = 0; j < this.birds.length; j++) {
          var b = this.birds[j]
          if (this.collisionDetec(b, arrow)) {
            arrow.kiledBirdsNo++;
            this.birds.splice(j, 1);
            j--;
            b.destroy();
            this.killBird(b);
            // b.animations.stop();
            if (b.name == "sgbird") {
              this.arrows.splice(i, 1);
              i--;
              
              arrow.destroy();
              break;
            }
          }
        }
      }
    }, 0);

6、响应gameState关闭事件:将所有interval删除并解绑touch事件(从第2段代码到本段代码聚在preload中):

this.state.onShutDownCallback = function () {
      clearInterval(that.createBirdsIntervalId);
      clearInterval(that.recoverBirdsIntervalId);
      clearInterval(that.durationChangeIntervalId);
      clearInterval(that.collisionDetecIntervalId);

      that.offTouchEvents();
    }

7、绑定touchStart,touchMove,touchEnd事件:

create() {
    let game = this.game;
    let that = this;
    /**
     * 计算弓箭偏移角度
     */
    function calAngle(x, y) {
      if (y >= 0) {
        if (x > 0) {
          return 90;
        }
        if (x < 0) {
          return -90;
        }
      }
      var z = Math.sqrt(x * x + y * y);
      return Math.asin(x / z) * 180 / Math.PI;
    }

    wx.onTouchStart(touchStartCallback = (event) => {
      // 如果按下返回按钮返回到导航页面
      var x = event.changedTouches[0].clientX;
      var y = event.changedTouches[0].clientY;
      if (x >= that.backButton.x - 20 && x <= that.backButton.x + 20 &&
        y >= that.backButton.y - 20 && y <= that.backButton.y + 20) {
        if (!that.gameIsOver()) {
          wx.showModal({
            title: "游戏提醒",
            content: "游戏尚未结束是否退出?",
            cancelColor: 'red',
            success: function (res) {
              if (res.confirm) {
                that.updateRanks();
                game.state.start("NavState");
                that.shutdown();
              }
            }
          })
        } else {
          game.state.start("NavState");
          that.shutdown();
        }
        return;
      }

      that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);
    });

    wx.onTouchMove(touchMoveCallback = (event) => {
      var x = event.changedTouches[0].clientX;
      var y = event.changedTouches[0].clientY;
      that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);
    });

    wx.onTouchEnd(touchEndCallback = (event) => {
      var x = event.changedTouches[0].clientX;
      var y = event.changedTouches[0].clientY;

      for (var i = 0; i < that.arrows.length; i++) {
        var arrow = that.arrows[i];
        // 如果箭矢超出2.2秒未销毁,销毁
        if(new Date().getTime() - arrow.shootTime >= 2200){
          that.arrows.splice(i,1);
          i--;
          arrow.destroy();
        }
      }
      that.bowArrow.angle = calAngle(x - that.bowArrow.x, y - that.bowArrow.y);

      // arrow在同一时刻只能有两个
      if (that.arrows.length >= 2) return;
      if (!canShoot) return;
      canShoot = false;
      setTimeout(()=>{
        canShoot = true;
      },SHOOT_DURATION);
      utils.playAudio("assets/audio/shoot.ogg");

      // 发射箭矢
      var arrow = game.add.image(windowWidth / 2, windowHeight - 80, "arrow");
      arrow.shootTime = new Date().getTime();
      arrow.anchor.set(0.5);
      arrow.scale.set(0.38);
      arrow.angle = that.bowArrow.angle;
      // 计算箭矢终点坐标(toX,toY)
      var rightAngle = calAngle(windowWidth - that.bowArrow.x, -that.bowArrow.y);
      var leftAngle = -rightAngle;
      var toX = -arrow.height / 2;
      var toY = -arrow.height / 2;

      if (arrow.angle >= leftAngle && arrow.angle <= rightAngle) {
        toX = Math.tan(-arrow.angle * Math.PI / 180) * (toY - arrow.y) + arrow.x;
      } else if (arrow.angle > rightAngle) {
        toX = windowWidth - toX;
      }
      toY = (toX - arrow.x) / Math.tan(-arrow.angle * Math.PI / 180) + arrow.y;
      var $z = Math.sqrt(Math.pow(toX - arrow.x, 2) + Math.pow(toY - arrow.y, 2));

      game.add.tween(arrow).to({
        x: toX,
        y: toY
      }, $z / that.arrowSpeed, "Linear", true, 0, -1, false);
      
      arrow.kiledBirdsNo = 0;

      arrow.events.onDestroy.add(() => {
        
        var text = null;
        var score = TWO_BIRDS_SCORE;
        if (arrow.kiledBirdsNo == 2) {
          // 一箭双雕
          text = "一箭双雕 +" + TWO_BIRDS_SCORE;
        }
        if (arrow.kiledBirdsNo == 3) {
          // 一石三鸟
          text = "一石三鸟 +" + THREE_BIRDS_SCORE;
          score = THREE_BIRDS_SCORE;
        }

        if (text) {
          utils.playAudio("assets/audio/combo.ogg");
          this.createShowComboText(text);
          this.changeScoreText(score);
        }
      });

      that.arrows.push(arrow);
    })
  }

8、创建雄鹰对象并判断游戏是否结束:

  update() {
    let that = this;

    // 每增加500分出现一次雄鹰,同时一个屏幕中只能出现一个
    if (this.score >= 500 && this.score % 500 < EAGLE_SCORE && !this.eagle) {
      var eagleFrom = ["right", "left"][Math.floor(Math.random() * 2)];
      var x = -80 - Math.random() * windowWidth,
        y = 40 + Math.ceil(Math.random() * windowHeight * 0.45);
      var toX = windowWidth + 80,
        toY = 40 + Math.ceil(Math.random() * windowHeight * 0.45);

      if (eagleFrom == "right") {
        x = windowWidth * (1 + Math.random()) + 80;
        toX = -80;
      }

      this.createNewEagle(eagleFrom, x, y, toX, toY);

    } else if (this.eagle) { // 雄鹰一直存在于屏幕中除非被击杀
      if ((this.eagle.from == "right" && this.eagle.x <= 20) ||
        (this.eagle.from == "left" && this.eagle.x >= windowWidth - 20)) {
        var from = this.eagle.from == "left" ? "right" : "left";
        var x = this.eagle.x,
          y = this.eagle.y;
        var toX = windowWidth - 20,
          toY = toY = 40 + Math.ceil(Math.random() * windowHeight * 0.45);;
        if (from == "right") {
          toX = 20;
        }
        this.createNewEagle(from, x, y, toX, toY);
      }
    }

    if (this.gameIsOver()) {
      utils.playAudio("assets/audio/gameover.ogg");
      var extText = this.updateRanks();

      this.eagle.destroy();
      this.eagle = null;
      // console.log("gameOver");
      wx.showModal({
        title: "游戏提示",
        content: "Game Over\r\n" + that.scoreText.text + extText,
        cancelColor: 'red',
        confirmText: "新游戏",
        cancelText: "退出",
        confirmColor: "blue",
        success: (res) => {
          that.offTouchEvents();
          if (res.confirm) {
            that.state.restart("GameState");
          } else {
            that.state.start("NavState");
          }
        }
      });
    }
  }

9、相关辅助函数

createNewEagle(from, x, y, toX, toY) {
    var duration = DURATIONS[Math.floor(Math.random() * 3)] / 2.5 + Math.random() * 2000;
    var tempEagle = this.eagle;
    this.eagle = this.game.add.sprite(x, y, from == "left" ? "eagle" : "eagleback");
    this.eagle.animations.add('fly', null, 8, true);
    this.eagle.animations.play('fly', 8, true, true);
    this.eagle.anchor.set(0.5);
    this.eagle.scale.set(EAGLE_SCALE);
    this.eagle.from = from;

    this.game.add.tween(this.eagle).to({
      x: toX,
      y: toY
    }, duration, "Linear", true, 0, -1, false);

    if (tempEagle) {
      this.eagle.shoted = tempEagle.shoted;
      tempEagle.destroy();
    } else {
      this.eagle.shoted = 0;
    }
  }

  render() {

  }

  // 碰撞检测
  collisionDetec(bird, arrow) {
    // 箭矢消失,取消碰撞检测
    if (!arrow) return false;
    var birdStartPointer = {};
    var birdEndPointer = {};

    birdStartPointer.x = bird.x - bird.width / 2 + 5;
    birdStartPointer.y = bird.y - 8;

    birdEndPointer.x = bird.x + bird.width / 2 - 5;
    birdEndPointer.y = bird.y + 8;

    var arrowHalfHighet = arrow.height / 2 - 3;

    var offsetX = arrowHalfHighet * Math.sin(arrow.angle * Math.PI / 180);
    var offsetY = arrowHalfHighet * Math.cos(arrow.angle * Math.PI / 180);

    var arrowHeadPointer = {};
    arrowHeadPointer.x = arrow.x + offsetX;
    arrowHeadPointer.y = arrow.y - offsetY;

    // 如果箭头超出屏幕范围,取消碰撞检测
    if (arrowHeadPointer.x < 0 || arrowHeadPointer.x > windowWidth ||
      arrowHeadPointer.y > windowHeight - 80 ||
      arrowHeadPointer.y < 0) {
      return false;
    }
    if (arrowHeadPointer.x >= birdStartPointer.x && arrowHeadPointer.x <= birdEndPointer.x &&
      arrowHeadPointer.y >= birdStartPointer.y && arrowHeadPointer.y <= birdEndPointer.y) {
      utils.playAudio("assets/audio/hited.ogg");
      return true;
    } else {
      return false;
    }
  }

  attack() {
    var eagleInLeft = windowWidth / 2 > this.eagle.x;
    var targetX = this.bowArrow.x,
      targetY = this.bowArrow.y; // 攻击点为底部弓箭中点坐标

    // 从高处发起攻击,默认在左侧被射中
    var prepareToX = 20 + (windowWidth / 2 - 20) * Math.random(),
      prepareToY = this.eagle.y * Math.random();

    var name = "eaglepreattack",
      attackPic = "eagle005";

    // 如果在右侧被射中
    if (!eagleInLeft) {
      prepareToX = windowWidth / 2 + (windowWidth / 2 - 20) * Math.random();
      name = "eaglebackpreattack";
      attackPic = "eagleback005"
    }

    var te = this.eagle;
    this.eagle = this.game.add.sprite(this.eagle.x, this.eagle.y, name);
    this.eagle.anchor.set(0.5);
    this.eagle.scale.set(EAGLE_SCALE);
    this.eagle.animations.add('fly', null, 8, true);
    this.eagle.animations.play('fly', 8, true, true);
    this.eagle.shoted = te.shoted;
    this.eagle.from = te.from;
    te.destroy();

    var offsetX = (prepareToX - this.eagle.x) / 100;
    var offsetY = (prepareToY - this.eagle.y) / 100;

    var times = 0;
    let that = this;
    this.eagle.intervalId = setInterval(() => {
      times++;
      that.eagle.x += offsetX;
      that.eagle.y += offsetY;
      if (times > 100) {
        var tempEagle = that.eagle;
        that.eagle = that.game.add.sprite(that.eagle.x, that.eagle.y, name, attackPic);
        this.eagle.anchor.set(0.5);
        this.eagle.scale.set(EAGLE_SCALE);
        that.eagle.shoted = tempEagle.shoted;
        that.eagle.intervalId = tempEagle.intervalId;
        that.eagle.from = tempEagle.from;
        that.game.add.tween(that.eagle).to({
          x: targetX,
          y: targetY
        }, EAGLE_DURATIONS, "Linear", true, 0, -1, false);
        tempEagle.destroy();
        clearInterval(that.eagle.intervalId);
      }
    }, 5);
  }

  createDiedStatus(bird, duration, times) {
    // 创建小鸟死亡图像
    let that = this;
    var diedImg = that.game.add.sprite(bird.x, bird.y, bird.name, bird.diedPic);
    diedImg.anchor.set(0.5);
    diedImg.scale.set(bird.zoom);

    // 尸体闪烁
    var tintChangedTimes = 0;
    diedImg.intervalId = setInterval(() => {
      // console.log("titmer...");
      tintChangedTimes++;
      diedImg.tint = Math.random() * 0xffffff;
      if (tintChangedTimes > times) {
        clearInterval(diedImg.intervalId);
        diedImg.destroy();
      }
    }, duration);
  }

  // 判断飞鸟是否飞出屏幕
  birdHasFlyAway(bird) {
    if (bird.from == "right") {
      if (bird.x + 80 <= 1) {
        return true;
      }
    } else {
      if (windowWidth + 80 - bird.x <= 1) {
        return true;
      }
    }
    return false;
  }

  changeScoreText(price) {
    this.score = parseInt(this.scoreText.text.replace("得分:", "")) + price;
    this.scoreText.text = "得分:" + JSON.stringify(this.score);
  }

  gameIsOver() {
    if (!this.eagle) return false;
    var x = this.eagle.x,
      y = this.eagle.y + 20;
    var bowArrowStartPointer = {
      x: this.bowArrow.x - this.bowArrow.width / 2,
      y: this.bowArrow.y - 10
    };
    var bowarrowEndPointer = {
      x: this.bowArrow.x + this.bowArrow.width / 2,
      y: this.bowArrow.y + 10
    };

    return x >= bowArrowStartPointer.x && x <= bowarrowEndPointer.x &&
      y >= bowArrowStartPointer.y && y <= bowarrowEndPointer.y;
  }

  killBird(bird) {
    let that = this;
    // 如果小鸟超出屏幕范围不计分
    if (bird.x < -bird.width / 2 || bird.x > windowWidth + bird.width / 2) {
      return;
    }
    // console.log("bird " + bird.name + " was killed.....");
    // console.log("bird width/2:" + bird.width / 2);
    // 创建加分text
    var priceText = that.game.add.text(bird.x, bird.y - 10, "+  " + bird.price, {
      font: "20px",
      fill: "#00FF00",
      align: "center",
      stroke: "#d4237a",
      strokeThickness: 1
    });
    // 250毫秒内消失,击杀分数
    priceText.anchor.set(0.5);
    var priceTextChangedTimes = 0;
    priceText.intervalId = setInterval(() => {
      priceText.y -= 2;
      if (priceTextChangedTimes > 10) {
        clearInterval(priceText.intervalId)
        priceText.destroy();
      }
      priceTextChangedTimes++;
    }, 50);

    this.createDiedStatus(bird, 100, 5);
    this.changeScoreText(bird.price);
  }

  createShowComboText(text) {
    var showTextChangedTimes = 0;
    var showText = this.game.add.text(windowWidth / 2, windowHeight / 2, text, {
      font: "22px",
      fill: "#f00b59",
      align: "center",
      stroke: "#f8abc5",
      strokeThickness: 0.5
    });

    showText.anchor.set(0.5);
    showText.intervalId = setInterval(() => {
      showText.y -= 1.5;
      if (showTextChangedTimes > 35) {
        clearInterval(showText.intervalId);
        showText.destroy();
      }
      showTextChangedTimes++;
    }, 25);
  }

  updateRanks() {
    if (!this.score) return 0;
    var ranks = wx.getStorageSync('ranks');
    var len = ranks.length;
    var i = 0;
    var item = {
      time: utils.formatTime(new Date()),
      score: this.score
    };
    while (i < len && ranks[i].score > this.score) {
      i++;
    }

    if (i == len && len < 10) {
      ranks.push(item);
    } else {
      ranks.splice(i, 0, item);
    }

    if (ranks.length > 10) ranks.splice(10, ranks.length - 10);

    wx.setStorageSync('ranks', ranks);

    return i == 0 ? "\r\n新的最高分:" + this.score : "";
  }

  offTouchEvents() {
    wx.offTouchStart(touchStartCallback);
    wx.offTouchMove(touchMoveCallback);
    wx.offTouchEnd(touchEndCallback);
  }

至此小游戏主要逻辑就万成了。喜欢的话可以扫码体验一下哦。

你可能感兴趣的:(前端,javascript,微信小程序)