Block Types

Block types define the properties of each unique kind of block in the world. Since a Chunk references blocks at specific coordinates using their block type, we don't need to repeatedly define all the properties for every block in a chunk. Instead, we simply assign a block type's ID. This makes everything more efficient and streamlined!

Creating A BlockType

You can create a BlockType by creating a new instance using the BlockType class. Remember though, before the world is aware of this block type and before it can be used to set blocks of that type in the world, we have to register it with the world.blockTypeRegistry .

// Create a block type
const bouncyBlockType = new BlockType({
  id: bouncyBlockId // must be between 1 and 255, cannot be 0, 0 is reserved for air or "no block".
  textureUri: 'textures/clay.png',
  name: 'Bouncy Clay',
  customColliderOptions: {
    bounciness: 5,
  }
});

// Remember! You have to register it before you can use it in the world.
world.blockTypeRegistry.registerBlockType(bouncyBlockType);

Creating Block Types Via BlockTypeRegistry

If you want another, equivalent but syntactically a bit simpler way to create block types, you can do so through the world.blockTypeRegistry.

Let's use the blockTypeRegistry directly to create and register a block that looks like clay but makes players bounce when they touch it.

// This code can be anywhere, but typically you'd
// do something like this in your game's setup
// function for startServer(world => {}).

// Register the block type
const bouncyBlockId = 77;
world.blockTypeRegistry.registerGenericBlockType({
  id: bouncyBlockId // must be between 1 and 255, cannot be 0, 0 is reserved for air or "no block".
  textureUri: 'textures/clay.png',
  name: 'Bouncy Clay',
  customColliderOptions: {
    bounciness: 5,
  }
});

// Use the registered block type, 
// set a block of this type at a coordinate in the world.
world.chunkLattice.setBlock({ x: 1, y: 0, z: -1 }, bouncyBlockId);

BlockType Textures

BlockTypes can have their texture defined as a single texture that applies to all faces of the block, or as a path to a directory that has a unique image for each face of the block.

Note: While texture images can technically be any resolution, we strongly recommend you keep your textures as a 24x24 pixel image to maintain HYTOPIA's stylistic consistency and rendering performance for player's game clients.

To use a single image for a textureUri, we can do so like this:

new BlockType({
  id: knockbackBlockId,
  textureUri: 'textures/sand.png', // Uses 'assets/textures/sand.png' image for all faces
  name: 'Knockback Sand',
});

To use a unique texture for each face of a block, we can provide a path to a folder for our textureUri. HYTOPIA will know to look for +x.png , +y.png , +z.png , -x.png , -y.png , -z.png in the provided folder uri.

new BlockType({
  id: knockbackBlockId,
  // Looks in the 'assets/textures/grass' folder for a texture
  // for each block face. This resolves to using textures at
  // the following paths.
  // assets/textures/grass/+x.png
  // assets/textures/grass/+y.png
  // assets/textures/grass/+z.png
  // assets/textures/grass/-x.png
  // assets/textures/grass/-y.png
  // assets/textures/grass/-z.png
  textureUri: 'textures/grass',
  name: 'Knockback Sand',
});

Block Types With Collision & Contact Callbacks

Block types support setting a callback for when an entity starts and stops colliding with them, as well as a callback providing the amount of contact force for the start of the collision.

Note: As of now, entity collision callbacks with blocks do not return the coordinate of collision. We recognize this is extremely useful for determine what specific block coordinate an entiy collided with. We're working on implementing this, and you can expect the onEntityCollision callback signature to be updated to include the collision coordinate soon.

const sandKnockbackBlockId = 55;
const sandKnockbackBlockType = new BlockType({
  id: knockbackBlockId,
  textureUri: 'textures/sand.png',
  name: 'Knockback Sand',
});

sandKnockbackBlockType.onEntityCollision = (blockType, entity, started) => {
  // block type will be sandKnockbackBlockType
  // entity is the entity that collided with the block
  // starts is true if the collision started, false when the entity is no longer colliding
  entity.applyImpulse({ x: 0, y: 20, z: 0}); // apply vertical knockback shooting them up.
};

sandKnockbackBlockType.onEntityContactForce = (blockType, entity, contactForceData) => {
  // block type will be sandKnockbackBlockType
  // entity is the entity that collided with the block
  // log the contact force data, or do something with it.
  console.log(contactForceData);  
};

world.blockTypeRegistry.registerBlockType(sandKnockbackBlockType);

world.chunkLattice.setBlock({ x: 0, y: 0, z: -3 });

Alternatively, you may have a block type that is already registered and you want to either set a collision callback on it at some point in your code, or even remove a collision callback.

We can do that as follows

// Let's assume block id 14 is lava
const lavaBlockType world.blockTypeRegistry.getBlockTypeId(14);

// Set the collision callback
lavaBlockType.onEntityCollision = (blockType, entity, started) => {
  if (started) {
    // kill the entity by despawning it since it touched lava.
    entity.despawn();
  }
}

// Now, let's say later in our code lava suddenly no longer
// kills players for some reason, we can either reassign the callback
// if we want to change it's complete behavior, or remove it if we want
// lava collisions to no longer have any logic when a entity collides with them
lavaBlockType.onEntityCollision = undefined;

Diving Deeper

The BlockType implementation is constantly evolving and having new features added to it. You can find the latest BlockType API Reference here.

If there are features that we don't currently support for BlockType that you'd like to see added to the HYTOPIA SDK, you can submit a feature request here.

Last updated