导航

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

侧边栏
最新评论
广树
2024-12-12 13:22
@HelloGakki:没有突破性的玩法导致玩家有些疲劳了吧。Nexon内部现在也很混乱,近些年太多谜之操作。除了这次要求全球的冒险岛R区的普区化,还有为了强推新版的跑跑卡丁车强行把有盈利的旧版跑跑卡丁车关了,现在因为没得到玩家认可又缩小了新版跑跑卡丁车的服务范围。
HelloGakki
2024-12-12 13:16
@广树:确实!现在各种活动换皮,感觉流失了好多玩家了
广树
2024-12-12 13:12
@HelloGakki:是的呀,我是在菈菈的版本入坑的日服R区,菈菈太可爱了!
HelloGakki
2024-12-12 13:09
大佬玩的是拉拉呀
广树
2024-12-12 09:09
@zephyr:本来是不符合要求的,但是想了想还是加上了,希望未来能多多交流👏
正在攻略

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