导航

萌即是正义!时不时分享一些ACG活动记录与有趣代码的小站!

侧边栏
热门文章
1页面
程序员老黄历&求签
热度
481
2推文
一开始还以为是2.5次元的舞台剧,结果仔细一看是音乐剧!?有一丢丢的兴趣,到时候看看能不能搞到门票。 《FateZero》真人音乐剧 定妆照预告:https://www.bilibili.com/video/BV1RqSoYmEzJ
热度
181
3博文
探访《蜡笔小新》老家——春日部游记
热度
130
4博文
《孤独摇滚》圣地巡礼——下北泽&下北泽咖喱节2023
热度
104
5推文
知道这套道具的人年纪应该都不小了吧。
热度
78
6博文
冒险岛日服(JMS)台服(TMS)64位客户端语言区域转区教程
热度
78
7博文
记录第一次在日本进行携号转网(MNP)
热度
65
8推文
好家伙,我用的日版oppo手机在刚才系统更新之后突然多出了两款系统APP,分别是“APP Picks”和“News Plus”,看起来是用来取代安卓原生的“Google Play”和“Google News”。如果是新机购买时自带还能理解,可问题是你在只提到了这次更新是安全性更新的前提下,突然给我加两个APP是不是有点太恶心了?甚至还不能删除! 在有“Google Play”的情况下,“APP Picks”真的非常鸡肋,上架“APP Picks”的APP主要面向国际版,少了一些日本本土的APP,而且还时不时弹出各种通知简直就是噩梦级体验。 我觉得加是可以加,但请至少给用户一些选择权。而不是在安全更新补丁中搞点自己的东西!
热度
52
9推文
刚发完对《冒险岛Online》R区的遗憾没多久,韩服那边又开始搞事情了,做出了如下的修改: 1、删除持有金币,主要消耗、其他道具; 2、可使用拍卖、个人件交易、金币市场功能; 3、装备卷轴强化和可用附加潜能,但是该效果在3个月内不生效,R区的终伤BUFF仍旧保存3个月; 4、删除R区被动中BOSS装备掉率增加属性等全部属性; 5、现有装备道具均变为永久不可交易道具。 ※主要删除道具:金币、红魔方、黑魔方、痕迹、强大火花、核心宝石、可获得装备的部分消耗道具、可通过金币购买的道具等。 这可真是至R区于死地啊...要说修改游戏的模式也就算了,现在不仅没有任何补充,还把先前的劳动成果给删除了是不是太过分了!? 日服到时候肯定二话不说跟进,国际服那边如果没了韩服对R区的技术支持,个人觉得服软也是时间的问题。
热度
52
10博文
谈谈这次《冒险岛Online》日服R区的削弱事件
热度
52
最新评论
广树
2024-11-21 16:04
@hikari:BGM也许是吧,歌曲部分感觉不一定
hikari
2024-11-21 09:52
居然是音乐剧!真是没想到,难道说会用梶浦大妈的配乐吗?看预告片里就用的是fz里最扣人心弦的bgm。🥰
广树
2024-11-20 08:18
@石樱灯笼:这应该是受众的年龄层的问题吧
石樱灯笼
2024-11-20 06:57
@广树:承太郎抽根烟,整张嘴都被打码。
广树
2024-11-20 06:48
@石樱灯笼:不是挺符合人设吗?为什么会离谱?
正在攻略

logo_kai.jpg


PSN奖杯卡

PSN奖杯卡

赞助商广告

2D WebGL renderer Pixi.js v4 入门【最终回】

作者:广树时间:2018-09-12 13:47:31分类:JavaScript/jQuery/Vue

前情回顾

上回链接:2D WebGL renderer Pixi.js v4 入门【第五回】》

上回学习了WebGL的各种图像与文字的绘制。


检测碰撞

上图为理想状态。

左边的猫能够使用键盘操作,右边的块是无法移动的,然后上面的信息提示状态。


在基于<第三回>的键盘控制所写的代码中,

改造和追加了一部分代码。

var 猫, 块, 消息, 状态;
function setup(){
  放入猫;
  制作块;
  消息初始化;
  设置键盘事件的移动数值;
  将状态设定为play;
  gameLoop();
}

function play(){
  接收键盘的移动数值;
  if(碰撞){
    变化块的颜色和消息;
  }else{
    回滚块的颜色和消息;
  }
}

function 碰撞(r1, r2){
  查找每个元素的中心点;
  计算每个元素一半的高和一半的宽;
  r1的一半高度和r2的一半高度相加;
  r1的一半宽度和r2的一半宽度相加;

  if(r1,r2的中心点距离 < r1,r2 相加的距离){
    碰撞;
  }
}


翻译成代码就是这样

var cat, box, message, state;

function setup() {
  box = new PIXI.Graphics();
  box.beginFill(0xCCFF99);
  box.drawRect(0, 0, 64, 64);
  box.endFill();
  box.x = 120;
  box.y = 96;
  stage.addChild(box);

  cat = new Sprite(resources["images/cat.png"].texture);
  cat.x = 16;
  cat.y = 96; 
  cat.vx = 0;
  cat.vy = 0;
  stage.addChild(cat);

  var left = keyboard(37),
      up = keyboard(38),
      right = keyboard(39),
      down = keyboard(40);

  left.press = function() {
    cat.vx = -5;
    cat.vy = 0;
  };
  left.release = function() {
    if (!right.isDown && cat.vy === 0) {
      cat.vx = 0;
    }
  };
  up.press = function() {
    cat.vy = -5;
    cat.vx = 0;
  };
  up.release = function() {
    if (!down.isDown && cat.vx === 0) {
      cat.vy = 0;
    }
  };
  right.press = function() {
    cat.vx = 5;
    cat.vy = 0;
  };
  right.release = function() {
    if (!left.isDown && cat.vy === 0) {
      cat.vx = 0;
    }
  };
  down.press = function() {
    cat.vy = 5;
    cat.vx = 0;
  };
  down.release = function() {
    if (!up.isDown && cat.vx === 0) {
      cat.vy = 0;
    }
  };

  message = new PIXI.Text(
    "No collision...", 
    {font: "18px sans-serif", fill: "white"}
  );
  message.position.set(8, 8);
  stage.addChild(message);

  state = play;
 
  gameLoop();
}

function play() {
  cat.x += cat.vx;
  cat.y += cat.vy;

  if (hitTestRectangle(cat, box)) {
    message.text = "hit!";
    box.tint = 0xff3300;
  } else {
    message.text = "No collision...";
    box.tint = 0xccff99;
  }
}

function hitTestRectangle(r1, r2) {
  var hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
  hit = false;

  r1.centerX = r1.x + r1.width / 2; 
  r1.centerY = r1.y + r1.height / 2; 
  r2.centerX = r2.x + r2.width / 2; 
  r2.centerY = r2.y + r2.height / 2; 

  r1.halfWidth = r1.width / 2;
  r1.halfHeight = r1.height / 2;
  r2.halfWidth = r2.width / 2;
  r2.halfHeight = r2.height / 2;

  vx = r1.centerX - r2.centerX;
  vy = r1.centerY - r2.centerY;

  combinedHalfWidths = r1.halfWidth + r2.halfWidth;
  combinedHalfHeights = r1.halfHeight + r2.halfHeight;

  if (Math.abs(vx) < combinedHalfWidths) {
    if (Math.abs(vy) < combinedHalfHeights) {
      hit = true;
    } else {
      hit = false;
    }
  } else {
    hit = false;
  }

  return hit;
};


分析之后,发现还挺简单的。

如果没问题的话就进入正题吧。


寻宝游戏

游戏原型大致是这样:避开怪物,获取宝物,从门那里出去后成功。如果碰到怪物的话减少HP。减完了则判断为输。

代码设计

// pixi.js的初始化以及图像的读取

function setup() {
  // 初始化,然后游戏状态切换为play。
  // gameLoop();
}

function gameLoop() {
  // 描绘,并将精灵传递到render。
}

function play() {
  // 游戏所有的逻辑写这里。
}

function end() {
  // 游戏结束后。
}

// 其他必要的helper functions:
// `keyboard`, `hitTestRectangle`, `contain` and `randomInt`


首先将游戏场景和游戏结束场景分开。(不同的Container)

gameScene = new Container();
stage.addChild(gameScene);

gameOverScene = new Container();
stage.addChild(gameOverScene);

然后,先将gameOverScene隐藏。

gameOverScene.visible = false;


游戏场景

首先将简单的洞窟背景、门、勇者、宝箱描绘出来。

id = resources["images/treasureHunter.json"].textures;

//洞窟
dungeon = new Sprite(id["dungeon.png"]);
gameScene.addChild(dungeon);

//门
door = new Sprite(id["door.png"]);
door.position.set(32, 0);
gameScene.addChild(door);

//勇者
explorer = new Sprite(id["explorer.png"]);
explorer.x = 68;
explorer.y = gameScene.height / 2 - explorer.height / 2;
explorer.vx = 0;
explorer.vy = 0;
gameScene.addChild(explorer);

//宝箱
treasure = new Sprite(id["treasure.png"]);
treasure.x = gameScene.width - treasure.width - 48;
treasure.y = gameScene.height / 2 - treasure.height / 2;
gameScene.addChild(treasure);

接下来制作随机移动的怪物。

// 设定怪物数量

var numberOfBlobs = 6,

    // 怪物与怪物之间的间距

    spacing = 48,

    // 从左边开始最初的空隙

    xOffset = 150,

    // 速度

    speed = 2,

    // 方向

    direction = 1;



// 储存怪物的数组

blobs = [];



// 按照设定的数值描绘怪物

for (var i = 0; i < numberOfBlobs; i++) {



  // 怪物出生(?)

  var blob = new Sprite(id["blob.png"]);



  // 空出沿x轴一定距离的空间

  var x = spacing * i + xOffset;



  // y坐标(随机)

  var y = randomInt(0, stage.height - blob.height);



  // 给与坐标

  blob.x = x;

  blob.y = y;



  // 方向(direction)为1的话向下,-1的话向上

  // 方向 乘以 速度

  blob.vy = speed * direction;



  // 下一只怪物为反方向

  direction *= -1;



  // 配置到数组中

  blobs.push(blob);



  gameScene.addChild(blob);

}

然后是血条

healthBar = new PIXI.DisplayObjectContainer();
healthBar.position.set(stage.width - 170, 6)
gameScene.addChild(healthBar);

// HP血条的空间
var innerBar = new PIXI.Graphics();
innerBar.beginFill(0x000000);
innerBar.drawRect(0, 0, 128, 8);
innerBar.endFill();
healthBar.addChild(innerBar);

// HP血条的HP值
var outerBar = new PIXI.Graphics();
outerBar.beginFill(0xFF3300);
outerBar.drawRect(0, 0, 128, 8);
outerBar.endFill();
healthBar.addChild(outerBar);

healthBar.outer = outerBar;

这里出现的DisplayObjectContainer和Container和ParticleContainer的差异非常明显呢。

另外,outer的意义现在还不是很清楚,等知道了补充。


游戏结束场景

准备好游戏结束后的生理和失败的消息。

// 写在之前隐藏gameOverScene代码的后面

  message = new Text(
    "The End!", 
    {font: "64px Futura", fill: "white"}
  );
  message.x = 120;
  message.y = stage.height / 2 - 32;
  gameOverScene.addChild(message);


play function

已经准备好登场人物和怪物了,现在将游戏时要做什么列了一张表。


勇者移动,移动范围限制。
怪物的移动,移动范围限制,碰撞到墙壁时的反馈。
勇者和怪物碰撞了吗?
勇者和宝箱碰撞了吗?(获得宝物)
勇者和门碰撞了吗?(成功逃出)
游戏成功与失败的判定


然后,整理一下吧!

作为helper function集中起来由play function调用比较好的有


范围限制(判断是否在边界)
碰撞判断


那么,改变代码吧

// 范围限制(判断是否在边界)
// 这里放入的值是能够移动、移动中的精灵(这里为勇者和怪物)和可移动范围
function contain(sprite, container) {
  var collision = undefined;
  // 左
  if (sprite.x < container.x) {
    sprite.x = container.x;
    collision = "left";
  }
  // 上
  if (sprite.y < container.y) {
    sprite.y = container.y;
    collision = "top";
  }
  // 右
  if (sprite.x + sprite.width > container.width) {
    sprite.x = container.width - sprite.width;
    collision = "right";
  }
  // 下
  if (sprite.y + sprite.height > container.height) {
    sprite.y = container.height - sprite.height;
    collision = "bottom";
  }
  // 返回值
  return collision;
}
// 判断是否碰撞的函数
function hitTestRectangle(r1, r2) {
  var hit, combinedHalfWidths, combinedHalfHeights, vx, vy;

  hit = false;

  r1.centerX = r1.x + r1.width / 2; 
  r1.centerY = r1.y + r1.height / 2; 
  r2.centerX = r2.x + r2.width / 2; 
  r2.centerY = r2.y + r2.height / 2; 

  r1.halfWidth = r1.width / 2;
  r1.halfHeight = r1.height / 2;
  r2.halfWidth = r2.width / 2;
  r2.halfHeight = r2.height / 2;

  vx = r1.centerX - r2.centerX;
  vy = r1.centerY - r2.centerY;

  combinedHalfWidths = r1.halfWidth + r2.halfWidth;
  combinedHalfHeights = r1.halfHeight + r2.halfHeight;

  if (Math.abs(vx) < combinedHalfWidths) {
    if (Math.abs(vy) < combinedHalfHeights) {
      hit = true;
    } else {
      hit = false;
    }
  } else {
    hit = false;
  }

  return hit;
};
// 虽然感觉没必要独立出来,但还是和教程一样分开写吧
function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

放入play function

function play() {

  // 用键盘事件中获取的数值来移动
  explorer.x += explorer.vx;
  explorer.y += explorer.vy;

  // 勇者的移动范围
  contain(explorer, {x: 28, y: 10, width: 488, height: 480});
  //contain(explorer, stage);

  // 给勇者一个没有碰撞的初始设定
  var explorerHit = false;

  // 使用怪物的分布
  blobs.forEach(function(blob) {

    // 使其移动
    blob.y += blob.vy;

    // 给怪物一个移动范围的限制,捕捉返回的值
    var blobHitsWall = contain(blob, {x: 28, y: 10, width: 488, height: 480});

    // 判断返回的值,将方向反向
    if (blobHitsWall === "top" || blobHitsWall === "bottom") {
      blob.vy *= -1;
    }

    // 如果和勇者碰上了,就改变刚才碰撞的值
    if(hitTestRectangle(explorer, blob)) {
      explorerHit = true;
    }
  });

  // 如果勇者和怪物碰上了
  if(explorerHit) {

    // 一瞬间半透明
    explorer.alpha = 0.5;

    // HP削减
    healthBar.outer.width -= 1;

  } else {

    // 从半透明恢复
    explorer.alpha = 1;
  }

  // 如果勇者和宝箱碰撞
  if (hitTestRectangle(explorer, treasure)) {

    // 做一个手持宝箱的样子(宝箱的位置一直跟随在勇者右下角)
    treasure.x = explorer.x + 8;
    treasure.y = explorer.y + 8;
  }

  // HP归零则游戏结束,弹出失败信息
  if (healthBar.outer.width < 0) {
    state = end;
    message.text = "You lost!";
  }

  // 宝箱(和勇者)和门碰撞,游戏结束,弹出成功信息
  if (hitTestRectangle(treasure, door)) {
    state = end;
    message.text = "You won!";
  } 
}

然后用end function切换场景

function end() {
  gameScene.visible = false;
  gameOverScene.visible = true;
}

游戏完成

<点击预览游戏效果>


补充:关于精灵

精灵除了坐标、visible、旋转之外还有各种设置,详情可以在官方文档中查找。

连接如下

Class: Sprite


基本上精灵是遵循继承规则的

DisplayObject > Container > Sprite

就此教程结束。

各位看官辛苦了。


本回的代码:进入git


本文翻译自:2D WebGL renderer Pixi.js v4【連載第六回】2D WebGL renderer Pixi.js v4【連載最終回】
翻译者:广树
转载请注明出处!


#pixi.js

donate.png

1210 x 50(蓝底).png

cloudcone