You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PIXI.js复刻agar.io随玩家移动的滚动网格背景(高效版)

Hey there! I’ve messed around with recreating agar.io-style scrolling grids in PIXI.js before, and the key to doing it efficiently is only rendering the grid lines that are actually in (or just slightly outside) the viewport—no wasting resources drawing lines that players can’t see. Let me walk you through the best way to pull this off:

Core Concept

Agar.io’s grid scrolls with the player because the camera follows the player’s position. Instead of drawing an infinite grid (which is impossible and terrible for performance), we calculate exactly which grid lines fall within the current viewport (plus a small buffer to prevent edge flickering) and only render those lines. We’ll update this set of lines as the player moves.

Step-by-Step Implementation

Let’s break this down into working code:

1. Set Up PIXI.js & Basic Player

First, initialize the PIXI app and create a simple player object to simulate movement:

const app = new PIXI.Application({
  width: window.innerWidth,
  height: window.innerHeight,
  backgroundColor: 0x1a1a1a
});
document.body.appendChild(app.view);

// Player sprite (simulate agar.io's player blob)
const player = new PIXI.Graphics();
player.beginFill(0xff0000);
player.drawCircle(0, 0, 20);
player.endFill();
player.position.set(app.screen.width / 2, app.screen.height / 2);
app.stage.addChild(player);

// Grid configuration
const gridSize = 32; // Spacing between grid lines
const gridColor = 0x333333;
const gridThickness = 1;

// Store active grid lines to avoid recreating everything every frame
let horizontalLines = [];
let verticalLines = [];

2. Build the Grid Update Function

This function calculates the visible grid range, cleans up lines that are no longer in view, and creates new lines for the newly visible area:

function updateGrid() {
  // Make the camera follow the player
  app.stage.pivot.copy(player.position);
  app.stage.position.set(app.screen.width / 2, app.screen.height / 2);

  // Calculate viewport bounds (add a grid-sized buffer to prevent edge flicker)
  const halfViewportWidth = app.screen.width / 2;
  const halfViewportHeight = app.screen.height / 2;
  const viewportLeft = player.x - halfViewportWidth - gridSize;
  const viewportRight = player.x + halfViewportWidth + gridSize;
  const viewportTop = player.y - halfViewportHeight - gridSize;
  const viewportBottom = player.y + halfViewportHeight + gridSize;

  // --- Handle horizontal lines ---
  // Calculate the range of Y positions we need to render
  const startY = Math.floor(viewportTop / gridSize) * gridSize;
  const endY = Math.ceil(viewportBottom / gridSize) * gridSize;

  const neededYPositions = new Set();
  for (let y = startY; y <= endY; y += gridSize) {
    neededYPositions.add(y);
  }

  // Clean up old lines that are no longer needed
  const updatedHorizontalLines = [];
  for (const line of horizontalLines) {
    if (neededYPositions.has(line.yMarker)) {
      updatedHorizontalLines.push(line);
      neededYPositions.delete(line.yMarker);
    } else {
      app.stage.removeChild(line);
      line.destroy();
    }
  }

  // Create new lines for missing Y positions
  for (const y of neededYPositions) {
    const line = new PIXI.Graphics();
    line.lineStyle(gridThickness, gridColor);
    line.moveTo(viewportLeft, y);
    line.lineTo(viewportRight, y);
    line.yMarker = y; // Mark the line's Y position for future checks
    app.stage.addChild(line);
    updatedHorizontalLines.push(line);
  }
  horizontalLines = updatedHorizontalLines;

  // --- Handle vertical lines ---
  const startX = Math.floor(viewportLeft / gridSize) * gridSize;
  const endX = Math.ceil(viewportRight / gridSize) * gridSize;

  const neededXPositions = new Set();
  for (let x = startX; x <= endX; x += gridSize) {
    neededXPositions.add(x);
  }

  const updatedVerticalLines = [];
  for (const line of verticalLines) {
    if (neededXPositions.has(line.xMarker)) {
      updatedVerticalLines.push(line);
      neededXPositions.delete(line.xMarker);
    } else {
      app.stage.removeChild(line);
      line.destroy();
    }
  }

  for (const x of neededXPositions) {
    const line = new PIXI.Graphics();
    line.lineStyle(gridThickness, gridColor);
    line.moveTo(x, viewportTop);
    line.lineTo(x, viewportBottom);
    line.xMarker = x; // Mark the line's X position for future checks
    app.stage.addChild(line);
    updatedVerticalLines.push(line);
  }
  verticalLines = updatedVerticalLines;
}

3. Add Player Movement & Grid Updates

Add keyboard controls for the player, and trigger grid updates as the player moves:

// Player movement speed
const moveSpeed = 5;

window.addEventListener('keydown', (e) => {
  switch(e.key) {
    case 'ArrowUp':
      player.y -= moveSpeed;
      break;
    case 'ArrowDown':
      player.y += moveSpeed;
      break;
    case 'ArrowLeft':
      player.x -= moveSpeed;
      break;
    case 'ArrowRight':
      player.x += moveSpeed;
      break;
  }
});

// Update grid every frame (we'll optimize this next!)
app.ticker.add(updateGrid);

Performance Optimization Tips

To make this even smoother (especially for large viewports or high frame rates), try these tweaks:

  • Reduce Update Frequency: You don’t need to update the grid every frame. Only call updateGrid() when the player has moved more than half the gridSize distance. Track the player’s last position and compare it to the current position to trigger updates only when necessary.
  • Use an Object Pool: Instead of destroying unused lines, store them in a pool and reuse them when new lines are needed. This cuts down on garbage collection (GC) pauses, which is critical for smooth performance.
  • Cache Line Styles: If your grid style never changes, use PIXI.LineStyleCache to avoid re-setting the line style every time you create a new line.
  • Batch Rendering: For even better performance, use a single PIXI.Graphics object to draw all grid lines at once, or use PIXI.Mesh for batch rendering. This reduces the number of draw calls PIXI has to process.

Final Result

This implementation will give you a smooth, scrolling grid that only renders what’s visible—just like agar.io. It’s lightweight, efficient, and avoids wasting resources on off-screen content.

内容的提问来源于stack exchange,提问作者eeze

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:54:56