# Communal Hyperfy Note Pool # itsmetamike <!-- ## Hyperfy World System ### Overview Hyperfy uses a core World system as its main orchestrator. The World system manages all other systems and provides core functionality for the 3D environment. ### Key Features - Acts as the central system manager - Handles system registration and initialization - Provides access to shared resources like physics and loaders - Maintains the scene graph hierarchy ### System Registration ```javascript // Systems can be registered using world.register(SystemClass) // or previously world.systems.add(SystemClass) ``` ### Core Components - **Scene Management**: The World system maintains the Three.js scene graph - **Physics Integration**: Provides access to PhysX physics engine - **Asset Loading**: Manages asset loading through the loader system - **System Lifecycle**: Handles init and start lifecycle of registered systems ### Common Usage ```javascript // Systems can access world instance this.world.loader // Access asset loader this.world.physics // Access physics engine ``` ## Hyperfy Asset Management ### Overview Hyperfy implements a sophisticated asset management system through its loader subsystem, handling various types of 3D assets and resources. ### Asset Loading System - Located in `src/core/systems/ServerLoader.js` - Supports multiple asset types (GLB, VRM, HDR) - Implements caching for performance - Uses asset:// protocol for path resolution ### Asset Types - **GLB Models**: Primary 3D model format - **VRM Models**: For avatar/character models - **HDR**: For environment maps - **Emotes**: For character animations ### Asset Resolution ```javascript // Assets are resolved relative to configured assets directory this.assetsDir = path.join(__dirname, '../', process.env.ASSETS_DIR) // Using asset:// protocol loader.load('glb', 'asset://model.glb') ``` ### Caching - Assets are cached by type and URL - Cache is maintained in memory using Map - Prevents redundant loading of shared assets ## Hyperfy Physics System (needs work) ### Overview Hyperfy integrates the PhysX physics engine for realistic physics simulation in the 3D environment. ### Physics Components - **PhysX Integration**: Direct integration with NVIDIA PhysX - **Rigid Bodies**: Support for both static and dynamic rigid bodies - **Materials**: Customizable physics materials for friction and restitution - **Collision Shapes**: Various primitive shapes supported ### Common Physics Operations ```javascript // Creating physics materials const material = world.physics.physics.createMaterial(friction, restitution, frictionCombine) // Creating rigid bodies const body = world.physics.physics.createRigidDynamic(transform) const staticBody = world.physics.physics.createRigidStatic(transform) // Shape creation const shape = world.physics.physics.createShape(geometry, material, isExclusive, flags) ``` ### Collision Filtering - Supports collision groups and filters - Can specify which objects interact with each other - Uses PxFilterData for configuration ### Scene Integration - Physics bodies can be attached to Three.js meshes - Automatic synchronization of physics and visual representation - Scene manages physics world updates ## Hyperfy System Architecture ### Overview Hyperfy uses a modular system-based architecture where functionality is encapsulated in systems that can be registered with the world. ### System Base Class - All systems extend from base System class - Systems have defined lifecycle methods - Systems can access world instance and other systems ### System Lifecycle ```javascript class CustomSystem extends System { constructor(world) { super(world) } init() { // Initialize system } start() { // Start system after initialization } } ``` ### System Communication - Systems can access each other through world instance - Event-based communication supported - Shared state through world object ### Directory Structure ``` src/ core/ systems/ # Core system implementations World.js # World system definition apps.js # System registration ``` ### Best Practices - Systems should be self-contained - Use dependency injection through world - Follow consistent naming conventions - Implement proper cleanup in destroy methods ### System Architecture Overview #### Core Systems - World system - Physics system - Rendering system - Asset management #### System Communication - Event system - Message passing - Direct references - Shared state #### Data Flow - System updates - Event propagation - State management - Resource handling #### Performance Considerations - Update ordering - Memory management - CPU optimization - GPU utilization #### Debugging Tools - System monitors - Performance profiling - State inspection - Error tracking ## Hyperfy Rendering System ### Overview Hyperfy uses Three.js as its core rendering engine, with additional features for shadows, materials, and optimization. ### Key Features - Three.js integration - Shadow system - Material system - Scene graph management ### Scene Management ```javascript // Adding objects to scene this.base = new THREE.Group() world.scene.add(this.base) // Object traversal object.traverse((child) => { if (child.isMesh) { child.castShadow = true child.receiveShadow = true } }) ``` ### Material System - Support for standard Three.js materials - Custom material implementations - PBR material support - Material caching ### Optimization Features - Automatic LOD management - Object pooling - Scene culling - Shadow optimization ### Asset Integration - GLB/GLTF model support - VRM character support - Custom model processing - Texture management --> <!-- ## Implementing Skyboxes in Hyperfy This guide explains how to implement skyboxes in Hyperfy using the `ClientEnvironment` system. Skyboxes provide a 360-degree background environment for your virtual world. ### Overview The skybox implementation in Hyperfy involves two main components: 1. The texture loading system (`ClientLoader.js`) 2. The environment setup (`ClientEnvironment.js`) ### Texture Loading System #### Setting up the Loader In `ClientLoader.js`, we need to ensure we have the proper loaders for different texture types: ```javascript import * as THREE from 'three' import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' class ClientLoader extends System { constructor(world) { super(world) this.textureLoader = new THREE.TextureLoader() // For JPG/PNG textures this.rgbeLoader = new RGBELoader() // For HDR textures } } ``` #### Implementing Texture Loading Add support for different texture types in the `load` method: ```javascript load(type, url) { const key = `${type}/${url}` if (this.promises.has(key)) { return this.promises.get(key) } url = this.resolveURL(url) let promise if (type === 'texture') { promise = this.textureLoader.loadAsync(url).then(texture => { this.results.set(key, texture) return texture }) } if (type === 'hdr') { promise = this.rgbeLoader.loadAsync(url).then(texture => { this.results.set(key, texture) return texture }) } // ... other loaders } ``` ### Environment Setup #### Building the Skybox In `ClientEnvironment.js`, implement the skybox setup: ```javascript async buildSkybox() { try { const url = '/cyberpunk-skybox.jpg' // Path relative to public directory console.log('Loading skybox texture from:', url) const texture = await this.world.loader.load('texture', url) if (!texture) { console.error('Texture failed to load') return } // Configure texture mapping for skybox texture.mapping = THREE.EquirectangularReflectionMapping texture.colorSpace = THREE.SRGBColorSpace // Set as scene background this.world.stage.scene.background = texture } catch (error) { console.error('Error building skybox:', error) } } ``` #### HDR Environment For better lighting, you can also set up an HDR environment: ```javascript async buildHDR() { const url = '/environment.hdr' const texture = await this.world.loader.load('hdr', url) texture.mapping = THREE.EquirectangularReflectionMapping this.world.stage.scene.environment = texture await this.buildSkybox() } ``` ### Asset Requirements 1. **Skybox Texture**: - Place in `src/client/public/` - Use equirectangular projection format - JPG or PNG format for basic skyboxes - HDR format for high dynamic range environments 2. **File Structure**: ``` src/ client/ public/ cyberpunk-skybox.jpg # Your skybox texture environment.hdr # Optional HDR environment map ``` ### Tips and Best Practices 1. **Texture Format**: - Use JPG/PNG for simple skyboxes - Use HDR for better lighting and reflections - Ensure textures are in equirectangular projection format 2. **Performance**: - Keep texture sizes reasonable (2048x1024 is often sufficient) - Consider using compressed texture formats for better loading times 3. **Debugging**: - Check console logs for loading errors - Verify texture paths are correct relative to public directory - Ensure textures are properly formatted for equirectangular mapping ### Common Issues 1. **Texture Not Loading**: - Verify file exists in public directory - Check file permissions - Ensure correct loader type is being used ('texture' vs 'hdr') 2. **Distorted Skybox**: - Confirm texture is in equirectangular projection format - Check texture mapping settings - Verify texture dimensions (should be 2:1 ratio) 3. **Missing Reflections**: - HDR environments provide better reflections than standard textures - Ensure `scene.environment` is set for HDR maps --> # hiroP ## Really basic: adding mesh to your world To add a mesh to your world you can edit `src/core/systems/ClientEnvironment.js` In there just add something like: ``` const hiroLogo = await this.world.loader.load('glb', 'asset://hiroLogo01.glb') const hiroRoot = hiroLogo.toNodes() hiroRoot.activate({ world: this.world, physics: true }) hiroRoot.position.set(0, 10, 10) // Adjust x,y,z as needed ``` to ` async start()` The asset in the above will be accessible in whatever directory you set in the `.env` ## Adding collisions to meshes ### Basic collider There are a few occassions when you might want a simple collision but usually you'll need to structure your object with a hierarchy like below in the [#Dynamic-collider](#Dynamic-collider) section. Let's imagine you do actually want a simple sphere to act as a collider. You would create the following hierarchy in your Blender object: ``` MyBall (Empty) ├── BallMesh (Detailed sphere if you need one) └── PhysicsSphere (Simple sphere mesh) └── Custom Properties: node = "rigidbody" type = "dynamic" node = "collider" convex = true ``` To set the custom properties you select your mesh (simple sphere in this example) and scroll down to the bottom and add a new custom property like this: ![blender_hBt5JNVUNI](https://hackmd.io/_uploads/HJmzqoXPkx.png) - click OK and then set the value to 'collider' so it looks like this: ![blender_nEzAWBJJCu](https://hackmd.io/_uploads/rkQLcj7Dyx.png) If you want to create a fancy ball (eg one with football panels/decals etc. you'd add that as a sibling of PhysicsSphere so that it isn't used in the physics calculations. For anything more complicated that this you will want to separate the collision and rigidbody meshes as shown in the next section. ### Dynamic collider To create a collider that responds to physics you need to create an object with the following structure: ``` MyDynamicObject (Empty/Group) ├── DetailedMesh (Complex mesh - what the thing looks like) └── RigidBody (Empty or minimal mesh) └── Custom Properties: node = "rigidbody" type = "static" | "kinematic" | "dynamic" mass = [number value >= 0] (see note below) tag = [optional string, cannot be "player"] └── CollisionShape (Simplified collision mesh) └── Custom Properties: node = "collider" convex = [true/false] ``` _Note: mass is not currently supported so just leave it out - it will be set to a default of 1._ #### RigidBody types Looking at the RigidBody.js code, there are three types defined: ```javascript const types = ['static', 'kinematic', 'dynamic'] ``` Here's what each does: 1. `static` - For immovable objects like walls, floors, or fixed furniture. The code shows these create a `createRigidStatic` PhysX actor which: - Won't move when hit - Other objects will collide with it - Most efficient performance-wise 2. `dynamic` - For objects that should move realistically with physics. Creates a `createRigidDynamic` PhysX actor that: - Will respond to gravity - Will bounce/collide with other objects - Will be pushed by forces - Uses the mass property for physics calculations 3. `kinematic` - For objects that move but aren't affected by physics. Also creates a `createRigidDynamic` actor but sets the `KINEMATIC` flag: - Can be moved programmatically - Other objects will collide with it - Won't respond to gravity or forces - Mass is less important since it's not physics-driven So for your example - if you want the object to fall and bounce realistically, use `type="dynamic"`. If it should stay perfectly still, use `type="static"`. Use `kinematic` if you want to move it programmatically but still have other objects collide with it. # peezy ## Deploying Your Hyperfy World This guide will walk you through deploying your Hyperfy world on a VPS. Don't worry if you're new to server deployment - we'll go through it step by step! ### Initial VPS Setup Before connecting to your VPS, you need to upload an SSH key to your provider's platform. ```bash # Generate an SSH key if you don't have one ssh-keygen -t ed25519 -C "your_email@example.com" # Display your public key cat ~/.ssh/id_ed25519.pub # On Mac/Linux # or type $env:USERPROFILE\.ssh\id_ed25519.pub # On Windows PowerShell ``` Copy the entire key and add it to your VPS provider's dashboard. ### Setting Up Your Server #### 1. Initial Connection ```bash # Connect to your VPS as root ssh root@your_server_ip ``` #### 2. Create a Deployment User ```bash # Create new user (replace YOUR_USERNAME with your preferred username) adduser YOUR_USERNAME usermod -aG sudo YOUR_USERNAME # Set up SSH for the new user mkdir -p /home/YOUR_USERNAME/.ssh cp ~/.ssh/authorized_keys /home/YOUR_USERNAME/.ssh/ chown -R YOUR_USERNAME:YOUR_USERNAME /home/YOUR_USERNAME/.ssh chmod 700 /home/YOUR_USERNAME/.ssh chmod 600 /home/YOUR_USERNAME/.ssh/authorized_keys # Exit and reconnect as your user exit ssh YOUR_USERNAME@your_server_ip ``` #### 3. Secure the SSH Configuration After verifying you can log in as your user, secure the SSH service: ```bash # Make a backup of the original configuration sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak # Edit the SSH configuration sudo nano /etc/ssh/sshd_config ``` Make the following changes in the configuration file: ```bash # Disable root login PermitRootLogin no # Disable password authentication PasswordAuthentication no # Enable public key authentication PubkeyAuthentication yes # Specify which users can connect via SSH AllowUsers YOUR_USERNAME # Optional but recommended: Limit SSH access attempts MaxAuthTries 3 ``` Apply the changes: ```bash # Test the configuration for syntax errors sudo sshd -t # Restart the SSH service sudo systemctl restart sshd ``` Important: Before closing your current SSH session: 1. Open a new terminal 2. Try to connect with the new configuration 3. Make sure it works before closing your working session If something goes wrong, you can restore the backup: ```bash sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config sudo systemctl restart sshd ``` #### 4. Install Node.js Via NVM ```bash # Install required build tools sudo apt update sudo apt install -y curl git build-essential # Install NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash # Load NVM (or reconnect to your server) export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # Install required Node version (22.11.0+) nvm install 22 # Set as default nvm alias default 22 ``` #### 4. Install PM2 and Nginx ```bash # Install PM2 globally npm install -g pm2 # Install Nginx sudo apt install -y nginx ``` ### Deploying Your Hyperfy World #### 1. Clone and Setup Your World ```bash # Go to your home directory cd ~ # Clone the repository git clone https://github.com/hyperfy-xyz/hyperfy.git my-world cd my-world # Copy environment file cp .env.example .env # Install dependencies npm install ``` #### 2. Configure Environment Variables change the environment variables to point to your domain url ```bash # Edit your environment file nano .env ``` ```bash PUBLIC_WS_URL=https://YOUR_DOMAIN.com/ws PUBLIC_API_URL=https://YOUR_DOMAIN.com/api PUBLIC_ASSETS_URL=https://YOUR_DOMAIN.com/assets ``` #### 3. Start Your Application with PM2 ```bash # Start the application pm2 start npm --name "hyperfy" --interpreter bash -- start # Set PM2 to start on system boot pm2 startup pm2 save ``` #### 4. Configure Nginx as Reverse Proxy ```bash # Remove default config sudo rm /etc/nginx/sites-enabled/default # Create new config (replace yourdomain.com with your actual domain) sudo nano /etc/nginx/sites-available/yourdomain.com ``` Add this configuration: ```nginx server { listen 80; server_name your-domain.com; # Change to your domain location / { proxy_pass http://localhost:3000; # Change port if different proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # WebSocket support proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } ``` Enable and start Nginx: ```bash # Enable the site sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/ # Test configuration sudo nginx -t # Start Nginx sudo systemctl restart nginx ``` #### 5. Configure Your Domain's DNS Before enabling HTTPS, you need to point your domain to your server: 1. Go to your domain registrar's website (like Namecheap, GoDaddy, etc.) 2. Find the DNS settings or DNS management section 3. Add or modify the following records: ``` Type | Name | Value A | @ | your_server_ip CNAME | www | your_domain ``` 4. Save your changes DNS changes can take anywhere from a few minutes to 48 hours to propagate. You can check the propagation by going on https://dnschecker.org/ and inserting your domain. it should point to your server ip #### 6. Set Up HTTPS with Certbot Let's secure your site with a free SSL certificate from Let's Encrypt: ```bash # Install Certbot and its Nginx plugin sudo apt install certbot python3-certbot-nginx # Obtain and install the certificate sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # When prompted: # 1. Enter your email address # 2. Agree to terms of service # 3. Choose whether to redirect HTTP to HTTPS (recommended) ``` Certbot will automatically modify your Nginx configuration to use HTTPS. It also sets up automatic renewal of your certificates. You can verify that the SSL certificate is set to auto-renew by checking the Certbot timer ```bash sudo systemctl status certbot.timer ``` #### 7. Configure Firewall ```bash # Allow SSH, HTTP, and HTTPS sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' # Enable firewall sudo ufw enable ``` ### Updating Your World When you need to update your world: ```bash # Go to your world directory cd ~/my-world # Pull latest changes git pull # Install any new dependencies npm install # Restart the application pm2 restart hyperfy ``` ### Monitoring and Troubleshooting #### View Application Status ```bash # Check status pm2 status # View logs pm2 logs hyperfy # Monitor CPU/Memory usage pm2 monit ``` #### Check Nginx Status ```bash # View Nginx status sudo systemctl status nginx # Check error logs sudo tail -f /var/log/nginx/error.log ``` #### Common Issues 1. Port 3000 already in use: ```bash # Find what's using the port sudo lsof -i :3000 # Kill the process sudo kill -9 <PID> ``` 2. Node version issues: ```bash # Verify Node version node -v # Switch version if needed nvm use 22 ``` 3. Permission issues: ```bash # Fix ownership if needed sudo chown -R YOUR_USERNAME:YOUR_USERNAME ~/my-world ``` 4. SSL Certificate issues: ```bash # Check certificate status sudo certbot certificates # Renew certificates manually if needed sudo certbot renew # View Certbot logs sudo tail -f /var/log/letsencrypt/letsencrypt.log ``` Remember to check your application logs (`pm2 logs`) for any specific error messages if you encounter issues. Most problems can be diagnosed through the logs! # JoelPlatoon ## Hyperfy Meets IQ6900: A Scalable & Cost-Efficient Storage Solution **IQ6900's Code-In Technology** provides a revolutionary approach to blockchain-based storage, enabling efficient, secure, and cost-effective solutions for managing 3D assets. Its unique ability to attach to any blockchain makes it a versatile choice for Hyperfy's infrastructure. ### Key Features: - **Storage Efficiency**: Significantly reduces on-chain storage costs compared to traditional methods. - **Cross-Chain Compatibility**: Works seamlessly with Ethereum, Solana, and other blockchains. - **Security**: Supports encryption, chunking, and on-chain metadata for robust asset management. ### GitHub Repository: - [Code-In for Ethereum](https://github.com/zo-eth/code-in-for-eth) This plugin can be integrated into Hyperfy to store 3D assets more efficiently, leveraging [IQ6900's](https://iq6900.com/) innovative technology. ## Setting up Code/Test Environment ## 1. Smart Contract Deployment - Ensure the `code-in-for-eth` smart contract is properly deployed on Ethereum with the correct ABI and address. - Verify that the `storeMetadata` and `getMetadata` functions in the contract work as intended. --- ## 2. Library Installation Install all required libraries before running the code: ``` npm install web3 axios crypto fs ``` ## 3. Environment Variables Use environment variables (.env) to securely store sensitive data like private keys and API keys. Example .env setup: ``` INFURA_PROJECT_ID=<your-infura-id> PRIVATE_KEY=<your-private-key> ENCRYPTION_KEY=<your-encryption-key> ``` ## 4. Input Validation Validate all user inputs and file paths to ensure no malformed data is processed. Use libraries like path to sanitize file paths. ## 5. Chunk Size Optimization Set an optimal chunk size based on IQ6900’s storage capacity and retrieval speeds. Test the chunking process with different sizes (e.g., 10 KB, 100 KB). ## 6. Ethereum Gas Handling Ensure sufficient ETH in the sender’s account to cover gas fees for transactions. Use a gas price oracle to dynamically adjust gas prices. ## 7. Security Measures ### Encryption Use AES-256 for encrypting assets. Ensure the encryption key is long and stored securely. ### Private Key Handling Never hardcode private keys. Use environment variables or secure vaults (e.g., AWS Secrets Manager). ### Access Control Ensure storeMetadata and getMetadata functions on the Ethereum contract enforce proper access controls. ## 8. Error Handling Add comprehensive try-catch blocks to handle errors gracefully and log issues for debugging. ## 9. Testing Test the integration end-to-end with small assets before scaling up to larger files. Simulate edge cases (e.g., network failure, insufficient funds). ## Code Example ``` require('dotenv').config(); const Web3 = require('web3'); const axios = require('axios'); const crypto = require('crypto'); const fs = require('fs'); // Initialize Ethereum connection const web3 = new Web3(`https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`); // Ethereum smart contract configuration const contractABI = [ /* ABI from code-in-for-eth */ ]; const contractAddress = '0xYourContractAddress'; const contract = new web3.eth.Contract(contractABI, contractAddress); // Utility functions function compressData(data) { return data; } // Placeholder function decompressData(data) { return data; } // Placeholder function encryptData(data, encryptionKey) { const cipher = crypto.createCipher('aes-256-cbc', encryptionKey); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); return encrypted; } function decryptData(data, encryptionKey) { const decipher = crypto.createDecipher('aes-256-cbc', encryptionKey); let decrypted = decipher.update(data, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } function splitIntoChunks(data, chunkSize) { const chunks = []; for (let i = 0; i < data.length; i += chunkSize) { chunks.push(data.slice(i, i + chunkSize)); } return chunks; } // Main Plugin Class class IQ6900HyperfyPlugin { constructor() { this.encryptionKey = process.env.ENCRYPTION_KEY; this.privateKey = process.env.PRIVATE_KEY; } async uploadAsset(filePath, userAddress) { // Implementation here } async retrieveAsset(metadataTxid) { // Implementation here } } // Example Usage (async () => { const plugin = new IQ6900HyperfyPlugin(); const userAddress = '0xYourEthereumAddress'; const filePath = './path/to/3d-model.glb'; const metadataTxid = await plugin.uploadAsset(filePath, userAddress); console.log('Metadata TxID:', metadataTxid); const assetData = await plugin.retrieveAsset(metadataTxid); console.log('Retrieved Asset Data:', assetData); })(); ``` ## Features ## Secure Data Handling * Encryption: Protects 3D asset data during storage and retrieval. * Compression: Reduces file size to save costs and improve speed. ## Chunked Uploads * Splits large assets into manageable 10 KB chunks for efficient storage and retrieval. ## On-Chain Metadata * Stores chunk references (TxIDs) on Ethereum using the code-in-for-eth contract. ## Seamless Retrieval * Retrieves all chunks from IQ6900, decrypts, decompresses, and reassembles the asset. ## Ethereum Integration * Interacts with Hyperfy’s Ethereum environment for secure metadata management. ## Cost Comparison: Ethereum vs. IQ6900 (Solana) | **Asset Size** | **Ethereum Cost (USD)** | **IQ6900 Cost (USD)** | **Savings (%)** | |----------------|--------------------------|------------------------|------------------| | 500 KB | $1,285.79 | $0.3339 | ~99.97% | | 2 MB | $5,143.14 | $1.3354 | ~99.97% | | 10 MB | $25,715.70 | $6.677 | ~99.97% | ## Insights on Cost Savings ### 🚀 **Massive Cost Savings** - **IQ6900** reduces costs by **~99.97%** compared to Ethereum storage. --- ### 💾 **Efficient Storage Solution** - Storing a **10 MB file**: - **IQ6900**: **$6.68** - **Ethereum**: **$25,715.70** --- ### 🔗 **Interoperable Architecture** - **Metadata on Ethereum**: Store critical metadata for compatibility. - **Assets on IQ6900**: Offload large 3D assets (e.g., `.glb` files) for efficient, secure, and scalable storage. --- ### 📈 **Scalable for Hyperfy** - Enables Hyperfy to: - Store assets **affordably**. - Maintain **fast access** and **high reliability**. # b0gie ## BuildMode System Documentation ### Overview BuildMode is a specialized camera control system for Hyperfy that enables free-flying camera movement for building and world editing. When activated, it temporarily disables player controls and provides smooth, unrestricted camera movement throughout the world. ### Features #### Camera Controls - **Activation**: Press `B` to toggle BuildMode - **Movement**: - `W/A/S/D`: Move camera forward/left/backward/right - `Space`: Move camera up - `C`: Move camera down - `Mouse`: Look around (smooth camera rotation) - `Mouse Wheel`: Adjust movement speed #### Speed Control - Default movement speed: 10 units/second - Speed range: 1 to 50 units/second - Scroll up: Decrease speed - Scroll down: Increase speed - Speed adjustment factor: 1.2x per scroll tick #### Player State Management When BuildMode is activated: - Player model is hidden - Player animations are frozen - Player physics are locked - Player position is preserved - Player controls are disabled ### Technical Implementation #### Core Components ##### Camera System ```javascript // Camera initialization this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) // Smooth rotation handling this.targetRotation = new THREE.Euler(0, 0, 0, 'YXZ') this.currentRotation = new THREE.Euler(0, 0, 0, 'YXZ') ``` ##### Movement System The movement system uses quaternion-based directional movement for smooth camera control: ```javascript moveForward(distance) { const direction = new THREE.Vector3(0, 0, -1) direction.applyQuaternion(this.camera.quaternion) this.camera.position.addScaledVector(direction, distance) } ``` #### State Management ##### Activation Process 1. Store original camera and player states 2. Disable player controls and visibility 3. Enable free camera movement 4. Lock player physics 5. Enable pointer lock for smooth mouse control ```javascript toggleBuildMode() { if (this.active) { // Store states this.originalCameraPosition.copy(this.world.camera.position) this.originalCameraQuaternion.copy(this.world.camera.quaternion) // Disable player player.control.camera.unclaim() player.control.priority = -1 player.visible = false // Enable build mode camera this.control.camera.claim() } // ... } ``` ### Integration Guide #### Adding BuildMode to Your World 1. Register the BuildMode system with your world: ```javascript import { BuildMode } from './systems/BuildMode' // In your world creation: world.addSystem(BuildMode) ``` 2. Ensure proper control priorities are set: ```javascript // In ControlPriorities.js export const ControlPriorities = { EDITOR: 100, // BuildMode priority PLAYER: 0 // Normal player priority } ``` #### Event Handling BuildMode integrates with Hyperfy's event system: - Listens for key press events (B key for toggle) - Handles mouse movement for camera rotation - Processes scroll events for speed adjustment ### Best Practices 1. **State Preservation** - Always store original states before modification - Restore all states when deactivating - Handle edge cases (e.g., disconnection while in BuildMode) 2. **Performance** - Use smooth interpolation for camera movement - Implement proper cleanup in the destroy method - Manage event listeners carefully 3. **User Experience** - Provide smooth camera movement - Implement intuitive controls - Maintain consistent behavior across different scenarios ### Common Issues and Solutions 1. **Player Visibility** ```javascript // Problem: Player still visible in BuildMode // Solution: Properly hide both avatar and base if (player.avatar) { player.avatar.visible = false player.avatar.mixer.stopAllAction() } if (player.base) { player.base.visible = false } ``` 2. **Camera Rotation** ```javascript // Problem: Camera flipping at extreme angles // Solution: Clamp rotation values this.targetRotation.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.targetRotation.x)) ``` ### Future Enhancements Potential improvements to consider: 1. Grid system for precise placement 2. Snap-to-surface functionality 3. Object selection and manipulation 4. Multiple camera modes (orbit, first-person, etc.) 5. Customizable key bindings 6. Save/restore camera positions ### Contributing When contributing to BuildMode: 1. Maintain the existing control scheme 2. Test edge cases thoroughly 3. Document any changes or additions 4. Ensure smooth state transitions 5. Consider performance implications ### Related Systems - Player Control System - Camera Control System - Physics System - Input Management System ## First-Person Camera Implementation ### Overview This document outlines the implementation of a first-person camera system in Hyperfy, including camera positioning, rotation handling, and smooth transitions between first and third-person modes. ### Key Features - Toggle between first and third-person modes using 'C' key - Camera positioned at eye level in first-person mode - Smooth transitions between camera modes - Proper collision handling and clipping prevention - Consistent interaction distances in both modes ### Implementation Details ### Camera Configuration ```javascript this.firstPersonCam = { position: new THREE.Vector3(), quaternion: new THREE.Quaternion(), rotation: new THREE.Euler(0, 0, 0, 'YXZ'), offset: new THREE.Vector3(0, 1.65, -0.7), // Eye level height and forward offset targetPosition: new THREE.Vector3(), targetQuaternion: new THREE.Quaternion(), lerpSpeed: 15 } ``` ### Key Components 1. **Camera Mode Toggle** - Activated by pressing 'C' - Handles zoom and camera position transitions - Preserves VRM first-person settings 2. **Position Updates** - Camera positioned at eye level (1.65 units up) - Forward offset (-0.7 units) to prevent clipping - Smooth position transitions using lerp 3. **Rotation Handling** - Separate rotation limits for first-person (60 degrees) and third-person (90 degrees) - Base rotation only affected by left/right movement - Head bone rotation follows camera in first-person mode 4. **Interaction System** - Consistent interaction distances in both modes - Rig position updated to match base position - Maintains proper 'E' action functionality ### Code Examples #### Camera Mode Toggle ```javascript if (code === 'KeyC') { this.isFirstPerson = !this.isFirstPerson if (this.isFirstPerson) { this.normalZoom = this.cam.zoom this.cam.zoom = 0 this.control.camera.zoom = 0 } else { this.cam.zoom = this.normalZoom this.control.camera.zoom = this.normalZoom } } ``` #### Position Updates ```javascript // First-person camera position update activeCam.targetPosition.copy(this.base.position) activeCam.targetPosition.y += this.firstPersonCam.offset.y const forward = new THREE.Vector3(0, 0, this.firstPersonCam.offset.z) forward.applyQuaternion(this.base.quaternion) activeCam.targetPosition.add(forward) ``` #### Rotation Updates ```javascript if (this.isFirstPerson) { activeCam.rotation.x = clamp(activeCam.rotation.x, -this.firstPersonRotationLimit, this.firstPersonRotationLimit) if (this.headBone) { this.headBone.rotation.set(0, 0, 0) this.headBone.rotation.x = activeCam.rotation.x } const baseRotation = new THREE.Euler(0, activeCam.rotation.y, 0, 'YXZ') this.base.quaternion.setFromEuler(baseRotation) } ``` ### Best Practices 1. Always update the rig position to match the base position for consistent interactions 2. Use smooth transitions for camera movements to prevent jarring changes 3. Implement proper collision checks to prevent camera clipping 4. Maintain separate rotation limits for first and third-person modes 5. Update head bone rotation to match camera rotation in first-person mode ### Known Considerations - Camera position needs careful tuning to prevent seeing inside the character model - Rotation limits should be adjusted based on the desired level of head movement - Interaction distances should be consistent across both camera modes - VRM first-person settings should be respected for proper model rendering ## Enhanced App Inspection System This guide explains how to add position/rotation controls and a freeze toggle to Hyperfy's app inspection system. ### Implementation Steps #### 1. Update InspectPane.js First, modify `src/client/components/InspectPane.js` to add the new fields: ```javascript // Add state for transform values function Fields({ app, blueprint }) { const [position, setPosition] = useState(app.root?.position || new THREE.Vector3()) const [rotation, setRotation] = useState(app.root?.rotation || new THREE.Euler()) const [frozen, setFrozen] = useState(app.frozen || false) // Add live updates useEffect(() => { const onUpdate = () => { if (app.root) { setPosition(app.root.position.clone()) setRotation(app.root.rotation.clone()) } setFrozen(app.frozen) } onUpdate() app.on('update', onUpdate) return () => app.off('update', onUpdate) }, [app]) // Add transform fields to the UI const transformFields = [ { type: 'section', key: 'transform', label: 'Transform', }, { type: 'vector3', key: 'position', label: 'Position', value: position, }, { type: 'euler', key: 'rotation', label: 'Rotation', value: rotation, }, { type: 'switch', key: 'frozen', label: 'Freeze', value: frozen, options: [ { value: true, label: 'Frozen' }, { value: false, label: 'Unfrozen' } ] }, ...fields ] } ``` #### 2. Add Transform Handlers Add these handlers to the `modify` function in `Fields`: ```javascript const modify = (key, value) => { if (config[key] === value) return // Position updates if (key === 'position' && app.root) { app.root.position.copy(value) app.data.position = value.toArray() world.network.send('entityModified', { id: app.data.id, position: app.data.position }) if (app.networkPos) { app.networkPos.pushArray(app.data.position) } setPosition(value.clone()) return } // Rotation updates if (key === 'rotation' && app.root) { app.root.rotation.copy(value) const quaternion = new THREE.Quaternion().setFromEuler(value) app.data.quaternion = quaternion.toArray() world.network.send('entityModified', { id: app.data.id, quaternion: app.data.quaternion }) if (app.networkQuat) { app.networkQuat.pushArray(app.data.quaternion) } setRotation(value.clone()) return } // Freeze updates if (key === 'frozen') { app.frozen = value world.network.send('entityModified', { id: app.data.id, frozen: value }) setFrozen(value) return } } ``` #### 3. Update App.js Modify `src/core/entities/App.js` to handle frozen state: ```javascript export class App extends Entity { constructor(world, data, local) { super(world, data, local) // Add frozen state this.frozen = data.frozen || false } modify(data) { let rebuild // Handle frozen state if (data.hasOwnProperty('frozen')) { this.frozen = data.frozen this.data.frozen = data.frozen if (this.frozen && this.data.mover) { this.data.mover = null rebuild = true } } // Block position/rotation updates when frozen if (data.hasOwnProperty('position') && !this.frozen) { this.data.position = data.position this.networkPos.pushArray(data.position) } if (data.hasOwnProperty('quaternion') && !this.frozen) { this.data.quaternion = data.quaternion this.networkQuat.pushArray(data.quaternion) } if (rebuild) { this.build() } } move() { // Block movement when frozen if (this.frozen) return this.data.mover = this.world.network.id this.build() world.network.send('entityModified', { id: this.data.id, mover: this.data.mover }) } } ``` ### Features - Direct numeric input for position/rotation - Freeze toggle to prevent movement - Real-time network sync - Works with existing movement system #### Usage 1. Right-click an app to open inspect pane 2. Use numeric inputs for precise positioning 3. Toggle freeze to lock in place 4. Changes sync across network automatically ### Testing 1. Verify position/rotation inputs update in real-time 2. Check freeze prevents all movement 3. Confirm changes sync to other clients 4. Test interaction with right-click move system ## Door > A powerful HyperScript implementing a door for hyperfy ### 📋 Implementation Overview ### Core State Variables | State Variable | Purpose | |---------------|---------| | `isOpen` | Tracks whether the door is currently open or closed | | `isMoving` | Indicates if the door is in motion | | `currentPosition` | Current door position (0 = closed, 1 = open) | | `targetPosition` | Target door position (0 = closed, 1 = open) | | `openTimer` | Tracks duration before auto-closing | ### Configuration Options ```json { "type": "sliding", // or "swinging" "direction": "outward", // or "inward" "slideDistance": 0.65, "maxRotation": 45 } ``` ### 🎮 Door Components it is important to match the names of the components in your blender scene to the names in the code. - `doorFrame`: Main door structure - `doorL`: Left door component - `doorR`: Right door component ![](https://imgur.com/gniojmv.png) ### ⚙️ Configuration UI #### Available Settings - **Door Type** - Sliding (default) - Swinging - **Slide Distance** - Default: 1.8 units - **Max Rotation** - Default: 45 degrees - **Direction** - Inward - Outward ### 🔄 Door Types #### 1. Sliding Doors FUTURE MODE - Horizontal movement along X-axis - Configurable slide distance - Smooth animation system #### 2. Swinging Doors SALOON MODE - Rotation-based movement - Adjustable maximum angle - Direction control ### 🎯 Interaction System 1. Trigger action appears above door frame 2. Players can toggle door state 3. Action label updates dynamically 4. Auto-close activates after 3 seconds 5. Smooth animation transitions ### 🔧 Technical Notes - Uses delta time for consistent animations - Value clamping for stability - Automatic state management - Configurable through simple JSON > **Pro Tip**: All numerical values are automatically clamped to prevent unexpected behavior. #### 🚪 Setting Up Your Door in Blender Proper setup in Blender is crucial for this script to function correctly. Follow these steps carefully: ##### 0. 🔍 Figure out the door structure (important) To achieve the door animation, you need to have a good hierarchy and naming of your rigid body components. In the example object the door is comprised of 4 parts: - Empty: The parent object of the door. - FrameRigidBody: The main structure of the door. - FrameCollider: The collider for the door frame - FrameMesh: The mesh for the door frame - LeftDoorRigidBody: The left panel of the door. - LeftDoorCollider: The collider for the left door. - LeftDoorMesh: The mesh for the left door. - RightDoorRigidBody: The right panel of the door. - RightDoorCollider: The collider for the right door. - RightDoorMesh: The mesh for the right door. ![](https://i.imgur.com/rQ1Sc5u.png) --- ##### 1. 🏗️ Door Frame (Rigid Body / static) - **Name**: `Frame` - **Description**: This is the main structure of your door. It should be a static object that serves as the parent for all door components. - **Positioning**: Place it where you want your door to appear in the environment. --- ##### 2. ⬅️ Create the Left Door (Rigid Body / kinematic) - **Name**: `LeftDoor` - **Description**: This is the left panel of your door. It should be a child of the `Empty` object. --- ##### 3. ➡️ Create the Right Door (Rigid Body / kinematic) - **Name**: `RightDoor` - **Description**: This is the right panel of your door. It should also be a child of the `Empty` object. --- ##### 4. 💾 Export Your Meshes - Export your entire setup (Empty, Frame, LeftDoor, RightDoor) as a single .glb file for use in hyperfy. - Ensure all names match exactly as specified (`Empty`, `Frame`, `LeftDoor`, `RightDoor`). ## Implementing Double Jump in Hyperfy This guide explains how to add a double jump feature with a flip animation to your Hyperfy world. ### 1. Create the Double Jump System Create a new file `src/core/systems/DoubleJump.js`: ```javascript import { System } from './System' import * as THREE from '../extras/three' import { Emotes, emotes } from '../extras/playerEmotes' export class DoubleJump extends System { constructor(world) { super(world) this.lastJumpTime = 0 this.DOUBLE_JUMP_FORCE = 9.75 // 1.5x the base jump force this.isDoubleJumping = false } async init({ loadPhysX }) { // Preload the flip animation await this.world.loader.load('emote', emotes[Emotes.DOUBLE_JUMP]) } start() { // Bind to space key with priority 0 this.control = this.world.controls.bind({ priority: 0, onPress: code => this.handleKeyPress(code) }) } getLocalPlayer() { return Array.from(this.world.entities.items.values()) .find(entity => entity.isPlayer && entity.constructor.name === 'PlayerLocal') } update() { const localPlayer = this.getLocalPlayer() if (!localPlayer) return // Handle animation timing if (this.isDoubleJumping) { const timeSinceLastJump = performance.now() - this.lastJumpTime if (timeSinceLastJump > 400) { // Switch back to float after flip completes localPlayer.emote = Emotes.FLOAT } if (timeSinceLastJump > 800) { this.isDoubleJumping = false } } // Reset on landing if (localPlayer.grounded) { this.isDoubleJumping = false this.lastJumpTime = 0 } } handleKeyPress(code) { if (code !== 'Space') return false const localPlayer = this.getLocalPlayer() if (!localPlayer) return false // Only double jump if: // 1. In the air (jumping/falling) // 2. Not already double jumping // 3. Not grounded if (!localPlayer.grounded && !this.isDoubleJumping && (localPlayer.jumping || localPlayer.falling)) { // Apply upward force const currentVel = localPlayer.capsule.getLinearVelocity() v1.copy(currentVel) v1.y = this.DOUBLE_JUMP_FORCE localPlayer.capsule.setLinearVelocity(v1.toPxVec3()) // Start flip animation this.isDoubleJumping = true this.lastJumpTime = performance.now() localPlayer.emote = Emotes.DOUBLE_JUMP // Sync animation with other players this.world.network.send('entityModified', { id: localPlayer.data.id, p: localPlayer.base.position.toArray(), q: localPlayer.base.quaternion.toArray(), e: Emotes.DOUBLE_JUMP, }) return true } return false } } ``` ### 2. Add the Double Jump Emote Add the following to `src/core/extras/playerEmotes.js`: ```javascript export const Emotes = { IDLE: 0, WALK: 1, RUN: 2, FLOAT: 3, DOUBLE_JUMP: 4, // Add this line // ... other emotes } export const emotes = { 0: 'asset://emote-idle.glb', 1: 'asset://emote-walk.glb', 2: 'asset://emote-run.glb', 3: 'asset://emote-float.glb', 4: 'asset://emote-flip.glb', // Add this line // ... other emotes } ``` ### 3. Register the System Add this to your world creation (usually in `createClientWorld.js`): ```javascript import { DoubleJump } from './systems/DoubleJump' // In your world creation: world.register(DoubleJump) ``` ### Features The double jump system provides: - Double jump ability by pressing space while in the air - Front flip animation during the double jump - Network synchronization for multiplayer - Automatic transition back to floating animation - State reset upon landing ### Technical Details - Double jump force is 1.5x the normal jump force (9.75) - Flip animation plays for 400ms before transitioning to float - Double jump state is tracked for 800ms to prevent additional jumps - Uses the existing player physics and animation systems - Integrates with the network layer for multiplayer support ## Dodge System Documentation ### Overview The Dodge system is an advanced movement mechanic that allows players to perform both ground and aerial dodges. It seamlessly integrates with the existing movement systems, including double jumps, and provides fluid, momentum-based movement options for enhanced player mobility. ### Features #### Ground Dodges - **Forward Roll**: Performed while moving - Maintains directional momentum - Full dodge force (15 units) - 30% momentum preservation - Uses `emote-roll.glb` animation - **Backstep**: Performed while stationary - Moves backward relative to player orientation - Full dodge force (15 units) - 30% momentum preservation - Uses `emote-backstep.glb` animation #### Air Dodges The air dodge system features dynamic force adjustments based on the player's jump state: 1. **After First Jump** - 80% of base dodge force - 50% momentum preservation - 0.3 units of upward force - Directional control based on movement input 2. **After Double Jump** - 70% of base dodge force - 60% momentum preservation - 0.2 units of upward force - Enhanced momentum preservation for better control 3. **During Fall** - 75% of base dodge force - 50% momentum preservation - 0.3 units of upward force - Direction based on movement or forward orientation ### Technical Details #### System Properties ```javascript dodgeForce = 15 // Base force for all dodges dodgeDuration = 0.7 // Duration of dodge animation in seconds dodgeCooldown = 2000 // Cooldown between dodges in milliseconds ``` #### Movement Mechanics #### Force Calculation ```javascript finalForce = baseDodgeForce * stateMultiplier ``` Where `stateMultiplier` is: - Ground: 1.0 - Air (first jump): 0.8 - Air (double jump): 0.7 - Air (falling): 0.75 #### Momentum Preservation ```javascript momentumFactor = { ground: 0.3, air: 0.5, doubleJump: 0.6 } ``` #### Upward Force (Air Dodges) ```javascript upwardForce = { normal: 0.3, afterDoubleJump: 0.2 } ``` ### Integration with Other Systems #### Double Jump Integration - Cannot dodge during double jump animation - Reduced forces after double jump - Enhanced momentum preservation for better control - Reduced upward force to prevent excessive height gain #### Physics Integration - Uses PhysX capsule for collision detection - Maintains momentum through velocity preservation - Applies forces relative to player orientation - Handles ground and air states separately #### Animation System - Seamlessly transitions between dodge and movement animations - Respects animation priority with double jump system - Returns to appropriate idle/movement state after dodge - Network synchronized for multiplayer consistency ### Usage Examples #### Basic Usage ```javascript // Ground dodge while running forward W + SHIFT = Forward roll // Ground dodge while stationary SHIFT = Backstep // Air dodge while moving JUMP + (direction) + SHIFT = Directional air dodge // Air dodge after double jump JUMP + JUMP + SHIFT = Reduced-force air dodge ``` #### Advanced Techniques 1. **Jump-Cancel Dodge** - Jump during a ground dodge - Maintains dodge momentum into the jump - Can chain into double jump or air dodge 2. **Momentum-Preserved Air Dodge** - Use movement momentum before air dodge - Higher preservation factor in air - Useful for covering large distances 3. **Double Jump to Air Dodge** - Double jump for height - Air dodge for horizontal distance - Uses reduced force but higher momentum preservation ### Network Synchronization The system sends the following data for multiplayer synchronization: ```javascript { id: player.id, p: position.toArray(), q: quaternion.toArray(), e: emoteType } ``` ### Performance Considerations - Efficient use of vector pooling (v1) for calculations - Minimal garbage collection impact - Optimized physics calculations - Smart animation state management ### Debug Information The system includes extensive debug logging: - Dodge state and progress - Force calculations - Movement state - Animation transitions - Physics velocity updates ### Future Considerations Potential areas for enhancement: 1. Additional dodge types (side dodges, recovery dodges) 2. Customizable dodge parameters per player 3. Enhanced VFX/SFX integration 4. Advanced combo system integration 5. Dodge canceling mechanics ### Dependencies - Three.js for vector math and quaternions - PhysX for physics simulation - Custom animation system for emotes - Network synchronization system - Player control system ### Files - `src/core/systems/Dodge.js` - Main system implementation - `src/core/extras/playerEmotes.js` - Emote definitions - `assets/emote-roll.glb` - Roll animation - `assets/emote-backstep.glb` - Backstep animation emote-roll.glb can be found in the hyperfy-ref emote-backstep.glb can be found in the hyperfy-ref ## Sky Controller Documentation (concept) ### Overview The Sky Controller is a Hyperfy script that allows dynamic control of the sky and environmental lighting in your world. It provides a user-friendly interface to switch between different times of day and customize each state with specific sky textures and HDR environment maps. ### Features - Four different time-of-day states: Day (☀️), Dusk (🌅), Night (🌙), and Aurora (🌌) - Custom sky texture support for each state - Custom HDR lighting support for each state - Real-time switching between states - Default engine sky fallback for day mode ### Technical Implementation #### Configuration UI ```javascript app.configure(() => { return [ // Section Header { type: 'section', key: 'title', label: 'Sky', }, // Time of Day Switcher { type: 'switch', key: 'switch', label: 'TOD', value: 1, options: [ { value: 1, label: '☀️' }, { value: 2, label: '🌅' }, { value: 3, label: '🌙' }, { value: 4, label: '🌌' } ] }, // File inputs for each state... ] }) ``` The configuration UI is built using Hyperfy's configuration system, which automatically creates an inspector panel with: - A section header labeled "Sky" - A switch control for Time of Day (TOD) - File upload fields for each sky texture and HDR map #### Sky States Each state (except Day) requires two files: 1. **Sky Texture**: A background image for the sky dome - File type: JPG or PNG - Purpose: Visual appearance of the sky 2. **HDR Map**: A high dynamic range environment map - File type: HDR - Purpose: Environmental lighting and reflections #### State Configurations: 1. **Day Mode** (☀️) - Uses engine defaults - No files required - Clean, default lighting 2. **Dusk Mode** (🌅) - `sky1`: Dusk sky texture - `hdr1`: Dusk HDR lighting 3. **Night Mode** (🌙) - `sky2`: Night sky texture - `hdr2`: Night HDR lighting 4. **Aurora Mode** (🌌) - `sky3`: Aurora sky texture - `hdr3`: Aurora HDR lighting #### Core Functionality #### Sky Creation ```javascript const sky = app.create('sky') app.add(sky) ``` Creates and adds a sky entity to the world. #### Update Function ```javascript function updateSky() { const mode = app.config.switch console.log('Current mode:', mode) if (mode === 4) { // Aurora settings } else if (mode === 3) { // Night settings } else if (mode === 2) { // Dusk settings } else { // Day settings } } ``` The `updateSky()` function: - Reads the current mode from configuration - Sets appropriate sky texture (`sky.bg`) and HDR map (`sky.hdr`) - Falls back to engine defaults for day mode #### Event Handling ```javascript // Initial setup updateSky() // Listen for configuration changes app.on('config', () => { updateSky() }) // Update every frame to ensure sync app.on('update', dt => { updateSky() }) ``` The script maintains synchronization through: 1. Initial state setup on load 2. Configuration change listener for UI interactions 3. Frame update listener for continuous synchronization ### Usage Instructions 1. **Basic Setup**: - Add the script to your world - Scale appropriately using `app.scale.set(2.5, 2.5, 1.5)` 2. **Customizing States**: - Click the file upload fields in the inspector - Select appropriate sky textures (JPG/PNG) and HDR maps - Files are automatically uploaded and cached 3. **Switching States**: - Use the TOD switch in the inspector - Changes take effect immediately - Switch between Day, Dusk, Night, and Aurora modes 4. **Best Practices**: - Use appropriately sized textures for performance - Ensure HDR maps match their corresponding sky textures - Test transitions between states for smooth changes ### Technical Notes - The script uses optional chaining (`?.`) for safe property access - Configuration updates are handled through Hyperfy's event system - Frame updates ensure consistent state synchronization - File uploads are managed by Hyperfy's asset system # ashxn ## UV Scroll on Contact ``` const body = app.get('Body') const mesh = app.get('Mesh') let active = false body.onContactStart = e => { console.log('start', e.player) if (e.player) { active = true } } body.onContactEnd = e => { console.log('end', e.player) if (e.player) { active = false } } app.on('update', delta => { if (!active) return mesh.material.textureX += 1 * delta mesh.material.textureY += 1 * delta }) ``` # devilsadvocate.sol ## Getting Started With Hyperfy V2 Begin by dragging a glb into your hyperfy world. Remember to give yourself admin priviledges with `/admin <admin-key>`. Right click on the model to inspect it and click script to open the in world scripting UI. ``` const appId = app.id console.log('appId: ', appId) ``` When you drag a model into a hyperfy world an app entity is automatically created with an appId matching the root model name. If you don't know this name, you can return it with app.id. ``` const model = app.get(app.id) console.log('model',model) ``` ## World Hierarchy ``` Worlds └─ Entities ├─ Players └─ Apps └─ rootNodes (root model) └─ subNodes (sub-model e.g., collider) ``` A node or its subnodes can be accessed using app.get('node-id'). This is useful for assigning colliders, rigidbodies, or actions to specific subnodes of a root model. const rb = app.create('rigidbody') rb.type = 'dynamic' ## Rigidbody Types To allow your model to interact with physics it needs a rigidbody. There are three types: static(default), dynamic, or kinematic. Use the latter two options if you plan to move the object through code, with kinematic being most performant(clarity need on why this is). Other rigidbody attributes can be found in the github docs. ``` const collider = app.create('collider') rb.add(collider) app.add(rb) ``` Once we've created our rigidbody we can assign a collider to it. After creating the rb and adding the collider to it, we can add the rb to the app runtime making our object collidable. Colliders can also be returned from a model object using app.get('collider-name') if they have been included in the glb model as subnodes. ``` const action = app.create('action') action.label = 'Water Plant' action.position.set(0, 2.2, 0) action.duration = 0.3 ``` ## Actions Another app attribute are actions which can be used to make objects interactable in the world. Actions include attributes like a label, position, and duration to control their appearance and function. ``` action.onTrigger= () => { console.log(appId) } app.add(action) ``` Additionally actions can trigger functions when they are interacted with by a player. Actions must be added directly to the app with app.add(action) or to their nodes/subnodes using app.get('node-to-add-action').add(action) ``` const appPosition = app.position console.log('app', app) console.log('action', action) console.log('rb', rb) console.log('collider', collider) ```