pixi 平铺精灵 demo (二)

引言

上篇主要讲述了 pixi 平铺精灵的创建与使用及视差滚动,这篇主要讲述的是基于上篇 demo 的优化与新增的功能。


目录

  1. 新增功能

    1.1 分数&&速度

    1.2 上方障碍

    1.3 碰撞检测

    1.4 二段跳

    1.5 血量

    1.6 配乐

  2. setup代码

  3. 总结

  4. 了解更多


1、新增功能

1.1 分数&&速度

分数这里是偷了个小懒,这里是直接在 ticker 中做了个累加 1 的操作,想的是通过分数达到多少分,游戏整体速度会提升一个阶段,当然也可以是避开障碍物获得分数,后续如果有金币之类的可以做进一步的优化。

app.ticker.add(() => {
  // 累加得分
  $('.score-ctn').text(`分数:${score += 1}`);
  
  // 提升速度
  score > 10000 && score < 20000 && (speed = 11);
  score > 20000 && score < 30000 && (speed = 12);
  score > 30000 && score < 40000 && (speed = 13);
  score > 40000 && (speed = 14);

  // 无限滚动
  bgSpr.tilePosition.x -= 1;
  bgSpr.tilePosition.x %= PIXI.Loader.shared.resources['bg2_2'].texture.width;
  
  // 碰撞
  bump(role, monster) && (roleSmoothie.update = inverted.bind(this, 3));
});


1.2 上方障碍

上方障碍跟怪物是一样的,只不过位置不同,在做碰撞检测的时候改变一下碰撞的反馈。setup() 新增代码:

let column = new PIXI.Sprite(PIXI.Loader.shared.resources['column'].texture);

let columnSmoothie = null; // 上方障碍移动

column.position.set(7700, 0);

app.stage.addChild(column);

// 前景移动
columnSmoothie = new Smoothie({
  engine: PIXI,
  renderer: app.renderer,
  root: app.stage,
  update: translate.bind(this, column, speed)
});
columnSmoothie.start();


1.3 碰撞检测

碰撞检测这里与碰撞到陆地障碍物反馈不同,当碰撞到陆地障碍是直接扣除一点生命值,在碰撞到上方障碍物则不会直接扣除生命值,而是会随着上方障碍物的移动把人物推出屏幕才会扣除生命值,这里与碰撞到陆地障碍不同的是上方障碍物不能直接穿过,后者可以穿过,但会直接扣除生命值。

这里的碰撞检测思路也比较简单,陆地障碍物只要人物的 x,y 轴位置与障碍物的 x,y 轴相交则返回 true 相交的点需要注意的是现在精灵的中心点在正下方在做碰撞检测点时候需要加上精灵的 width / 2 , 在做跳跃碰撞检测的时候障碍物的 y 值则需要减去高度。 如果 x 轴减去障碍物 x 轴 大于等于 障碍物负的宽度的一半并且小于障碍物宽度的一半并且 y 值减去障碍物的 y 值减去障碍物的高度大于等于 20 则返回 true 。

由于精灵贴图都是矩形的,在跳跃的过程中可能会碰到贴图多余的直角出现误判,这里直接粗暴的减去了 20 按理来说大于等于 0 就算触碰了。也可以做圆形碰撞检测可以避免直角误判。

// 陆地障碍
function bottombump (spr1, spr2) {
  let spr1X = spr1.x + spr1.width / 2;
  let spr2Y = spr2.y - spr2.height;

  return spr1X - spr2.x <= spr2.width / 2 && spr1X - spr2.x >= -spr2.width / 2 && spr1.y -                   spr2Y >= 20? true: false;
}

// 上方障碍
function topbump (spr1, spr2) {
  return spr1.x - spr2.x < 30 && spr1.x - spr2.x > -30 && (spr1.y - spr1.height) - (spr2.y +                    spr2.height) <= 0? true: false;
}

效果预览:

pixikp_1


1.4 二段跳

二段跳是在一段跳的起跳完成且下落帧前再次按跳跃键触发,其余期间无法触发,跳跃动画配置在最上方定义好了

一段跳思路是预先定义好一个动画帧下标从 0 开始,起跳完成至最高点是 4 帧图片。判断下标小于 4 并且起跳的高度不超过一定值情况下下标累加,人物 y 轴递减 在下标大于 4 时做 y 值递增下落,在最后一帧时将下标复原至 0 切换奔跑状态。

二段跳需要两个变量做判断,一个用于记录点击跳跃次数,一个用于判断是否二段跳,连续两次跳跃人物刚好在一段跳下落前,这里设定第二段跳的高度要比一段跳的高度低,在完成二段跳动作之后人物处于至高点,然后接入一段跳的第 5 帧动画继续做一段跳的下落动作。

let roleSprJumpIndex = 0; // 一段跳动画下标

// 跳跃
$('.jump').on('touchstart', (e) => {
  if (jumpNum < 2) { // 禁止多次跳跃
    if (!isJump) {
      roleSmoothie.update = jump.bind(this); // 一段跳
      isJump = true;
    } else {
      roleSmoothie.update = jump2.bind(this); // 二段跳
      isJump = false;
    }
  }
  jumpNum++;
})
// 一段跳
function jump () {
  role.texture = PIXI.Loader.shared.resources[config.jump[roleSprJumpIndex]].texture;
  // 起跳
  if (roleSprJumpIndex <= 4 && role.position.y > 285) {
    roleSprJumpIndex++;
    role.position.y -= 40;
    isAction = false;
  }
  // 下落
  if (roleSprJumpIndex > 4 ) {
    if (role.position.y < 465) {
      role.position.y += 35;
    }
    roleSprJumpIndex++;
    isAction = true;
  }
  // 落地
  if (roleSprJumpIndex > 8) {
    roleSprJumpIndex = 0;
    role.position.y = 465;
    isAction = true;
    jumpNum = 0; // 重置跳跃次数
    isJump = false;
    roleSmoothie.update = go.bind(this); // 奔跑状态
  }
}

// 二段跳
function jump2(){
  role.texture = PIXI.Loader.shared.resources[config.jump2[roleSprJumpIndex2]].texture;
  if (roleSprJumpIndex2 < 6) {
    roleSprJumpIndex2++;
    role.position.y -= 15;
  } else {
    roleSprJumpIndex = 5;
    roleSprJumpIndex2 = 0;
    roleSmoothie.update = jump.bind(this);
  }
}

效果图:


屏幕录制2021-04-01下午10 (2)


1.5 血量

血量在每次碰撞是扣除一点,ticker 碰撞反馈新增代码:

这里在被上方障碍物推出屏幕之后会被扣除一点血,人物直接复原到原来的位置,如果在推出屏幕前下滑躲过上方障碍物则人物 x 轴慢慢递增到原来位置。

// 陆地障碍物反馈
if (isBottomBump || isBottomBump2) {
  role.tint = 0xFFFF660;
  if (isBlood && bloodNum >= 0) {
    bloodArr[bloodNum].style.display = 'none';
    bloodNum--;
    isBlood = false;
  }
} else {
  role.tint = 0xFFFFFF;
  isBlood = true;
}

// 上方障碍物反馈
if (topBupm) {
  role.x = column.x - 30;
  if (role.x < -100) {
    bloodArr[bloodNum].style.display = 'none';
    role.x = 400;
    bloodNum--;
  }
}
// 递增到原来位置
if (role.x < 400) {
  role.x = role.x + 1;
}

效果图:

屏幕录制2021-04-01下午9


1.6 配乐

网上找了两个音效,一个背景音乐,一个按键音效,加上音效让能体验不那么平淡。

这里是用了一个 audio 的插件 howler.js 使用非常简单:

// 背景音乐
let bgBgm = new Howl({
  src: './demo_bg.mp3',
  loop: true // 是否循环
});
bgBgm.play(); // 播放

// 按钮音效
let btnBgm = new Howl({
  src: './demo2.mp3',
  loop: false
});

在按钮事件触发播放即可。


2、setup 代码

function setup () {
  // 背景
  let bgSpr = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['bg'].texture, app.renderer.width, app.renderer.height);
  // 前景
  let bridge = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['bridge'].texture, 1600, 437);
  // 人物
  let role = new PIXI.Sprite(PIXI.Loader.shared.resources['role'].texture);
  // 怪物
  let monster = new PIXI.Sprite(PIXI.Loader.shared.resources['monster'].texture);
  let monster2 = new PIXI.Sprite(PIXI.Loader.shared.resources['monster2'].texture);
  // 上方障碍物
  let column = new PIXI.Sprite(PIXI.Loader.shared.resources['column'].texture);

  let roleSmoothie = null; // 人物动画
  let monsterSmoothie = null; // 怪物动画
  let monster2Smoothie = null;
  let bridgeSmoothie = null; // 前景动画
  let columnSmoothie = null; // 上方障碍物动画

  let roleSprGoIndex = 0; // 走路动作图片下标
  let roleSprRunIndex = 0; // 跑动作图片下标
  let roleSprJumpIndex = 0; // 跳动作图片下标
  let roleSprJumpIndex2 = 0;
  let roleSprInverIndex = 0; // 倒动作图片下标

  let speed = 10; // 滚动速度
  
  let jumpNum = 0; // 跳跃次数
  let isJump = false; // 跳跃状态
  
  let isBlood = true; 
  let bloodNum = 2; // 血量下标
  let bloodArr = $('.blood-ctn').children(); // 血量数组
  
  let score = 0; // 分数

  let isAction = true; // 动作状态
  
  // 背景音乐
  let bgBgm = new Howl({
    src: './demo_bg.mp3',
    loop: true // 是否循环
  });
  bgBgm.play(); // 播放

  // 按钮音效
  let btnBgm = new Howl({
    src: './demo2.mp3',
    loop: false
  });
  
  // 中心点
  role.anchor.set(0.5, 1);
  monster.anchor.set(0.5, 1);
  monster2.anchor.set(0.5, 1);

  // 缩放比例
  role.scale.set(1.5, 1.5);
  monster2.scale.set(0.9, 0.9);

  // 位置
  role.position.set(400, 465);
  monster.position.set(2000, 465);
  monster2.position.set(3200, 465);
  bridge.position.set(0, 440);
  column.position.set(7700, 0);

  // 添加到舞台
  app.stage.addChild(bgSpr, bridge, role, monster, monster2, column);
  
  // 平移
  function translate (spr, num) {
    spr.tilePosition.x -= num;
    spr.tilePosition.x %= PIXI.Loader.shared.resources['prospect'].texture.width;
  };

  // 怪物移动
  function monsterTranslate (spr, num, x) {
    spr.position.x -= num;
    spr.position.x < -x && (spr.position.x = 1600);
  };
 
  // 跑
  function go () {
    role.texture = PIXI.Loader.shared.resources[config.go[roleSprGoIndex]].texture;
    roleSprGoIndex < 6? roleSprGoIndex++ : roleSprGoIndex = 0;
  };

  // 跳
  function jump () {
    role.texture = PIXI.Loader.shared.resources[config.jump[roleSprJumpIndex]].texture;
    if (roleSprJumpIndex <= 4 && role.position.y > 285) {
      roleSprJumpIndex++;
      role.position.y -= 40;
      isAction = false;
    }
    if (roleSprJumpIndex > 4 ) {
      if (role.position.y < 465) {
        role.position.y += 35;
      }
      roleSprJumpIndex++;
      isAction = true;
    }
    if (roleSprJumpIndex > 8) {
      roleSprJumpIndex = 0;
      role.position.y = 465;
      isAction = true;
      jumpNum = 0;
      isJump = false;
      roleSmoothie.update = go.bind(this);
    }
  }

  // 二段跳
  function jump2(){
    role.texture = PIXI.Loader.shared.resources[config.jump2[roleSprJumpIndex2]].texture;
    if (roleSprJumpIndex2 < 6) {
      roleSprJumpIndex2++;
      role.position.y -= 15;
    } else {
      roleSprJumpIndex = 5;
      roleSprJumpIndex2 = 0;
      roleSmoothie.update = jump.bind(this);
    }
  }

  // 滑
  function slip (num) {
    role.texture = PIXI.Loader.shared.resources['sprite4_0'].texture;
  }
  // 人物移动
  roleSmoothie = new Smoothie({
    engine: PIXI,
    renderer: app.renderer,
    root: app.stage,
    fps: 8,
    update: go.bind(this)
  });
  roleSmoothie.start();

  // 怪物移动
  monsterSmoothie = new Smoothie({
    engine: PIXI,
    renderer: app.renderer,
    root: app.stage,
    update: monsterTranslate.bind(this, monster, 7, 100)
  });
  monsterSmoothie.start();

  // 前景移动
  prospectSmoothie = new Smoothie({
    engine: PIXI,
    renderer: app.renderer,
    root: app.stage,
    update: translate.bind(this, prospectSpr, 3)
  });
  prospectSmoothie.start();
  
  // 柱子移动
  columnSmoothie = new Smoothie({
    engine: PIXI,
    renderer: app.renderer,
    root: app.stage,
    update: monsterTranslate.bind(this, column, speed)
  });
  columnSmoothie.start();

  // 跳跃事件
  $('.jump').on('touchstart', (e) => {
    if (jumpNum < 2) {
      if (!isJump) {
        roleSmoothie.update = jump.bind(this);
        isJump = true;
      } else {
        roleSmoothie.update = jump2.bind(this);
        isJump = false;
      }
    }
    jumpNum++;
    btnBgm.play();
  });
  
  // 下滑事件
  $('.slip').on('touchstart', (e) => {
    roleSmoothie.update = slip.bind(this);
    roleSprJumpIndex = 0;
    roleSprJumpIndex2 = 0;
    jumpNum = 0;
    isJump = false;
    
    btnBgm.play();
  });
  
  // 下滑结束
  $('.slip').on('touchend', (e) => {
    roleSmoothie.update = go.bind(this);
  });
  
  app.ticker.add(() => {
    $('.score-ctn').text(`分数:${score += 1}`);

    // 提升速度
    score > 10000 && score < 20000 && (speed = 11);
    score > 20000 && score < 30000 && (speed = 12);
    score > 30000 && score < 40000 && (speed = 13);
    score > 40000 && (speed = 14);

    // 无限滚动
    bgSpr.tilePosition.x -= 1;
    bgSpr.tilePosition.x %= PIXI.Loader.shared.resources['bg2_2'].texture.width;

    isBottomBump = bottombump(role, monster);
    isBottomBump2 = bottombump(role, monster2);
    topBupm = topbump(role, column);

    // 怪物碰撞
    if (isBottomBump || isBottomBump2) {
      role.tint = 0xFFFF660;
      if (isBlood && bloodNum >= 0) {
        bloodArr[bloodNum].style.display = 'none';
        bloodNum--;
        isBlood = false;
      }
    } else {
      role.tint = 0xFFFFFF;
      isBlood = true;
    }

    // 柱子碰撞
    if (topBupm) {
      role.x = column.x - 30;
      if (role.x < -100) {
        bloodArr[bloodNum].style.display = 'none';
        role.x = 400;
        bloodNum--;
      }
    }

    if (role.x < 400) {
      role.x = role.x + 1;
    }
  })
  });
  
 // 怪物碰撞
  function bottombump (spr1, spr2) {
    let spr1X = spr1.x + spr1.width / 2;
    let spr2Y = spr2.y - spr2.height;

    return spr1X - spr2.x <= spr2.width / 2 && spr1X - spr2.x >= -spr2.width / 2 && spr1.y - spr2Y >= 20? true: false;
  }
  
  // 柱子碰撞
  function topbump (spr1, spr2) {
    return spr1.x - spr2.x < 30 && spr1.x - spr2.x > -30 && (spr1.y - spr1.height) - (spr2.y + spr2.height) <= 0? true: false;
  }

项目链接:demo


3、总结

这个 demo 目前看来虽然没什么比较难的点,但目前还只是属于能跑起来,比较粗糙,很多细节还是需要优化的,当然再继续往下做肯定会有挑战的,比如目前怪物出现都是定死的,需要改成随机出现,并且柱子与怪物不能再一个 Y 轴上,还有新增像金币、飞行、断桥等一些进阶的功能。


4、了解更多

原文链接:pixi 平铺精灵 demo (二)

你可能感兴趣的:(pixi 平铺精灵 demo (二))