Repository navigation
PrimitiveShapes API
PrimitiveShapes renders 3D vector graphics directly on Bedrock clients using the native PrimitiveShapesPacket.
| Aspect | Traditional PMMP Approach | PrimitiveShapes API |
|---|---|---|
| Mechanism | Spawns fake ArmorStands, Invisible Entities, or Particle tasks | Native Bedrock PrimitiveShapesPacket
|
| Server Load | High CPU entity tick loops & physics collision checks | 0 server entity overhead, rendered on client GPU |
| Viewer Replay | Requires custom player join listeners & chunk tracking tasks | Automatic chunk shape replay on chunk load |
| Lifecycle | Manual entity management & despawn packets |
ShapeHandle lifecycle token with idempotent .remove()
|
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\BoxShape;
// Add a 2x2x2 box to the world
$handle = $world->addShape($pos, new BoxShape(
origin: $pos,
bound: new Vector3(2, 2, 2),
color: new Color(255, 100, 0)
));
// Update the shape in real-time without client flicker
$handle->update(new BoxShape($pos, new Vector3(3, 3, 3), color: new Color(0, 255, 0)));
// Remove the shape
$handle->remove();When you call $world->addShape($pos, $shape), PocketmineMV tracks the shape inside $activeShapes by chunk hash.
When a player enters render range or loads a chunk, NetworkSession::startUsingChunk() automatically replays all active shapes in that chunk to the client.
All shape classes are in pocketmine\world\shape\shapes\*.
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\LineShape;
$shape = new LineShape(
start: Vector3 $start,
end: Vector3 $end,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\BoxShape;
$shape = new BoxShape(
origin: Vector3 $origin,
bound: Vector3 $bound,
scale: ?float $scale = null,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\SphereShape;
$shape = new SphereShape(
center: Vector3 $center,
segments: int $segments, // e.g. 16 or 20
scale: ?float $scale = null,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\CircleShape;
$shape = new CircleShape(
center: Vector3 $center,
segments: int $segments,
scale: ?float $scale = null,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\TextShape;
$shape = new TextShape(
position: Vector3 $position,
text: string $text,
useRotation: bool $useRotation = false,
backgroundColor: ?Color $backgroundColor = null,
depthTest: bool $depthTest = true, // false = visible through walls (X-Ray)
showBackface: bool $showBackface = true,
showTextBackface: bool $showTextBackface = true,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\ArrowShape;
$shape = new ArrowShape(
tail: Vector3 $tail,
head: Vector3 $head,
scale: ?float $scale = null,
color: ?Color $color = null,
arrowHeadLength: ?float $arrowHeadLength = null,
arrowHeadRadius: ?float $arrowHeadRadius = null,
segments: ?int $segments = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector2;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\CylinderShape;
$shape = new CylinderShape(
base: Vector3 $base,
radiusX: Vector2 $radiusX, // top and bottom radius X
radiusZ: Vector2 $radiusZ, // top and bottom radius Z
height: float $height,
segments: int $segments,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\PyramidShape;
$shape = new PyramidShape(
base: Vector3 $base,
width: float $width,
height: float $height,
depth: ?float $depth = null,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\EllipsoidShape;
$shape = new EllipsoidShape(
center: Vector3 $center,
radii: Vector3 $radii,
segmentsPerAxis: int $segmentsPerAxis,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);use pocketmine\color\Color;
use pocketmine\math\Vector2;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\ConeShape;
$shape = new ConeShape(
base: Vector3 $base,
radii: Vector2 $radii,
height: float $height,
segments: int $segments,
color: ?Color $color = null,
dimensionId: ?int $dimensionId = null,
attachedEntityId: ?int $attachedEntityId = null,
rotation: ?Vector3 $rotation = null
);Pass $rotation (Vector3($yaw, $pitch, $roll)) into any shape class:
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\BoxShape;
$world->addShape($pos, new BoxShape(
origin: $pos,
bound: new Vector3(2, 2, 2),
color: new Color(255, 100, 0),
rotation: new Vector3(45, 0, 0)
));addTemporaryShape accepts duration in ticks. The client auto-destroys the visual representation, and the server cleans up memory when expired.
use pocketmine\world\shape\shapes\SphereShape;
$handle = $world->addTemporaryShape(
pos: $pos,
shape: new SphereShape($pos, segments: 16, scale: 2.0),
durationTicks: 100 // 5 seconds
);Group multiple shapes into a single ShapeGroup token to manage complex structures:
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\BoxShape;
use pocketmine\world\shape\shapes\TextShape;
$arena = $world->addShapeGroup([
[$pos->add(-5, 0, -5), new BoxShape($pos->add(-5, 0, -5), new Vector3(1, 4, 1), color: new Color(255, 0, 0))],
[$pos->add( 5, 0, -5), new BoxShape($pos->add( 5, 0, -5), new Vector3(1, 4, 1), color: new Color(255, 0, 0))],
[$pos->add( 0, 4, 0), new TextShape($pos->add(0, 4, 0), "§eARENA")],
]);
$arena->update(2, new TextShape($pos->add(0, 4, 0), "§aROUND 2"));
$arena->remove();Attach shapes directly to players or entities. The client tracks position automatically:
use pocketmine\color\Color;
use pocketmine\math\Vector3;
use pocketmine\world\shape\shapes\CircleShape;
$player->attachShape(
shape: new CircleShape($player->getPosition(), segments: 32, scale: 1.2, color: new Color(255, 215, 0)),
offset: new Vector3(0, 0.1, 0)
);
$player->detachShapes();Send shapes visible only to a specific player:
use pocketmine\world\shape\shapes\TextShape;
$handle = $player->addShape(new TextShape($pos, "§aPrivate Quest Marker"));
$player->removeShape($handle);
$player->clearShapes();Event listener for shape creation:
use pocketmine\event\Listener;
use pocketmine\event\world\WorldShapeEvent;
class ShapeListener implements Listener{
public function onWorldShape(WorldShapeEvent $event) : void{
if($event->getWorld()->getFolderName() === "protected_hub"){
$event->cancel();
}
}
}