Valkyrien Skies Help

How to use joints

Learn how to create, update, and remove joints between two ships.

Introduction

Joints can be used to connect two ships (or a ship and the world) with a variety of connection types including fixed rotation, position, specific locked axis, or other setups.

If you want to create something like a rope or a bearing, then you're in the right place.

Steps

Creating a joint

To create a joint, you will need:

  • Two body ids (which are usually obtained from a ship)

  • An instance of a subclass of VSJoint

  • Two VSJointPose's to use in constructing the VSJoint

Available VSJoint subclasses

VSFixedJoint, VSDistanceJoint, VSPrismaticJoint, VSSphericalJoint, VSRevoluteJoint, VSGearJoint, VSRackAndPinionJoint, VSD6Joint



Different joint subclasses require different amounts of parameters to be specified.



For simplicity, this page will demonstrate using the VSFixedJoint, which requires the least parameters to set up.

Joints need to be created on the physics tick, however the GTPA has methods to allow creation of joints on the game tick.

Here is an example function (using the GTPA) which adds a fixed joint between two ships (at their center of mass, with their current rotation):

fun makeJoint(level: Level, ship0: Ship, ship1: Ship) { val pose0 = VSJointPose( ship0.shipAABB?.center(Vector3d()) ?: Vector3d(), ship0.transform.rotation ) val pose1 = VSJointPose( ship1.shipAABB?.center(Vector3d()) ?: Vector3d(), ship1.transform.rotation ) val gtpa = getOrCreateGTPA(level.dimensionId) val joint = VSFixedJoint(ship0.id, pose0, ship1.id, pose1) gtpa.addJoint(joint, 0) {} }
public void makeJoint(Level level, Ship ship0, Ship ship1) { VSJointPose pose0 = new VSJointPose( ship0.getShipAABB().center(new Vector3d()), ship0.getTransform().getRotation() ); VSJointPose pose1 = new VSJointPose( ship1.getShipAABB().center(new Vector3d()), ship1.getTransform().getRotation() ); GameToPhysicsAdapter gtpa = ValkyrienSkiesMod.getOrCreateGTPA( VSGameUtilsKt.getDimensionId(level) ); VSFixedJoint joint = new VSFixedJoint(ship0.getId(), pose0, ship1.getId(), pose1, null, VSJoint.DEFAULT_COMPLIANCE); gtpa.addJoint(joint, 0, (id) -> {}); }

If you are looking to add a joint on the physics tick, look at how the GameToPhysicsAdapter does it.

Storing the joint id

If you are keen-eyed, you will have noticed the empty lambda we passed into the addJoint method in the previous code example.

This lambda (which is a VSJointId consumer) will be run on the physics tick once the joint has been added. The VSJointId the lambda provides you is the (integer) id which has been allocated to your joint.

Addons will often use this to store the id into a variable in a block entity for updating/removing the joint.

private var jointId: VSJointId? = null fun makeJoint(level: Level, ship0: Ship, ship1: Ship) { // ... gtpa.addJoint(joint, 0) { id -> jointId = id } }
private @Nullable Integer jointId = null; public void makeJoint(Level level, Ship ship0, Ship ship1) { // ... gtpa.addJoint(joint, 0, (id) -> { jointId = id; }); }

Updating the joint

To update an existing joint you can get the current joint, copy its properties to a new VSJoint instance and change as needed. Then construct an instance of VSJointAndId to give to the GTPA to update.

fun updateJoint(level: Level, newPos0: Vector3d, jointId: VSJointId) { val gtpa = getOrCreateGTPA(level.dimensionId) val existingJoint = gtpa.getJointById(jointId) ?: return val newPose0 = VSJointPose( newPos0, existingJoint.pose0.rot ) val newJoint = VSFixedJoint( existingJoint.shipId0, newPose0, existingJoint.shipId1, existingJoint.pose1 ) val jointAndId = VSJointAndId(jointId, newJoint) gtpa.updateJoint(jointAndId) }
public void updateJoint(Level level, Vector3d newPos0, int jointId) { GameToPhysicsAdapter gtpa = ValkyrienSkiesMod.getOrCreateGTPA( VSGameUtilsKt.getDimensionId(level) ); VSFixedJoint existingJoint = (VSFixedJoint) gtpa.getJointById(jointId); if (existingJoint == null) return; VSJointPose newPose0 = new VSJointPose( newPos0, existingJoint.getPose0().getRot() ); VSFixedJoint newJoint = new VSFixedJoint( existingJoint.getShipId0(), newPose0, existingJoint.getShipId1(), existingJoint.getPose1(), existingJoint.getMaxForceTorque(), existingJoint.getCompliance() ); VSJointAndId jointAndId = new VSJointAndId(jointId, newJoint); gtpa.updateJoint(jointAndId); }

Removing the joint

To remove a joint, you simply give the GTPA a VSJointId.

fun removeJoint(level: Level, jointId: VSJointId) { val gtpa = getOrCreateGTPA(level.dimensionId) gtpa.removeJoint(jointId) }
public void removeJoint(Level level, int jointId) { GameToPhysicsAdapter gtpa = ValkyrienSkiesMod.getOrCreateGTPA( VSGameUtilsKt.getDimensionId(level) ); gtpa.removeJoint(jointId); }
Last modified: 09 June 2026