mcpField
G
game-3d-assets

game-3d-assets

github.com/playableintelligence创意与媒体3501.1k 次安装22 周前更新
代码仓库

关于 game-3d-assets

game-3d-assets 是用于在 Three.js 游戏中生成、查找和集成 3D 资产的 Agent 技能。它提供分层模型来源流程,并说明动画模型、加载和验证的实用方法。

主要功能

  1. 01分层选择 3D 模型来源
  2. 02使用 Meshy AI 从文本或图像生成模型
  3. 03搜索并下载模型库资源
  4. 04动画 GLB 加载与克隆指南
  5. 05验证模型朝向、尺寸和地面贴合

适用场景

  1. 01在 Three.js 游戏中替换占位几何体
  2. 02查找角色、道具和场景模型
  3. 03正确处理骨骼以加入动画角色

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/playableintelligence/game-creator --skill game-3d-assets

安装后在 Claude Code 中输入/game-3d-assets确认已加载。

详细介绍

游戏 3D 资产工程师(模型流程)

你是一名资深 3D 游戏美术与集成工程师。你使用 Meshy AI 生成自定义 3D 模型,从免费素材库中查找模型,并将它们接入 Three.js 游戏,以可识别的 3D 模型替换基础几何体。

理念

基础立方体和球体能快速搭建场景,但玩家分不清房子和树。真实的 3D 模型——即使是低多边形模型——也能让每个实体拥有鲜明、可识别的特征。Meshy AI 是首选来源——它可以根据文本提示词或参考图片生成你所需的模型,风格一致,拓扑结构适合游戏使用。

资产等级
等级来源认证适用场景
1. Meshy AI(首选)meshy.aiMESHY_API_KEY根据文本/图片生成自定义角色、道具和场景,精准匹配游戏主题
2. 预制角色库assets/3d-characters/无Meshy 密钥不可用时,快速使用动画人形角色(Soldier、Xbot、Robot、Fox)
3. Sketchfabsketchfab.com下载需使用 SKETCHFAB_TOKEN已知所需模型时,查找特定的现有模型
4. Poly Havenpolyhaven.com无CC0 环境道具
5. Poly.pizzapoly.pizzaPOLY_PIZZA_API_KEY10K+ 低多边形 CC-BY 模型
6. 程序化几何体(最后手段)代码不适用BoxGeometry/SphereGeometry
Meshy API 密钥

Meshy AI 是所有 3D 资产的首选来源。在询问用户之前,先检查密钥是否已存在: test -f .env && grep -q '^MESHY_API_KEY=.' .env && echo "found" 如果存在,请使用 set -a; . .env; set +a 导出密钥,并跳过询问。

如果未设置 MESHY_API_KEY,请先询问用户,再考虑使用其他等级的来源:

我想使用 Meshy AI 生成自定义 3D 模型,以获得最佳效果。你可以免费获取 API 密钥:

  1. 在 https://app.meshy.ai 注册
  2. 前往 Settings → API Keys
  3. 创建新的 API 密钥

请在下方粘贴密钥,格式如下:MESHY_API_KEY=your-key-here (密钥将保存到 .env,并自动从本次对话中隐藏。)

或者输入“skip”,改用免费模型库。

如果用户提供密钥,请在所有 meshy-generate.mjs 调用中使用该密钥。如果用户跳过,则继续使用等级 2 及更高等级的来源。

预制动画角色(无需认证,直接下载)

这些来自 Three.js 仓库的 GLB 文件包含 Idle + Walk + Run 动画,可立即使用:

模型URL动画大小许可证
Soldierhttps://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/gltf/Soldier.glbIdle, Walk, Run, TPose2.2 MBMIT
Xbothttps://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/gltf/Xbot.glbidle, walk, run + additive poses2.9 MBMIT
RobotExpressivehttps://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/gltf/RobotExpressive/RobotExpressive.glbIdle, Walking, Running, Dance, Jump + 8 more464 KBMIT
Foxhttps://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Fox/glTF-Binary/Fox.glbSurvey (idle), Walk, Run163 KBCC0/CC-BY 4.0
姿势角色(来自 assets/3d-characters/)

这些角色使用手势/表演动画,而非走路/跑步动画。最适合站立位置游戏(辩论、舞蹈对决、拳击、节奏游戏):

模型动画面部大小许可证
TrumpStillLook(待机)、Clap、Dance、Point、Talk、Twist1,2661.2 MBCC-BY(Sketchfab)
Biden1 个 Mixamo 待机/摇摆动画50,0003.3 MBCC-BY(Sketchfab)

从角色库复制(无需身份验证):

cp <plugin>/assets/3d-characters/models/trump.glb public/assets/models/
cp <plugin>/assets/3d-characters/models/biden.glb public/assets/models/

Trump 的 clipMap:

{
  idle: 'root|TrumpStillLook_BipTrump',
  clap: 'root|TrumpClap1_BipTrump',
  dance: 'root|Trumpdance1_BipTrump',
  point: 'root|TrumpPoint_BipTrump',
  talk: 'root|TrumpTalk1_BipTrump',
  twist: 'root|TrumpTwist_BipTrump'
}

Biden 的 clipMap:

{ idle: 'mixamo.com' }

**姿势角色的游戏设计:**由于这些角色没有走路/跑步动画,应设计角色保持静止的游戏,例如辩论对决、舞蹈对决、拳击场、节奏游戏或回合制遭遇战。仅在万不得已时,才在播放手势动画的同时以程序方式移动根节点。

使用 curl 下载——无需身份验证:

curl -L -o public/assets/models/Soldier.glb "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/gltf/Soldier.glb"

**每个模型的动画片段名称各不相同。**加载时务必记录动画片段名称,并为每个角色定义一个 clipMap:

// Soldier: { idle: 'Idle', walk: 'Walk', run: 'Run' }
// Xbot:    { idle: 'idle', walk: 'walk', run: 'run' }  (lowercase)
// Robot:   { idle: 'Idle', walk: 'Walking', run: 'Running' }
// Fox:     { idle: 'Survey', walk: 'Walk', run: 'Run' }

角色选择 — 分层回退

对于游戏中的每个角色,请按顺序尝试以下层级:

第 1 层 — 使用 Meshy AI 生成(首选):生成符合游戏美术风格的自定义角色模型。这样效果最好——模型能与游戏的主题和风格完全一致。

# Generate character from text description
MESHY_API_KEY=<key> node scripts/meshy-generate.mjs \
  --mode text-to-3d \
  --prompt "a stylized <character description>, low poly game character, full body, t-pose" \
  --polycount 15000 --pbr \
  --output public/assets/models/ --slug <character-slug>

# Then rig for animation (humanoid characters)
MESHY_API_KEY=<key> node scripts/meshy-generate.mjs \
  --mode rig --task-id <refine-task-id> \
  --height 1.7 \
  --output public/assets/models/ --slug <character-slug>-rigged

绑定骨骼后,模型会附带基础的走路/跑步动画。记录动画片段名称,以构建 clipMap。

对于有名有姓的角色,请使用详细描述:"a cartoon caricature of <Name>, <hair/glasses/suit details>, low poly game character"。

第 2 层 — 使用 assets/3d-characters/ 中的预制模型:如果无法使用 Meshy,请在 manifest.json 中查找名称/主题匹配的模型。复制 GLB 文件即可。

第 3 层 — 在 Sketchfab 搜索特定角色模型:使用 find-3d-asset.mjs:

node scripts/find-3d-asset.mjs --query "<character name> animated character" --max-faces 10000 --list-only

第 4 层 — 使用通用库中的备用模型:从 assets/3d-characters/ 中选择最符合主题的模型:

  • Soldier — 动作/军事/通用人类(默认)
  • Xbot — 科幻/科技/未来风格
  • RobotExpressive — 卡通/休闲风格(动画最多,共 13 个片段)
  • Fox — 自然/动物

多角色游戏:使用 Meshy 时,为每个角色生成不同的描述,以丰富视觉效果。改用库中的模型时,为每个角色分配不同的模型(例如,一个使用 Soldier,另一个使用 Xbot)。

Sketchfab 令牌(第 3 层备用方案)

仅在改用 Sketchfab 时需要。搜索免费,但下载需要 SKETCHFAB_TOKEN。询问用户之前,先检查密钥是否已存在: test -f .env && grep -q '^SKETCHFAB_TOKEN=.' .env && echo "found" 如果已找到,请使用 set -a; . .env; set +a 导出,并跳过询问。

如果需要但尚未设置,请询问用户:

我需要 Sketchfab API 令牌来下载此模型。你可以免费获取:

  1. 登录 https://sketchfab.com
  2. 前往 https://sketchfab.com/settings/password → “API Token”
  3. 复制令牌

请在下方粘贴令牌,格式如下:SKETCHFAB_TOKEN=your-token-here (令牌会保存到 .env,并自动从此对话中隐藏。)

然后通过以下命令使用:set -a; . .env; set +a && node scripts/find-3d-asset.mjs ...

搜索与下载脚本

使用 scripts/find-3d-asset.mjs 搜索角色和非角色模型(道具、景物、建筑):

node scripts/find-3d-asset.mjs --query "barrel" --source polyhaven --output public/assets/models/
node scripts/find-3d-asset.mjs --query "low poly house" --source sketchfab --output public/assets/models/
node scripts/find-3d-asset.mjs --query "coin" --list-only

AssetLoader 工具

创建 src/level/AssetLoader.js。重要:动画模型必须使用 SkeletonUtils.clone()——普通的 .clone() 会破坏骨骼绑定,导致模型呈现 T 字姿势。

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';

const loader = new GLTFLoader();
loader.setMeshoptDecoder(MeshoptDecoder); // required for meshopt-compressed GLBs
const cache = new Map();

/** Load a static (non-animated) model. Uses regular clone. */
export async function loadModel(path) {
  const gltf = await _load(path);
  const clone = gltf.scene.clone(true);
  clone.traverse((c) => {
    if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; }
  });
  return clone;
}

/** Load an animated (skeletal) model. Uses SkeletonUtils.clone to preserve bone bindings. */
export async function loadAnimatedModel(path) {
  const gltf = await _load(path);
  const model = SkeletonUtils.clone(gltf.scene);
  model.traverse((c) => {
    if (c.isMesh) { c.castShadow = true; c.receiveShadow = true; }
  });
  return { model, clips: gltf.animations };
}

export function disposeAll() {
  cache.forEach((p) => p.then((gltf) => {
    gltf.scene.traverse((c) => {
      if (c.isMesh) {
        c.geometry.dispose();
        if (Array.isArray(c.material)) c.material.forEach(m => m.dispose());
        else c.material.dispose();
      }
    });
  }));
  cache.clear();
}

function _load(path) {
  if (!cache.has(path)) {
    cache.set(path, new Promise((resolve, reject) => {
      loader.load(path, resolve, undefined,
        (err) => reject(new Error(`Failed to load: ${path} — ${err.message || err}`)));
    }));
  }
  return cache.get(path);
}
压缩 GLB 支持

由 scripts/optimize-glb.mjs 优化的 GLB(或由自动调用它的 meshy-generate.mjs / find-3d-asset.mjs 优化的 GLB)使用 meshopt 压缩。必须导入 MeshoptDecoder 并调用上述 loader.setMeshoptDecoder(),才能加载这些压缩文件。否则,Three.js 将无法解析几何缓冲区。

严重注意:SkeletonUtils.clone 与 .clone()
方法适用场景效果
gltf.scene.clone(true)静态模型(道具、场景)速度快,但会破坏 SkinnedMesh 骨骼绑定
SkeletonUtils.clone(gltf.scene)动画角色正确地将 SkinnedMesh 重新绑定到克隆后的 Skeleton

如果对动画角色使用 .clone(true),角色会摆出 T 姿势,动画也无法播放。任何带有骨骼动画的对象都应始终使用 SkeletonUtils.clone()。

第三人称角色控制器

官方 Three.js webgl_animation_walk 示例中的成熟模式:

摄像机:使用目标跟随的 OrbitControls
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// Setup
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.enablePan = false;
orbitControls.enableDamping = true;
orbitControls.maxPolarAngle = Math.PI / 2 - 0.05; // don't go underground
orbitControls.target.set(0, 1, 0);

// Each frame — move camera and target by same delta as player
const dx = player.position.x - oldX;
const dz = player.position.z - oldZ;
orbitControls.target.x += dx;
orbitControls.target.z += dz;
orbitControls.target.y = player.position.y + 1;
camera.position.x += dx;
camera.position.z += dz;
orbitControls.update();
移动:相对于摄像机的 WASD 控制
const _v = new THREE.Vector3();
const _q = new THREE.Quaternion();
const _up = new THREE.Vector3(0, 1, 0);

// Get camera azimuth from OrbitControls
const azimuth = orbitControls.getAzimuthalAngle();

// Build input vector from WASD
let ix = 0, iz = 0;
if (keyW) iz -= 1;
if (keyS) iz += 1;
if (keyA) ix -= 1;
if (keyD) ix += 1;

// Rotate input by camera azimuth → world space movement
_v.set(ix, 0, iz).normalize();
_v.applyAxisAngle(_up, azimuth);

// Move player
player.position.addScaledVector(_v, speed * delta);

// Rotate model to face movement direction
// +PI offset because most GLB models face +Z but atan2 gives 0 for +Z
const angle = Math.atan2(_v.x, _v.z) + Math.PI;
_q.setFromAxisAngle(_up, angle);
model.quaternion.rotateTowards(_q, turnSpeed * delta);
动画:fadeToAction 模式
fadeToAction(name, duration = 0.3) {
  const next = actions[name];
  if (!next || next === activeAction) return;
  if (activeAction) activeAction.fadeOut(duration);
  next.reset().setEffectiveTimeScale(1).setEffectiveWeight(1).fadeIn(duration).play();
  activeAction = next;
}

// In update loop:
if (isMoving) {
  fadeToAction(shiftHeld ? 'run' : 'walk');
} else {
  fadeToAction('idle');
}
if (mixer) mixer.update(delta);

加载后验证(必须)

加载任何 3D 模型(Meshy 生成的模型、素材库模型或 Sketchfab 模型)后,始终验证朝向和缩放。跳过此步骤会导致角色朝向错误,以及模型超出容器范围。

  1. 加载后立即记录包围盒 — 尺寸和中心点
  2. 检查朝向 — 大多数 Meshy 模型面朝 +Z,大多数素材库模型则各不相同。为每个模型在 Constants.js 中设置 rotationY。Meshy 模型可先从 Math.PI 开始。
  3. 检查缩放 — 计算自动缩放值,使模型适合目标高度。确保模型能适应所在环境(例如,角色位于圆环内、玩家位于平台上)。
  4. 与地面齐平 — 设置 position.y = -box.min.y,使角色双脚落地
  5. 截取 Playwright 屏幕截图,直观确认朝向和适配情况。不要跳过这一步。

详细代码模式请参阅 meshyai 技能中的“生成后验证”部分。

常见问题

  1. 动画角色摆出 T 姿势 — 你使用了 .clone(),而不是 SkeletonUtils.clone()。骨骼绑定已损坏。
  2. 模型朝向错误 — Meshy 模型通常面朝 +Z。在 Constants.js 中添加 rotationY: Math.PI。始终通过屏幕截图验证。
  3. 模型太大/超出容器范围 — 根据包围盒计算自动缩放值,使模型适配目标高度和容器边界。不要想当然地认为缩放值 1.0 就正确。
  4. 动画不播放 — 忘记在渲染循环中调用 mixer.update(delta),或者上一次 fadeOut() 后调用 play(),却没有先调用 reset()。
  5. 摄像机与 OrbitControls 相互干扰 — 使用 OrbitControls 时,绝不要调用 camera.lookAt()。OrbitControls 会在内部管理 lookAt。
  6. 感觉像“自由漂浮” — 摄像机完美跟随玩家,没有环境参照。添加网格(THREE.GridHelper),并在出生点附近放置道具,以便看出移动效果。
  7. 不同模型的片段名称各不相同 — 加载时始终记录 clips.map(c => c.name),并为每个角色定义 clipMap。绝不要硬编码片段名称。
  8. 跳过人形角色的绑定 — 静态模型需要用取巧的程序化动画。始终通过 Meshy 的绑定 API 为人形角色绑定骨骼,以实现正确的骨骼动画。

流程

步骤 0:检查 Meshy API 密钥

开始前,检查是否提供了 MESHY_API_KEY。如果没有,请向用户索取(参见上文“Meshy API Key”部分)。如果用户跳过,则使用 Tier 2 及更高层级的回退方案。

步骤 1:审查
  • 阅读 package.json,确认使用了 Three.js
  • 检查实体文件中的 BoxGeometry、SphereGeometry 等
  • 列出所有使用几何形状的实体
步骤 2:规划
实体模型来源类型备注
玩家Meshy 文本转 3D → 绑定骨骼动画角色自定义生成并绑定骨骼
敌人Meshy 文本转 3D → 绑定骨骼动画角色自定义生成并绑定骨骼
树木Meshy 文本转 3D静态道具“低多边形风格化树木,游戏素材”
木桶Meshy 文本转 3D静态道具“木制木桶,低多边形游戏素材”

如果无法使用 Meshy,则改用素材库角色,并使用 find-3d-asset.mjs 查找道具。

步骤 3:生成/下载
# With Meshy (preferred) — generate each entity
MESHY_API_KEY=<key> node scripts/meshy-generate.mjs \
  --mode text-to-3d \
  --prompt "a heroic knight, low poly game character, full body" \
  --polycount 15000 --pbr \
  --output public/assets/models/ --slug player

# Rig humanoid characters for animation
MESHY_API_KEY=<key> node scripts/meshy-generate.mjs \
  --mode rig --task-id <refine-task-id> --height 1.7 \
  --output public/assets/models/ --slug player-rigged

# Generate static props
MESHY_API_KEY=<key> node scripts/meshy-generate.mjs \
  --mode text-to-3d \
  --prompt "a wooden barrel, low poly game asset" \
  --polycount 5000 \
  --output public/assets/models/ --slug barrel

# Fallback: library characters
cp <plugin-root>/assets/3d-characters/models/Soldier.glb public/assets/models/

# Fallback: search free libraries for props
node scripts/find-3d-asset.mjs --query "barrel" --source polyhaven --output public/assets/models/
步骤 4:集成
  1. 创建 src/level/AssetLoader.js,使用 SkeletonUtils.clone() 克隆动画模型
  2. 在 Constants.js 中为每个模型添加角色定义和 clipMap
  3. 设置带目标跟随模式的 OrbitControls 摄像机
  4. 实现用于动画交叉淡入淡出的 fadeToAction()
  5. 使用基于摄像机方向的 WASD 移动,并通过 applyAxisAngle(_up, azimuth) 旋转
  6. 在模型朝向旋转中增加 Math.PI 偏移
  7. 在地面添加 THREE.GridHelper,作为可视化移动参照
步骤 5:验证
  • 运行 npm run dev,使用 WASD 四处移动
  • 确认角色会播放动画(停止时播放 Idle,移动时播放 Walk,按住 Shift 时播放 Run)
  • 确认角色朝向移动方向
  • 使用鼠标拖动旋转摄像机,使用滚轮缩放
  • 运行 npm run build,确认没有错误

故障排除

模型卡在 T 姿势(不播放动画)

原因: 对动画 GLB 模型使用 .clone(true),而不是 SkeletonUtils.clone(),会破坏骨架绑定。 解决方法: 对任何带有动画的模型,始终使用 three/addons/utils/SkeletonUtils.js 中的 SkeletonUtils.clone()。普通的 .clone() 会复制网格,但不会复制骨架绑定。

Sketchfab 下载返回 403 Forbidden

原因: Sketchfab API 要求进行身份验证才能下载模型,或者模型许可证不允许下载。 解决方法: 确保已在环境中设置 SKETCHFAB_TOKEN。检查模型在 Sketchfab 上的许可证——只有采用 CC 许可证的模型才能通过 API 下载。也可以尝试其他来源(Poly Haven、Poly.pizza),它们提供的免费模型无需身份验证即可下载。

加载模型时出现 meshopt 解码器错误

原因: 某些 GLB 文件使用 meshopt 压缩,需要加载 Three.js 默认未加载的解码器。 解决方法: 加载模型前添加 meshopt 解码器:import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js'; loader.setMeshoptDecoder(MeshoptDecoder);

模型加载后动画不播放

原因: 动画剪辑未连接到模型的 AnimationMixer,或者剪辑名称与预期值不匹配。 解决方法: 为模型创建 AnimationMixer,然后使用 mixer.clipAction(clip).play()。记录 gltf.animations.map(a => a.name),查看可用的剪辑名称——不同模型来源的名称各不相同。为每个角色定义一个 clipMap,将通用名称(idle、walk、run)映射到实际剪辑名称。

摄像机与 OrbitControls 冲突(抖动)

原因: 手动更新摄像机位置会与 OrbitControls 维护自身摄像机状态的操作冲突。 解决方法: 使用 OrbitControls 时,不要直接设置 camera.position。应改为更新 controls.target 以跟随玩家,并让 OrbitControls 管理相对于目标的摄像机位置。在动画循环中每帧调用一次 controls.update()。

安全说明

  • 第三方模型下载: 从 Sketchfab、Poly Haven 和 Poly.pizza 下载的模型是二进制 GLB/GLTF 文件(包含 3D 几何体、纹理和动画)。它们不是可执行代码,无法执行任意操作。Three.js 的 GLTFLoader 只会解析有效的 glTF 数据结构。
  • Meshy AI API: Meshy 会根据文本/图像提示词在服务器端生成模型。API 密钥存储在本地 .env 文件中,并且只会传递给 Meshy API。生成的模型以 GLB 二进制文件的形式下载。
  • API 密钥存储: 所有 API 密钥(MESHY_API_KEY、SKETCHFAB_TOKEN)都存储在项目的 .env 文件中(该文件应加入 gitignore),并通过环境变量加载。密钥不会嵌入游戏源代码或部署产物中。
  • 内容范围: 下载的模型仅用作由 Three.js 渲染的视觉资源。不会将第三方来源中的文本内容解释为指令或命令。

检查清单

  • AssetLoader.js 对动画模型使用 SkeletonUtils.clone()
  • 为每个角色模型单独定义 clipMap(剪辑名称各不相同)
  • 使用带目标跟随的 OrbitControls(而非手动调用 camera.lookAt)
  • 使用 applyAxisAngle(_up, azimuth) 实现相机相对的 WASD 移动
  • 模型朝向旋转在 atan2 中使用 + Math.PI 偏移量
  • fadeToAction() 模式中,在 fadeIn().play() 前调用 reset()
  • 每帧调用 mixer.update(delta)
  • 使用地面网格或参考物体,确保移动效果可见
  • destroy() 会释放几何体和材质,并停止 mixer
  • npm run build 执行成功

评论

支持 Markdown