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

如何在JavaScript中正确处理RPG角色精灵表(Spritesheet)

迷你RPG游戏精灵表渲染问题修复

我正在制作一款迷你RPG游戏,跟着教程操作时碰到了精灵表(Spritesheet)处理问题。我的精灵表来自Gaia个人资料,其中索引4-8是无脚角色的腿部动作循环帧,索引5是穿鞋的Idle帧。作为新手,我已经尝试调整各类尺寸参数,但还是没法正确渲染。

问题核心分析

  1. 精灵帧尺寸错误:当前代码里设置的spriteWidth = 60、spriteHeight = 160和实际精灵表的单帧尺寸不符,实际每个角色帧的宽度是64px,高度是128px,这是渲染错位的主要原因。
  2. 帧索引匹配错误:初始空闲帧设置为frame = 1,和描述的“索引5是穿鞋Idle帧”不符;腿部动画帧数组只包含了4-7,遗漏了索引8的帧。
  3. 碰撞逻辑瑕疵:TV区域的边界判断逻辑可能导致角色移动异常,需要调整Y轴限制条件。

修改后的完整代码

JavaScript代码

const canvas = document.getElementById('avatar-canvas');
const ctx = canvas.getContext('2d');

const sprite = new Image();
sprite.src = 'my_avi.png';

// 修正为精灵表实际单帧尺寸
const spriteWidth = 64;
const spriteHeight = 128;

const directions = {
  down: 0,
  left: 1,
  right: 2,
  up: 3
};

// 修正腿部动画帧范围为4-8
let legFrames = [4, 5, 6, 7, 8];
let legFrame = 0;

// 初始空闲帧设置为指定的索引5
let frame = 5;
let direction = directions.down;

let x = canvas.width / 2 - spriteWidth / 2;
let y = canvas.height - spriteHeight - 20;

let tick = 0;
let moving = false;
const keys = {};

// 绘制角色
function drawAvatar() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const col = moving ? legFrames[legFrame % legFrames.length] : frame;
  const row = direction;

  ctx.drawImage(
    sprite,
    col * spriteWidth, row * spriteHeight,
    spriteWidth, spriteHeight,
    x, y,
    spriteWidth, spriteHeight
  );
}

// 动画循环
function update() {
  tick++;

  if (moving && tick % 10 === 0) {
    legFrame++;
  }

  handleMovement();
  drawAvatar();
  requestAnimationFrame(update);
}

// 键盘事件监听
window.addEventListener('keydown', e => {
  keys[e.key.toLowerCase()] = true;
  moving = true;
});

window.addEventListener('keyup', e => {
  keys[e.key.toLowerCase()] = false;

  if (!keys['arrowup'] && !keys['arrowdown'] && !keys['arrowleft'] && !keys['arrowright']
      && !keys['w'] && !keys['a'] && !keys['s'] && !keys['d']) {
    moving = false;
    legFrame = 0;
    // 停止移动后回到穿鞋Idle帧(索引5)
    frame = 5;
  }
});

// 世界边界与TV区域限制
const tvBlockHeight = canvas.height * 0.4;
const padding = 10;

function handleMovement() {
  let nextX = x;
  let nextY = y;

  if (keys['w'] || keys['arrowup']) {
    nextY -= 2;
    direction = directions.up;
  } else if (keys['s'] || keys['arrowdown']) {
    nextY += 2;
    direction = directions.down;
  }

  if (keys['a'] || keys['arrowleft']) {
    nextX -= 2;
    direction = directions.left;
  } else if (keys['d'] || keys['arrowright']) {
    nextX += 2;
    direction = directions.right;
  }

  // X轴边界限制
  const maxX = canvas.width - spriteWidth - padding;
  if (nextX >= padding && nextX <= maxX) {
    x = nextX;
  }

  // Y轴边界限制:禁止进入TV区域(顶部40%)
  const maxY = canvas.height - spriteHeight - padding;
  const minY = tvBlockHeight + padding;
  if (nextY >= minY && nextY <= maxY) {
    y = nextY;
  }
}

let animationStarted = false;

sprite.onload = () => {
  drawAvatar();
  if (!animationStarted) {
    update();
    animationStarted = true;
  }
};

HTML代码

<div id="container">
  <div id="chat-panel">
    <div id="chat-messages"></div>
    <div class="chat-input">
      <div class="user-icon" style="background-image: url('user-ico/icon_1.jpg');"></div>
      <input type="text" id="chat-input" placeholder="Type a message..." />
    </div>
  </div>

  <div id="canvas-area">
    <canvas id="avatar-canvas" width="800" height="600" style="position:absolute; z-index:100;"></canvas>
    <img id="lounge-canvas" src="video-L.png" alt="Lounge Background" />
    <iframe width="560" height="315" src="https://www.youtube.com/embed/xolgs4-mymQ?si=KgabkxW6n-67L0oM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
  </div>

  <div id="playlist-panel">
    <div class="playlist-item" onclick="loadVideo('43gm3CJePn0')">MCR - Welcome to the Black Parade</div>
    <div class="playlist-item" onclick="loadVideo('zP2rjMx9stw')">Pierce The Veil - King For A Day</div>
    <div class="playlist-item" onclick="loadVideo('K0ibBPhiaG0')">BMTH - Can You Feel My Heart</div>
  </div>
</div>

额外提示

  • 确保my_avi.png文件路径正确,和HTML文件处于同一目录或路径设置准确。
  • 如果动画速度不合适,可以调整tick % 10中的数值,数值越小动画速度越快。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:54:53