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, true, true); // 第四个参数为 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 (一)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,979评论 6 528
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,750评论 3 412
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,647评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,299评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,104评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,623评论 1 320
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,716评论 3 434
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,873评论 0 285
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,380评论 1 330
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,344评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,496评论 1 365
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,055评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,756评论 3 343
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,146评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,410评论 1 281
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,072评论 3 386
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,485评论 2 370

推荐阅读更多精彩内容

  • PixiJS是一个2D渲染引擎,能自动侦测并使用WebGL或Canvas。 PixiJS使用JavaScript或...
    JunChow520阅读 13,131评论 4 10
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,570评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,203评论 4 8
  • 步骤:发微博01-导航栏内容 -> 发微博02-自定义TextView -> 发微博03-完善TextView和...
    dibadalu阅读 3,148评论 1 3
  • 回这一趟老家,心里多了两个疙瘩。第一是堂姐现在谈了一个有妇之夫,在她的语言中感觉,她不打算跟他有太长远的计划,这让...
    安九阅读 3,517评论 2 4