hytopia-assets

📁 abstrucked/hytopia-skills 📅 11 days ago
4
总安装量
2
周安装量
#52870
全站排名
安装命令
npx skills add https://github.com/abstrucked/hytopia-skills --skill hytopia-assets

Agent 安装分布

opencode 2
claude-code 2
mcpjam 1
openhands 1
zencoder 1
crush 1

Skill 文档

HYTOPIA Assets & UI

This skill helps you manage assets and UI in HYTOPIA SDK games.

Documentation: https://dev.hytopia.com/sdk-guides/assets

When to Use This Skill

Use this skill when the user:

  • Wants to load 3D models (GLTF/GLB)
  • Needs to add textures or materials
  • Asks about playing sounds or music
  • Wants to create UI elements (HUD, menus)
  • Needs to optimize asset loading
  • Asks about default SDK assets

Core Asset Concepts

Loading Models

import { Model, Entity } from 'hytopia';

class Character extends Entity {
  constructor() {
    super();
    
    this.model = new Model({
      modelUri: 'models/character.gltf',
      scale: 1.0,
      rotation: { x: 0, y: 0, z: 0 }
    });
  }
}

// Load with animation
const animatedModel = new Model({
  modelUri: 'models/player.gltf',
  animationUri: 'animations/player-idle.gltf',
  loopAnimation: true
});

Using Default Assets

import { Model, BlockType } from 'hytopia';

// Default models included with SDK
const defaultModels = {
  player: 'hytopia:models/player.gltf',
  cube: 'hytopia:models/cube.gltf',
  sphere: 'hytopia:models/sphere.gltf'
};

// Default block textures
const defaultBlocks = {
  grass: BlockType.GRASS,
  dirt: BlockType.DIRT,
  stone: BlockType.STONE,
  wood: BlockType.WOOD,
  leaves: BlockType.LEAVES
};

Custom Textures

import { BlockType, Material } from 'hytopia';

// Create block with custom texture
const customBlock = new BlockType({
  id: 'my-mod:custom-block',
  name: 'Custom Block',
  textureUri: 'textures/custom-block.png',
  material: new Material({
    roughness: 0.8,
    metalness: 0.0
  })
});

Audio

Playing Sounds

import { Audio, Entity } from 'hytopia';

class SoundEntity extends Entity {
  playSound() {
    // One-shot sound
    this.playAudio({
      uri: 'sounds/explosion.mp3',
      volume: 1.0,
      pitch: 1.0,
      spatial: true,  // 3D positional audio
      range: 20       // Audible within 20 units
    });
  }
  
  playMusic() {
    // Looping background music
    this.playAudio({
      uri: 'music/background.mp3',
      volume: 0.5,
      loop: true,
      spatial: false  // Global audio
    });
  }
}

// Play at location
world.playAudioAtPosition(
  { x: 0, y: 10, z: 0 },
  { uri: 'sounds/ambient.mp3', volume: 0.3, range: 30 }
);

Audio Management

// Stop specific sound
entity.stopAudio('sounds/explosion.mp3');

// Stop all sounds on entity
entity.stopAllAudio();

// Fade out
entity.fadeAudio('music/background.mp3', 0, 2000);  // Fade to 0 over 2 seconds

HytopiaUI

Creating UI Elements

import { Player, UI } from 'hytopia';

// Send UI to player
player.sendUI({
  type: 'panel',
  id: 'hud',
  position: { x: 0.5, y: 0.9 },  // Normalized screen position
  anchor: 'center-bottom',
  children: [
    {
      type: 'text',
      id: 'score',
      text: 'Score: 0',
      style: { fontSize: 24, color: '#ffffff' }
    },
    {
      type: 'bar',
      id: 'health',
      value: 100,
      max: 100,
      style: { 
        width: 200, 
        height: 20, 
        backgroundColor: '#333333',
        fillColor: '#ff0000'
      }
    }
  ]
});

Updating UI

// Update specific element
player.updateUI('score', {
  text: `Score: ${player.getData('score')}`
});

player.updateUI('health', {
  value: player.getData('health'),
  max: 100
});

// Remove UI
player.removeUI('hud');

Interactive UI

player.sendUI({
  type: 'panel',
  id: 'menu',
  children: [
    {
      type: 'button',
      id: 'start-btn',
      text: 'Start Game',
      onClick: 'start-game'
    },
    {
      type: 'button',
      id: 'settings-btn',
      text: 'Settings',
      onClick: 'open-settings'
    }
  ]
});

// Handle clicks
player.onUIEvent = (event) => {
  if (event.elementId === 'start-btn') {
    startGame(player);
  } else if (event.elementId === 'settings-btn') {
    openSettings(player);
  }
};

Asset Organization

Recommended Structure

assets/
├── models/
│   ├── characters/
│   ├── items/
│   └── environment/
├── textures/
│   ├── blocks/
│   ├── ui/
│   └── effects/
├── audio/
│   ├── sfx/
│   ├── music/
│   └── ambient/
└── ui/
    ├── hud.json
    ├── menu.json
    └── inventory.json

Asset Loading Strategy

import { AssetManager } from 'hytopia';

// Preload critical assets
await AssetManager.preload([
  'models/player.gltf',
  'textures/crosshair.png',
  'sounds/jump.mp3'
]);

// Load on demand
function loadLevel(levelId: string) {
  return AssetManager.loadBatch([
    `levels/${levelId}/terrain.gltf`,
    `levels/${levelId}/skybox.png`
  ]);
}

Best Practices

  1. Optimize models – Use GLB format, limit polygon count
  2. Compress textures – Use appropriate formats (PNG for UI, compressed for 3D)
  3. Audio formats – MP3 for music, WAV for short SFX
  4. Lazy loading – Load assets only when needed
  5. Reuse materials – Don’t create duplicate materials
  6. Atlas textures – Combine UI sprites into atlases

Common Patterns

Crosshair UI

player.sendUI({
  type: 'image',
  id: 'crosshair',
  position: { x: 0.5, y: 0.5 },
  anchor: 'center-center',
  source: 'textures/crosshair.png',
  style: { width: 32, height: 32 }
});

Damage Indicator

function showDamageIndicator(player: Player, damage: number) {
  player.sendUI({
    type: 'text',
    id: 'damage-text',
    position: { x: 0.5, y: 0.4 },
    anchor: 'center-center',
    text: `-${damage}`,
    style: { 
      fontSize: 36, 
      color: '#ff0000',
      fontWeight: 'bold'
    }
  });
  
  // Remove after delay
  setTimeout(() => player.removeUI('damage-text'), 1000);
}

Loading Screen

function showLoadingScreen(player: Player) {
  player.sendUI({
    type: 'panel',
    id: 'loading',
    fullScreen: true,
    style: { backgroundColor: '#000000' },
    children: [
      {
        type: 'text',
        text: 'Loading...',
        style: { fontSize: 48, color: '#ffffff' }
      },
      {
        type: 'progress',
        id: 'loading-bar',
        value: 0,
        max: 100,
        style: { width: 400, height: 20 }
      }
    ]
  });
}

function updateLoadingProgress(player: Player, percent: number) {
  player.updateUI('loading-bar', { value: percent });
  
  if (percent >= 100) {
    player.removeUI('loading');
  }
}