javascript - 使用 Pixi.js 优化提示的简单游戏

标签 javascript pixi.js game-development

我编写了一个小模拟,您可以在其中挖掘立方体,它们会落入一堆,您可以消耗一些立方体或使选择爆炸。

我开始这个小项目是为了好玩,我的目标是处理尽可能多的立方体(100k 将是一个好的开始)。

该项目很简单,有 3 种可能的操作:

  • 挖掘立方体(每次点击 2k)
  • 消耗立方体(每次点击 50 个)
  • 爆炸立方体(点击两点形成一个矩形)

目前,在我的计算机上,当我获得大约 20k 立方体时,性能开始下降。当您选择大部分立方体进行爆炸时,性能也会严重降低。我不确定简化物理的方法是最好的方法。

您能给我一些关于如何改进/优化它的提示吗?

这里是完整的代码:(堆叠在 SO 片段中不起作用,所以 here is the codepen version )

(() => {
  // Variables init
  let defaultState = {
    cubesPerDig : 2000,
    cubesIncome : 0,
    total : 0
  };
  let cubeSize = 2, dropSize = window.innerWidth, downSpeed = 5;
  let state,
      digButton, // Button to manually dig
      gameState, // An object containing the state of the game
      cubes, // Array containing all the spawned cubes
      heightIndex, // fake physics
      cubesPerX, // fake physics helper
      playScene; // The gamescene
  
  // App setup
  let app = new PIXI.Application();
  app.renderer.view.style.position = "absolute";
  app.renderer.view.style.display = "block";
  app.renderer.autoResize = true;
  document.body.appendChild(app.view);
  
  // Resize
  function resize() {
    app.renderer.resize(window.innerWidth, window.innerHeight);
  }
  window.onresize = resize;
  resize();
  
  // Hello ! we can talk in the chat.txt file
  
  // Issue : When there are more than ~10k cubes, performance start to drop
  // To test, click the "mine" button about 5-10 times
  
  // Main state
  function play(delta){
    // Animate the cubes according to their states
    let cube;
    for(let c in cubes){
      cube = cubes[c];
      switch(cube.state) {
        case STATE.LANDING:
          // fake physics
          if(!cube.landed){
            if (cube.y < heightIndex[cube.x]) {
              cube.y+= downSpeed;
            }else if (cube.y >= heightIndex[cube.x]) {
              cube.y = heightIndex[cube.x];
              cube.landed = 1;
              heightIndex[cube.x] -= cubeSize;
            }
          }
          break;
        case STATE.CONSUMING:
          if(cube.y > -cubeSize){
            cube.y -= cube.speed;
          }else{
            removeCube(c);
          }
          break;
        case STATE.EXPLODING:
          if(boundings(c)){
            continue;
          }
          cube.x += cube.eDirX;
          cube.y += cube.eDirY;
          break;
      }
    }
    updateUI();
  }
  
  // Game loop
  function gameLoop(delta){
    state(delta);
  }
  
  // Setup variables and gameState
  function setup(){
    state = play;
    digButton = document.getElementById('dig');
    digButton.addEventListener('click', mine);
    playScene = new PIXI.Container();
    gameState = defaultState;
    
    /* User inputs */
    // Mine
    document.getElementById('consume').addEventListener('click', () => {consumeCubes(50)});
    // Manual explode
    let explodeOrigin = null
    document.querySelector('canvas').addEventListener('click', e => {
      if(!explodeOrigin){
        explodeOrigin = {x: e.clientX, y: e.clientY};
      }else{
        explode(explodeOrigin, {x: e.clientX, y: e.clientY});
        explodeOrigin = null;
      }

    });
    window['explode'] = explode;

    heightIndex = {};
    cubesPerX = [];
    // Todo fill with gameState.total cubes
    cubes = [];
    app.ticker.add(delta => gameLoop(delta));
    app.stage.addChild(playScene);
  }
  
  /*
  * UI
  */
  function updateUI(){
    document.getElementById('total').innerHTML = cubes.length;
  }
  
  /*
  * Game logic
  */
  // Add cube when user clicks
  function mine(){
    for(let i = 0; i < gameState.cubesPerDig; i++){
      setTimeout(addCube, 5*i);
    }
  }
  
  // Consume a number of cubes
  function consumeCubes(nb){
      let candidates = _.sampleSize(cubes.filter(c => !c.eDirX), Math.min(nb, cubes.length));
      candidates = candidates.slice(0, nb);
      candidates.map(c => {
        dropCubes(c.x);
        c.state = STATE.CONSUMING;
      });
  }
  const STATE = {
    LANDING: 0,
    CONSUMING: 1,
    EXPLODING: 2
  }
  // Add a cube
  function addCube(){
    let c = new cube(cubeSize);
    let tres = dropSize / cubeSize / 2;
    c.x = window.innerWidth / 2 + (_.random(-tres, tres) * cubeSize);
    c.y = 0//-cubeSize;
    c.speed = _.random(5,8);
    cubes.push(c);
    c.landed = !1;
    c.state = STATE.LANDING;
    if(!cubesPerX[c.x]) cubesPerX[c.x] = [];
    if (!heightIndex[c.x]) heightIndex[c.x] = window.innerHeight - cubeSize;
    cubesPerX[c.x].push(c);
    playScene.addChild(c);
  }
  
  // Remove a cube
  function removeCube(c){
    let cube = cubes[c];
    playScene.removeChild(cube);
    cubes.splice(c,1);
  }
  
  // Delete the cube if offscreen
  function boundings(c){
    let cube = cubes[c];
    if(cube.x < 0 || cube.x + cubeSize > window.innerWidth || cube.y < 0 || cube.y > window.innerHeight)
    {
      removeCube(c);
      return true;
    }
  }
  
  // explode some cubes
  function explode(origin, dest){
    if(dest.x < origin.x){
      dest = [origin, origin = dest][0]; // swap
    }
    var candidates = cubes.filter(c => c.state != STATE.EXPLODING && c.x >= origin.x && c.x <= dest.x && c.y >= origin.y && c.y <= dest.y);
    if(!candidates.length)
      return;
    
    for(let i = origin.x; i <= dest.x; i++){
      dropCubes(i);
    }
    
    candidates.forEach(c => {
      c.explodingSpeed = _.random(5,6);
      c.eDirX = _.random(-1,1,1) * c.explodingSpeed * c.speed;
      c.eDirY = _.random(-1,1,1) * c.explodingSpeed * c.speed;
      c.state = STATE.EXPLODING;
    });
  }
  
  // Drop cubes
  function dropCubes(x){
    heightIndex[x] = window.innerHeight - cubeSize;
    if(cubesPerX[x] && cubesPerX[x].length)
      cubesPerX[x].forEach(c => {
        if(c.state == STATE.EXPLODING) return;
        c.landed = false; c.state = STATE.LANDING;
      });
  }
  
  /*
   * Graphic display
   */
  
  // Cube definition
  function cube(size){
    let graphic = new PIXI.Graphics();
    graphic.beginFill(Math.random() * 0xFFFFFF);
    graphic.drawRect(0, 0, size, size);
    graphic.endFill();
    return graphic;
  }
  
  // Init
  setup();
})()
/* styles */
/* called by your view template */

* {
  box-sizing: border-box;
}
body, html{
  margin: 0;
  padding: 0;
  color:white;
}

#ui{
  position: absolute;
  z-index: 2;
  top: 0;
  width: 0;
  left: 0;
  bottom: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.1/pixi.min.js"></script>
<script>PIXI.utils.skipHello();</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
<!-- Click two points to make cubes explode -->
<div id="ui">
    <button id="dig">
      Dig 2k cubes
    </button>
    <button id="consume">
      Consume 50 cubes
    </button>
    <p>
      Total : <span id="total"></span>
    </p>
  </div>

谢谢

最佳答案

使用 Sprites 总是比使用 Graphics 更快(至少在 v5 出现之前!)

所以我将你的多维数据集创建函数更改为

function cube(size) {
    const sprite = new PIXI.Sprite(PIXI.Texture.WHITE);
    sprite.tint = Math.random() * 0xFFFFFF;
    sprite.width = sprite.height = size;
    return sprite;
}

这提高了我自己的帧率

关于javascript - 使用 Pixi.js 优化提示的简单游戏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51596159/

相关文章:

javascript - METEOR - 将预构建的 HTML/CSS/JS 主题与 Meteor 集成

javascript - 使用浏览器扩展程序在浏览器外捕获键盘快捷键?

javascript - 在 pixi.js 中初始加载后访问纹理

javascript - 如何在 Canvas 上网格上的两点之间绘制瓷砖墙?

c++ - 如何在 C/C++ 中以编程方式查找 "Saved Games"文件夹?

javascript - 如何让 Dojo 复选框运行命令?

javascript - 如果父窗口关闭,如何关闭子窗口?

PIXI.js - png 图像资源未加载为图像/纹理、MIME 类型集,但没有扩展名

java - 更新表中的图像

javascript - 如何将 sql server 连接到我的 Javascript 游戏?