pixi 平铺精灵 demo (一)

本文介绍了如何使用Pixi.js库创建平铺精灵,包括两种创建方法及其区别,以及如何设置纹理偏移。接着讨论了视差滚动的概念,并解决在实现过程中出现的纹理精度偏差问题。同时,文章还涵盖了基本的碰撞检测方法,并提供了一个简单的碰撞检测实现。最后,通过整合这些技术,创建了一个包含人物动作、视差滚动和碰撞检测的跑酷游戏DEMO。
摘要由CSDN通过智能技术生成

pixi 平铺精灵 demo (一)


引言

本篇是在学习 pixi 平铺精灵的时候联想起的一个视差滚动加碰撞检测的 demo 以及遇到的一个问题。


目录

  1. 平铺精灵

    1.1 创建方式

    1.2 区别

    1.3 偏移值

    1.4纹理偏移代码

  2. 视差滚动

    2.1 纹理精度偏差

  3. 碰撞检测

  4. demo

  5. 总结

  6. 了解更多


1、平铺精灵

跳转回目录

1.1 创建方式

pixi 平铺精灵的创建方式:

// 第一种
new PIXI.extras.TilingSprite(texture, width, height);

// 第二种
new PIXI.extras.TilingSprite.from(source, width, height);

1.2 区别

第一种:

texture 通过 PIXI.Loader.shared.resources 中获取图片纹理。

let bgSpr = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['bg'].texture, app.renderer.width, app.renderer.height);

第二种:

source 可以通过 url 路径直接引入。

let bgSpr = new PIXI.extras.TilingSprite.from('./img/bg.jpg', app.renderer.width, app.renderer.height);

注意 widht height 是指平铺的范围值不设置默认为宽高 100px 。


1.3 偏移值

tilePosition:

tilePosition.set(x, y)positions.set(x, y) 的区别在与前者是移动平铺精灵纹理,后者移动的是平铺精灵的位置。

具体用法:

利用 ticker 游戏循环中更新 tilePosition.x 值。

app.ticker.add(() => {
  bgSpr.tilePosition.x -= -1;
});

1.4 纹理偏移代码

let app = new PIXI.Application({widht: app.renderer.width, height: app.renderer.height});

document.body.appendChild(app.view);

PIXI.Loader.shared.add('bg', './img/bg.jpg');

PIXI.Loader.shared.load(() => {
  setup();
});

function setup () {
  let bgSpr = new PIXI.extras,TilingSprite(PIXI.Loader.shared.resources['bg'].texture, app.renderer.width, app.renderer.height);
  
  app.stage.addChild(bgSpr);
  
  app.ticker.add(() => {
  	bgSpr.tilePosition.x -= 1;
	});
};

效果图(随便找的素材):

屏幕录制2021-03-08下午5.gif

2、 视差滚动

跳转回目录

平铺精灵一般用于创建无缝滚动背景,上面我们实现了平铺精灵的偏移,那什么是视差滚动呢?

cocos 文档:视差滚动是指让多层背景以不同的速度移动,从而形成的立体运动效果。比如超级马里奥游戏中,角色所在地面的移动与背景天空的移动,就是一个视差滚动。

也就是说需要两个平铺精灵然后在游戏循环中让他们以不同速度偏移。

let prospectSpr = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['prospect'].texture, app.renderer.width, 437);

app.stage.addChild(prospectSpr);

app.ticker.add(() => {
  prospectSpr.tilePosition.x -= 3;
})

效果图:

屏幕录制2021-03-08下午5-(1).gif

这样一个视差滚动就实现了,背景层的速度是 1 前景层的速度是 3。


2.1 纹理精度偏差

在学习的过程中也遇到了一个坑,就是在每次偏移一个循环时精灵图的清晰度都会变差一点,多个循环过后导致精灵图变成了马赛克图:

注意:在浏览器模拟移动端不会出现该情况,在移动端上才会有以下情况。
起始以为是游戏循环方法的问题,然后换了插件在试结果还是会出现以上情况,在 google 上查也到了类似的情况:
大概问题在于平铺精灵随时间推移出现精度问题。

也提出了解决方案:

prospectSpr.tilePosition.x %= PIXI.Loader.shared.resources['prospect'].texture.width;

ticker 代码如下:

app.ticker.add(() => {
  prospectSpr.tilePosition.x -= 3;
  prospectSpr.tilePosition.x %= PIXI.Loader.shared.resources['prospect'].texture.width;
})

3、碰撞检测

跳转回目录

在网页中碰撞检测的原理就是通过 x, y 坐标来计算两个矩形是否发生碰撞也就是判断他们是否有发生重叠。

碰撞检测方法可以参考 PIXI 教程里的碰撞检测,还有一个比较好用的插件 Bump.js 它的使用非常简单:

b.hit(sprite1, sprite2); // 返回一个 boolean 类型,true 为碰撞。

b.hit(sprite1, sprite2, true); // 第三个参数为 true 在碰撞时不会重叠。

b.hit(sprite1, sprite2, truetrue); // 第四个参数为 true 在碰撞时会反弹第一个精灵

详细教程前往:Bump.js 教程。

知道了原理其实做起来就很简单了,只要设置好精灵的中心点,在判断两个精灵的 x, y 轴是否重叠就可以了,以下是我实现的一个比较简陋的碰撞检测:

function bump (spr1, spr2) {
  spr1.anchor.set(0.5, 1); // 设置精灵中心点位置
  spr2.anchor.set(0.5, 1);
  
  if (spr1.x - spr2.x < spr2.width && spr1.x - spr2.x > -spr2.width) {
    // 符合x位置条件
    return spr1.y - spr2.y === 0? true : false; // 符合y条件返回
  } else {
    return false;
  }
}

然后只要判断返回的是 true 还是 false 就行了。

新增代码:

PIXI.Loader.shared
  .add('role', './img/sprite1_0.png')
  .add('monster', './img/blob.png')let role = new PIXI.Sprite(PIXI.Loader.shared.resources['role'].texture);
let monster = new PIXI.Sprite(PIXI.Loader.shared.resources['monster'].texture);
let isBump = null;

role.anchor.set(0.5, 1);
monster.anchor.set(0.5, 1);

role.scale.set(1.5, 1.5);
monster.scale.set(3, 3);

role.position.set(300, app.renderer.height - 180);
monster.position.set(1500, app.renderer.height - 180);

app.stage.addChild(role, monster);

app.ticker.add(() => {
  monster.x -= 3;
  bump(role, monster) && console.log('碰撞');
});

function bump (spr1, spr2) {
  spr1.anchor.set(0.5, 1); // 设置精灵中心点位置
  spr2.anchor.set(0.5, 1);
  
  if (spr1.x - spr2.x < spr2.width && spr1.x - spr2.x > -spr2.width) {
    // 符合x位置条件
    return spr1.y - spr2.y === 0? true : false; // 符合y条件返回
  } else {
    return false;
  }
}

效果图:

1615286963123606.gif

4、demo

跳转回目录

以上的主要功能就差不多了,在加上人物的动作和交互那么一个类似跑酷的demo就出来了,以下代码还用了一个插件用于替换人物动作图片 smoothie.js 教程前往

function setup () {
  // 背景
  let bgSpr = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['bg'].texture, app.renderer.width, app.renderer.height);
  // 前景
  let prospectSpr = new PIXI.extras.TilingSprite(PIXI.Loader.shared.resources['prospect'].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 roleSmoothie = null; // 人物动画
  let monsterSmoothie = null; // 怪物动画
  let prospectSmoothie = null; // 前景动画
  let isBump = null; // 跳跃状态

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

  let prospectSpeed = 3; // 前景速度

  let isAction = true; // 动作状态
  
  // 中心点
  prospectSpr.anchor.set(0, 1);
  role.anchor.set(0.5, 1);
  monster.anchor.set(0.5, 1);

  // 缩放比例
  role.scale.set(1.5, 1.5);
  monster.scale.set(3, 3);

  // 位置
  role.position.set(300, app.renderer.height - 180);
  monster.position.set(1500, app.renderer.height - 180);
  prospectSpr.y = app.renderer.height;

  // 添加到舞台
  app.stage.addChild(bgSpr, prospectSpr, role, monster);
  
  // 平移
  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 run () {
    role.texture = PIXI.Loader.shared.resources[config.run[roleSprRunIndex]].texture;
    if (roleSprRunIndex < 6) {
      roleSprRunIndex++;
    } else {
      roleSprRunIndex = 0;
      roleSmoothie.update = go.bind(this);
    }
  }

  // 跳
  function jump () {
    role.texture = PIXI.Loader.shared.resources[config.jump[roleSprJumpIndex]].texture;
    if (roleSprJumpIndex < 5) {
      roleSprJumpIndex++;
      role.position.y -= 30;
      isAction = false;
    } else {
      roleSprJumpIndex = 0;
      role.position.y = 570;
      isAction = true;
      roleSmoothie.update = go.bind(this);
    }
  }

  // 倒
  function inverted (num) {
    role.texture = PIXI.Loader.shared.resources[config.inverted[roleSprInverIndex]].texture;
    if (roleSprInverIndex < num) {
      roleSprInverIndex++;
      isAction = false;
    } else if (num === 6) {
      isAction = true;
    } else {
      roleSprInverIndex = 0;
      isAction = true;
      roleSmoothie.update = go.bind(this);
    }
  }
  // 人物移动
  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();

  // 键盘按下事件
  $(document).keydown((e) => {
    if (!isAction) return;
    e.keyCode === 38 && (roleSmoothie.update = jump.bind(this), prospectSmoothie.update = translate.bind(this, prospectSpr, 4));
    e.keyCode === 39 && (roleSmoothie.update = run.bind(this), prospectSmoothie.update = translate.bind(this, prospectSpr, 6));
  });

  // 键盘抬起
  $(document).keyup((e) => {
    prospectSmoothie.update = translate.bind(this, prospectSpr, 3);
  });
  
  app.ticker.add(() => {
    bgSpr.tilePosition.x -= 1;
    bgSpr.tilePosition.x %= PIXI.Loader.shared.resources['bg'].texture.width;
    bump(role, monster) && (roleSmoothie.update = inverted.bind(this, 3));
  });
  
  // 碰撞
  function bump (spr1, spr2) {
    spr1.anchor.set(0.5, 1);
    spr2.anchor.set(0.5, 1);
    if (spr1.x - spr2.x < spr2.width && spr1.x - spr2.x > -spr2.width) {
      return spr1.y - spr2.y === 0? true : false;
    } else {
      return false;
    }
  };
}

项目链接:demo


5、总结

学习知识还是需要通过实践的,从实践中就可以看出文字教程上没有的很多问题,比如说平铺精灵随时间推移会出现精度问题,没有做 demo 之前我是不知道会出现这个问题的并且在 pc 浏览器上也不会出现这个问题的。通过学习一个知识点去做一个 demo 不仅能巩固这个知识点,还能延伸到其他知识点,比如碰撞检测、精灵图纹理切换、游戏循环等。有了初步 demo 原型之后就可以随意添加其他功能,比如:血量、分数、场景变换、上方柱子障碍物等等,这样一个简单的小游戏就算出来了。


6、了解更多

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

微信搜索公众号:DigitMagic魔数实验室

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值