Advanced

Advanced

Advanced topics and techniques for using the React 3rd Person Character library.

Custom State Machines

Extending the character state machine with custom states and transitions:

import { createMachine } from 'xstate';
 
// Custom character state machine
const customCharacterMachine = createMachine({
  id: 'customCharacter',
  initial: 'idle',
  context: {
    velocity: new THREE.Vector3(),
    rotation: new THREE.Euler(),
    isOnGround: true,
    health: 100,
    stamina: 100
  },
  states: {
    idle: {
      on: {
        MOVE: 'walking',
        JUMP: 'jumping',
        ATTACK: 'attacking'
      }
    },
    walking: {
      on: {
        STOP: 'idle',
        RUN: 'running',
        JUMP: 'jumping'
      }
    },
    running: {
      on: {
        STOP: 'idle',
        WALK: 'walking',
        JUMP: 'jumping'
      }
    },
    jumping: {
      on: {
        LAND: 'idle',
        FALL: 'falling'
      }
    },
    falling: {
      on: {
        LAND: 'idle'
      }
    },
    attacking: {
      on: {
        ATTACK_END: 'idle'
      }
    }
  }
});
 
// Use custom machine in character config
const customCharacterConfig = new CharacterBuilder({ fps: 60 })
  .withName('Custom Character')
  .withStateMachine(customCharacterMachine)
  .withCameraSettings({ radius: 5 })
  .withMovement({ movementSpeed: 5 })
  .with3DModels('/models/player', PlayerModel, Scenario3D)
  .build();

Custom Physics Integration

Integrating custom physics systems:

import { useRapier } from '@react-three/rapier';
 
function CustomPhysicsCharacter() {
  const { rapier, world } = useRapier();
  const { characterState } = useActorContext();
  
  // Custom physics body
  const rigidBodyRef = useRef();
  
  useFrame(() => {
    if (!rigidBodyRef.current) return;
    
    const rigidBody = rigidBodyRef.current;
    const velocity = rigidBody.linvel();
    
    // Custom physics logic
    if (characterState.matches('walking')) {
      const targetVelocity = new THREE.Vector3(0, velocity.y, 5);
      rigidBody.setLinvel(targetVelocity, true);
    }
    
    // Custom collision handling
    const collider = rigidBody.collider(0);
    if (collider) {
      const contacts = world.contactPairs(rigidBody, collider);
      // Handle custom collision logic
    }
  });
  
  return (
    <RAPIER.RigidBody ref={rigidBodyRef}>
      <RAPIER.CapsuleCollider args={[0.5, 0.5]} />
      <PlayerModel />
    </RAPIER.RigidBody>
  );
}

Custom Animation Systems

Building custom animation systems:

import { useFrame } from '@react-three/fiber';
 
function CustomAnimatedCharacter() {
  const meshRef = useRef();
  const { characterState } = useActorContext();
  
  // Custom animation state
  const [animationState, setAnimationState] = useState({
    walkCycle: 0,
    runCycle: 0,
    jumpHeight: 0
  });
  
  useFrame((state, delta) => {
    if (!meshRef.current) return;
    
    const mesh = meshRef.current;
    
    // Custom walking animation
    if (characterState.matches('walking')) {
      animationState.walkCycle += delta * 5;
      mesh.rotation.y = Math.sin(animationState.walkCycle) * 0.1;
      mesh.position.y = Math.abs(Math.sin(animationState.walkCycle * 2)) * 0.1;
    }
    
    // Custom running animation
    if (characterState.matches('running')) {
      animationState.runCycle += delta * 8;
      mesh.rotation.y = Math.sin(animationState.runCycle) * 0.15;
      mesh.position.y = Math.abs(Math.sin(animationState.runCycle * 3)) * 0.15;
    }
    
    // Custom jumping animation
    if (characterState.matches('jumping')) {
      animationState.jumpHeight = Math.min(animationState.jumpHeight + delta * 10, 2);
      mesh.position.y = animationState.jumpHeight;
    } else {
      animationState.jumpHeight = Math.max(animationState.jumpHeight - delta * 10, 0);
      mesh.position.y = animationState.jumpHeight;
    }
  });
  
  return (
    <mesh ref={meshRef}>
      <capsuleGeometry args={[0.5, 1, 4, 8]} />
      <meshStandardMaterial color="blue" />
    </mesh>
  );
}

Custom Camera Systems

Implementing custom camera behaviors:

import { useThree } from '@react-three/fiber';
 
function CustomCamera() {
  const { camera } = useThree();
  const { characterState } = useActorContext();
  const { character } = useActorContext();
  
  // Custom camera logic
  useFrame(() => {
    const targetPosition = new THREE.Vector3();
    const characterPosition = new THREE.Vector3(0, 1, 0); // Get from character
    
    // Calculate camera position based on character state
    let cameraDistance = character.camera.radius;
    
    if (characterState.matches('running')) {
      cameraDistance *= 1.2; // Further camera when running
    }
    
    if (characterState.matches('jumping')) {
      cameraDistance *= 0.8; // Closer camera when jumping
    }
    
    // Calculate camera position
    targetPosition.copy(characterPosition);
    targetPosition.y += character.camera.cameraOffsetY;
    targetPosition.z += cameraDistance;
    
    // Smooth camera movement
    camera.position.lerp(targetPosition, 0.1);
    camera.lookAt(characterPosition);
  });
  
  return null;
}
 
// Use custom camera
function App() {
  return (
    <CharacterConfigProvider character={characterConfig}>
      <FIBER.Canvas>
        <ExperienceWrapper>
          <CustomCamera />
          <Scenario />
          <Player />
        </ExperienceWrapper>
      </FIBER.Canvas>
    </CharacterConfigProvider>
  );
}

Custom Input Systems

Building custom input handling:

import { useEffect, useState } from 'react';
 
function CustomInputSystem() {
  const [customInputs, setCustomInputs] = useState({
    mouseX: 0,
    mouseY: 0,
    mousePressed: false,
    gamepad: null
  });
  
  // Custom mouse input
  useEffect(() => {
    const handleMouseMove = (event) => {
      setCustomInputs(prev => ({
        ...prev,
        mouseX: (event.clientX / window.innerWidth) * 2 - 1,
        mouseY: -(event.clientY / window.innerHeight) * 2 + 1
      }));
    };
    
    const handleMouseDown = () => {
      setCustomInputs(prev => ({ ...prev, mousePressed: true }));
    };
    
    const handleMouseUp = () => {
      setCustomInputs(prev => ({ ...prev, mousePressed: false }));
    };
    
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mousedown', handleMouseDown);
    window.addEventListener('mouseup', handleMouseUp);
    
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mousedown', handleMouseDown);
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, []);
  
  // Custom gamepad input
  useEffect(() => {
    const handleGamepadConnected = (event) => {
      setCustomInputs(prev => ({ ...prev, gamepad: event.gamepad }));
    };
    
    const handleGamepadDisconnected = () => {
      setCustomInputs(prev => ({ ...prev, gamepad: null }));
    };
    
    window.addEventListener('gamepadconnected', handleGamepadConnected);
    window.addEventListener('gamepaddisconnected', handleGamepadDisconnected);
    
    return () => {
      window.removeEventListener('gamepadconnected', handleGamepadConnected);
      window.removeEventListener('gamepaddisconnected', handleGamepadDisconnected);
    };
  }, []);
  
  // Process gamepad input
  useFrame(() => {
    if (customInputs.gamepad) {
      const gamepad = navigator.getGamepads()[customInputs.gamepad.index];
      if (gamepad) {
        // Process gamepad axes and buttons
        const leftStickX = gamepad.axes[0];
        const leftStickY = gamepad.axes[1];
        const rightStickX = gamepad.axes[2];
        const rightStickY = gamepad.axes[3];
        
        // Apply gamepad input to character
        // This would integrate with your character movement system
      }
    }
  });
  
  return null;
}

Performance Optimization Techniques

Advanced performance optimization:

import { Suspense, useMemo } from 'react';
 
function OptimizedCharacter() {
  // Memoize expensive calculations
  const characterConfig = useMemo(() => {
    return new CharacterBuilder({ fps: 60 })
      .withName('Optimized Player')
      .withCameraSettings({ radius: 5 })
      .withMovement({ movementSpeed: 5 })
      .with3DModels('/models/player', PlayerModel, Scenario3D)
      .build();
  }, []);
  
  // Lazy load components
  const LazyPlayerModel = useMemo(() => {
    return React.lazy(() => import('./PlayerModel'));
  }, []);
  
  return (
    <CharacterConfigProvider character={characterConfig}>
      <FIBER.Canvas
        gl={{
          powerPreference: "high-performance",
          antialias: false,
          stencil: false,
          depth: false
        }}
        shadows={false}
        dpr={[1, 2]} // Responsive pixel ratio
      >
        <Suspense fallback={<LoadingSpinner />}>
          <ExperienceWrapper>
            <Scenario />
            <Player />
          </ExperienceWrapper>
        </Suspense>
      </FIBER.Canvas>
    </CharacterConfigProvider>
  );
}
 
// Optimized model loading
function OptimizedPlayerModel() {
  const { scene } = useGLTF('/models/player.glb');
  
  // Optimize geometry
  useEffect(() => {
    if (scene) {
      scene.traverse((child) => {
        if (child.isMesh) {
          child.geometry.computeBoundingSphere();
          child.geometry.computeBoundingBox();
          child.frustumCulled = true;
        }
      });
    }
  }, [scene]);
  
  return <primitive object={scene} />;
}

Custom Shaders and Materials

Implementing custom visual effects:

import { shaderMaterial } from '@react-three/drei';
 
// Custom shader material
const CustomCharacterMaterial = shaderMaterial(
  {
    time: 0,
    color: new THREE.Color(0.2, 0.0, 0.1),
    scale: 1.0
  },
  // Vertex shader
  `
    uniform float time;
    uniform float scale;
    varying vec2 vUv;
    varying vec3 vPosition;
    
    void main() {
      vUv = uv;
      vPosition = position;
      
      vec3 pos = position;
      pos.y += sin(time + position.x * 10.0) * 0.1 * scale;
      
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  // Fragment shader
  `
    uniform float time;
    uniform vec3 color;
    varying vec2 vUv;
    varying vec3 vPosition;
    
    void main() {
      vec3 finalColor = color;
      finalColor += sin(time + vUv.x * 10.0) * 0.1;
      
      gl_FragColor = vec4(finalColor, 1.0);
    }
  `
);
 
// Extend Three.js with custom material
extend({ CustomCharacterMaterial });
 
function CustomShadedCharacter() {
  const materialRef = useRef();
  
  useFrame((state) => {
    if (materialRef.current) {
      materialRef.current.time = state.clock.elapsedTime;
    }
  });
  
  return (
    <mesh>
      <capsuleGeometry args={[0.5, 1, 4, 8]} />
      <customCharacterMaterial ref={materialRef} />
    </mesh>
  );
}

Network Integration

Integrating with multiplayer systems:

import { useEffect, useRef } from 'react';
 
function NetworkedCharacter() {
  const { characterState } = useActorContext();
  const socketRef = useRef();
  const lastUpdateRef = useRef(0);
  
  // Connect to network
  useEffect(() => {
    socketRef.current = new WebSocket('ws://localhost:3001');
    
    socketRef.current.onopen = () => {
      console.log('Connected to game server');
    };
    
    socketRef.current.onmessage = (event) => {
      const data = JSON.parse(event.data);
      // Handle incoming character updates
      if (data.type === 'characterUpdate') {
        // Update character position/state from network
      }
    };
    
    return () => {
      if (socketRef.current) {
        socketRef.current.close();
      }
    };
  }, []);
  
  // Send character updates
  useFrame((state) => {
    if (socketRef.current && state.clock.elapsedTime - lastUpdateRef.current > 0.05) {
      const characterData = {
        type: 'characterUpdate',
        state: characterState.value,
        position: [0, 1, 0], // Get from character
        rotation: [0, 0, 0]  // Get from character
      };
      
      socketRef.current.send(JSON.stringify(characterData));
      lastUpdateRef.current = state.clock.elapsedTime;
    }
  });
  
  return null;
}

Debugging and Development Tools

Development utilities:

import { useThree } from '@react-three/fiber';
 
function DebugTools() {
  const { scene, camera } = useThree();
  const { characterState } = useActorContext();
  
  // Debug UI
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      console.log('Character State:', characterState.value);
      console.log('Scene Objects:', scene.children.length);
      console.log('Camera Position:', camera.position);
    }
  }, [characterState, scene, camera]);
  
  // Debug helpers
  return (
    <group>
      {/* Grid helper */}
      <gridHelper args={[20, 20]} />
      
      {/* Axes helper */}
      <axesHelper args={[5]} />
      
      {/* Camera helper */}
      <cameraHelper args={[camera]} />
    </group>
  );
}
 
// Performance monitoring
function PerformanceMonitor() {
  const [fps, setFps] = useState(0);
  const frameCountRef = useRef(0);
  const lastTimeRef = useRef(0);
  
  useFrame((state) => {
    frameCountRef.current++;
    
    if (state.clock.elapsedTime - lastTimeRef.current >= 1) {
      setFps(frameCountRef.current);
      frameCountRef.current = 0;
      lastTimeRef.current = state.clock.elapsedTime;
    }
  });
  
  return (
    <div className="performance-monitor">
      <div>FPS: {fps}</div>
      <div>Draw Calls: {performance.memory?.usedJSHeapSize || 'N/A'}</div>
    </div>
  );
}

These advanced techniques allow you to extend the library's functionality and create more complex character systems tailored to your specific needs.