导航

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

侧边栏
热门文章
1推文
一晃眼正式进入2025年的蛇年春节了呢。祝各位大佬新春快乐!
热度
985
2页面
友链
热度
182
3博文
《孤独摇滚》圣地巡礼——下北泽&下北泽咖喱节2023
热度
156
4推文
今天去看了首部初音未来的电影《剧场版 世界计划 破碎的世界与无法歌唱的未来》。 说是初音未来的电影,但其实是手游《世界计划》的剧场版。好像有挺多人看到是初音未来的电影就去看了,结果发现并不全是😅。其实在宣传海报中,初音未来后面的那些人才是主角。 不过,我也是那些没玩过手游的观众之一。其实整场看下来问题也不是很大,就是对于人物关系和世界观的设定可能会有些问题。电影其实也很贴心地在剧情开头,大致以剧情的形式展示了手游中的五个团体的人物及其性格,但感觉还是有些杯水车薪,约等于看个眼熟。 此次的电影是第一次购买应援场。所谓的应援场,就是能在电影院里像是听演唱会一样挥舞荧光棒和尽情呐喊。看着前排那些粉丝看到自己喜欢角色时的呐喊,以及演唱会环节的打CALL,应援场确实是很有意思的一种电影观看方式呢! 剧情方面,属于看了开头大概能猜到整部动画的剧情走向。不过,通过演唱会的气氛渲染,甚至还感受到了一丝丝的感动。 总体来说,对于手游的粉丝来说可能是嘉年华般的狂喜,对于没接触过手游的路人来说也能一定程度融入其中,属于一部不错的粉丝向电影(不管是手游粉丝还是初音未来粉丝)。 首周电影特典是CD和游戏内的兑换码。送CD这种形式还第一次遇到,官方大气!
热度
143
5页面
游戏
热度
104
6推文
在整理接下来的活动的时候发现key社的25周年联动咖啡有些抽象。比如饮料上面放鲷鱼烧、放肉包、放鱼干甚至还有马桶圈😅。创意和梗都能理解,就是有些抽象。
热度
78
7页面
关于
热度
78
8推文
大约用了1个月,通过投影仪通关了《交响乐之雨》。 作为一部20年前的氛围作,现在玩起来也完全不过时。 由冈崎律子亲自操刀的音乐是这部游戏的最大亮点。每位可攻略对象都有自己独自的主题曲,特色鲜明且非常动听,如果有兴趣的话可以在各大网站搜索试听。 此次作画是由《向山进发》的作者しろ负责。我买的是20周年版,所以立绘是重置的。对于是旧版立绘好看还是新版立绘好看,我个人觉得各有特色。同时也能看到しろ老师这20年来的画风变化。 剧情方面,确实是有亮点。尤其是回收伏笔的时候,我以为我看穿了全部,但其实只看到了一半,这一刻我感受到了这可能是一部神作?然而我得知所有谜底后,回过头重新看真结局的时候却又感觉到了真结局收尾的草率,产生了一定的失落感。 总的来说这部游戏的氛围感确实很强,至今脑海里还能浮现游戏里的雨声和音乐。但是剧情方面个人觉得不管是哪条线收尾都有些草率,有点可惜。但不影响我推荐这部作品。 Steam链接:https://store.steampowered.com/app/629650/Symphonic_Rain/ 游戏OP:https://www.bilibili.com/video/BV16J411D7e7
热度
65
9推文
看《间谍×过家家·代号:白》的时候亚马逊还顺带给我推荐了《铃芽之旅》,这才想起来我《铃芽之旅》也还没看呀!于是又抽空在亚马逊上把《铃芽之旅》给看完了。 可以说是相继《你的名字》、《天气之子》之后的集大成之作。画面依旧是那么的漂亮,剧情依旧是那个女孩遇见男孩。相比较前几作,这作在男女主的感情描绘上确实生硬了一些,有一种男主仅凭一张脸就让女主出生入死的感觉😅。不过相对的,这一作对于"灾难"的描绘比前几作要强上很多。伏笔也还不错,只不过感觉剧中有挺多我觉得应该是伏笔的地方结果居然不是,不知道是不是因为篇幅原因给删减了? 这时间一晃《铃芽之旅》已经是2022年的电影了,不知道新海诚的下一部作品什么时候出呀!
热度
65
10推文
还以为Cloudcone放弃大硬盘的VPS了呢。这次Cloudcone公布了蛇年特价VPS,都是大硬盘,非常适合用来储存不太重要但是硬盘空间占用大的数据。 以下为VPS的参数和购买链接: CPU:4核 内存:2G 硬盘:240GB 流量:5T/月 价格:28.88美元/年 购买链接:https://app.cloudcone.com.cn/vps/367/create?token=cny-25-vps-1&ref=12006 CPU:8核 内存:4G 硬盘:480GB 流量:6T/月 价格:53.00美元/年 购买链接:https://app.cloudcone.com.cn/vps/368/create?token=cny-25-vps-2&ref=12006 CPU:12核 内存:8G 硬盘:960GB 流量:7T/月 价格:101.00美元/年 购买链接:https://app.cloudcone.com.cn/vps/369/create?token=cny-25-vps-3&ref=12006
热度
65
最新评论
广树
2025-01-30 08:56
@MqyGalaxy:新春快乐!ヾ(≧∇≦*)ゝ
广树
2025-01-30 08:56
@Xia:新春快乐!୧(๑•̀⌄•́๑)૭
广树
2025-01-30 08:55
@Zrzzz:新年快乐!🙇
广树
2025-01-30 08:55
@ZeroCounter:新年快乐!
广树
2025-01-30 08:55
@恶魔菌:那旅游的时间有点长了🤭
正在攻略

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