UNPKG

hytopia

Version:

The HYTOPIA SDK makes it easy for developers to create massively multiplayer games using JavaScript or TypeScript.

179 lines (148 loc) 5.84 kB
import { startServer, ColliderShape, PlayerEntity, PlayerEvent, Entity, EntityEvent, RigidBodyType, GameServer, SimpleEntityController, } from 'hytopia'; import worldMap from './assets/map.json'; startServer(world => { // Boilerplate // Uncomment this to visualize physics vertices, will cause noticable lag. // world.simulation.enableDebugRendering(true); world.loadMap(worldMap); world.on(PlayerEvent.JOINED_WORLD, ({ player }) => { const playerEntity = new PlayerEntity({ player, name: 'Player', modelUri: 'models/players/player.gltf', modelLoopedAnimations: [ 'idle' ], modelScale: 0.5, }); playerEntity.spawn(world, { x: 0, y: 10, z: 0 }); }); world.on(PlayerEvent.LEFT_WORLD, ({ player }) => { world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn()); }); /** * Spawn a block entity as a moving platform */ const blockPlatform = new Entity({ blockTextureUri: 'blocks/grass', // A texture URI without a file extension will use a folder and look for the textures for each face in the folder (-x.png, +x.png, -y.png, +y.png, -z.png, +z.png) blockHalfExtents: { x: 1, y: 0.5, z: 1 }, rigidBodyOptions: { type: RigidBodyType.KINEMATIC_VELOCITY, // Kinematic means platform won't be effected by external physics, including gravity linearVelocity: { x: 0, y: 0, z: 3 }, // A starting velocity that won't change until we change it, because it's kinematic }, }); // Clamp the z range the platform moves back and forth between blockPlatform.on(EntityEvent.TICK, () => { const position = blockPlatform.position; if (position.z < -9) { blockPlatform.setLinearVelocity({ x: 0, y: 0, z: 3 }); } if (position.z > 8) { blockPlatform.setLinearVelocity({ x: 0, y: 0, z: -3 }); } }); blockPlatform.spawn(world, { x: 3, y: 3, z: -7 }); /** * Spawn a 2x2x2 block entity that spins in the air */ const spinningBlock = new Entity({ blockTextureUri: 'blocks/stone-bricks.png', blockHalfExtents: { x: 1, y: 1, z: 1 }, // half extents at the target size / 2. rigidBodyOptions: { type: RigidBodyType.KINEMATIC_VELOCITY, angularVelocity: { x: 1, y: 1, z: 1 }, }, }); spinningBlock.spawn(world, { x: 0, y: 10, z: -6 }); /** * Spawn a block entity that interacts with physics and can be pushed by players */ const movableBlock = new Entity({ // Entity is dynamic by default, meaning it will interact with external forces, including gravity blockTextureUri: 'blocks/sand.png', // A block of 1x1x1 would be half extents of 0.5x0.5x0.5, // if you do not explicitly create rigidBodyOptions.colliders // for the block, a collider will be created automatically // based on the block half extends. blockHalfExtents: { x: 0.5, y: 0.5, z: 0.5 }, }); movableBlock.on(EntityEvent.ENTITY_COLLISION, ({ started }) => { if (started) { world.chatManager.sendBroadcastMessage('The sand block was pushed!'); } }); movableBlock.spawn(world, { x: -4, y: 10, z: -6 }); /** * Spawn a very heavy block that interacts with physics, and is much harder to move, * Also, prevent it from rotating. */ const heavyBlock = new Entity({ blockTextureUri: 'blocks/stone-bricks.png', blockHalfExtents: { x: 0.5, y: 0.5, z: 0.5 }, rigidBodyOptions: { type: RigidBodyType.DYNAMIC, additionalMass: 10, enabledRotations: { x: false, y: false, z: false }, }, }); heavyBlock.spawn(world, { x: -4, y: 10, z: -1 }); /** * Spawn a bouncing block, because why not? * Also disable rotations so it doesn't spin around like a maniac */ const bouncingBlock = new Entity({ blockTextureUri: 'blocks/ice.png', blockHalfExtents: { x: 0.5, y: 0.5, z: 0.5 }, rigidBodyOptions: { type: RigidBodyType.DYNAMIC, enabledRotations: { x: false, y: false, z: false }, colliders: [ { shape: ColliderShape.BLOCK, halfExtents: { x: 0.5, y: 0.5, z: 0.5 }, bounciness: 2, }, ], }, }); bouncingBlock.spawn(world, { x: 0, y: 10, z: -3 }); /** * Make a block pet that follows a player around * Because, again why not? */ const blockPet = new Entity({ blockTextureUri: 'blocks/bricks.png', blockHalfExtents: { x: 0.5, y: 0.5, z: 0.5 }, // attach a simple entity controller so we can pathfind, // the entity controller will be created and associated when we spawn the entity controller: new SimpleEntityController(), }); blockPet.spawn(world, { x: 0, y: 10, z: -6 }); // Simple pathfinding let pathfindAccumulator = 0; blockPet.on(EntityEvent.TICK, ({ tickDeltaMs }) => { pathfindAccumulator += tickDeltaMs; if (pathfindAccumulator > 3000) { // only pathfind every 3 seconds so we don't do unecessary pathfinding pathfindAccumulator = 0; const connectedPlayers = GameServer.instance.playerManager.getConnectedPlayers(); if (!connectedPlayers.length) { return; } // if no players, don't pathfind. // pick a random player to follow const targetPlayer = connectedPlayers[Math.floor(Math.random() * connectedPlayers.length)]; // get the player's entity const targetPlayerEntity = world.entityManager.getPlayerEntitiesByPlayer(targetPlayer)[0]; if (!targetPlayerEntity) { return; } // if the player doesn't have an entity, don't pathfind. // pathfind to the player's entity const blockPetEntityController = blockPet.controller as SimpleEntityController; const targetPosition = targetPlayerEntity.position; blockPetEntityController.move(targetPosition, 3); blockPetEntityController.face(targetPosition, 1); } }); });