Table of Contents

Raycast

This example demonstrates how to implement raycasting and physics interactions in Stride. The sample shows:

  • Setting up a 3D scene with a sphere controlled by physics
  • Implementing mouse-based raycasting from the camera
  • Applying physics impulses to objects based on click location
  • Creating interaction between user input and physics objects

The example features a sphere that responds to physics. When you click on the ground, a ray is cast from the camera to determine the hit point, and the sphere is propelled in that direction with an appropriate impulse. Clicking directly on the sphere stops its movement entirely. A visual line indicator shows the direction of the applied force.

This example demonstrates key concepts for interactive physics-based games, including raycasting for object selection, applying physics forces at specific points, and creating visual feedback for user interactions.

Note

This example requires the additional NuGet packages Stride.CommunityToolkit.Skyboxes and Stride.CommunityToolkit.Bepu. Make sure to install both before running the code.

Stride UI Example

View on GitHub.

using Stride.BepuPhysics;
using Stride.CommunityToolkit.Bepu;
using Stride.CommunityToolkit.Engine;
using Stride.CommunityToolkit.Rendering.Gizmos;
using Stride.CommunityToolkit.Rendering.ProceduralModels;
using Stride.CommunityToolkit.Skyboxes;
using Stride.Core.Mathematics;
using Stride.Engine;
using Stride.Games;
using Stride.Graphics;
using Stride.Input;
using Stride.Rendering;
using Buffer = Stride.Graphics.Buffer;

// Constants for the impulse strength and sphere properties
const float ImpulseStrength = 0.5f;
const float SphereRadius = 0.5f;

// Game entities and components
CameraComponent? mainCamera = null;
Entity? sphereEntity = null;
BodyComponent? sphereBody = null;
Buffer? vertexBuffer = null;

// Line vertices to visualize the impulse direction (start and end points)
Vector3[] lineVertices = new Vector3[2];

// Initialize the game instance
using var game = new Game();

// Run the game loop with the Start and Update methods
game.Run(start: Start, update: Update);

// Sets up the initial scene with a skybox, profiler, ground reference, and a sphere with physics
void Start(Scene scene)
{
    // Set up a basic 3D scene with skybox, profiler, and a ground gizmo
    game.SetupBase3DScene();
    game.AddSkybox();
    game.AddProfiler();
    game.AddGroundGizmo(new(-5, 0, -5), showAxisName: true);

    // Create a sphere entity and position it above the ground
    sphereEntity = game.Create3DPrimitive(PrimitiveModelType.Sphere);
    sphereEntity.Transform.Position = new Vector3(0, 8, 0);

    // Retrieve the physics body component attached to the sphere
    sphereBody = sphereEntity.Get<BodyComponent>();

    // Add the sphere entity to the scene, this will make it visible in the game window
    sphereEntity.Scene = scene;

    // Retrieve the active camera from the scene
    mainCamera = scene.GetCamera();

    // Create a line entity used for visualizing the impulse direction
    var lineEntity = CreateLineEntity(game);

    // Attach the line as a child of the sphere so it moves along with it
    sphereEntity.AddChild(lineEntity);
}

// Called every frame to update game logic and process user input
void Update(Scene scene, GameTime time)
{
    if (mainCamera == null) return;

    // Display on-screen instructions for the user
    DisplayInstructions(game);

    // On left mouse button click, process the interaction
    if (game.Input.IsMouseButtonPressed(MouseButton.Left))
    {
        ProcessMouseClick();
    }
}

// Processes mouse click events by casting a ray and determining the appropriate action.
// If the sphere is clicked, its movement is halted; otherwise, an impulse is applied.
void ProcessMouseClick()
{
    // Cast a ray from the camera into the scene based on the mouse position
    var hit = mainCamera.Raycast(game.Input.MousePosition, 100, out var hitInfo);

    if (hit)
    {
        // Ensure the sphere's physics body and entity are valid
        if (sphereBody is null || sphereEntity is null) return;

        Console.WriteLine($"Hit entity: {hitInfo.Collidable.Entity.Name}");

        // If the sphere itself is clicked, stop its movement by zeroing its velocity
        if (hitInfo.Collidable.Entity == sphereEntity)
        {
            sphereBody.LinearVelocity = Vector3.Zero;
            sphereBody.AngularVelocity = Vector3.Zero;

            return;
        }

        // Update the line visualization to point from the sphere to the hit point
        UpdateLineVisualization(hitInfo.Point);

        // Calculate and apply an impulse to the sphere based on the hit point
        ApplyImpulseToSphere(hitInfo.Point);
    }
    else
    {
        Console.WriteLine("No hit");
    }
}

// Updates the endpoint of the line to visualize the hit position in the sphere's local space
void UpdateLineVisualization(Vector3 hitPointWorld)
{
    if (sphereEntity == null || vertexBuffer == null) return;

    // Convert the hit point from world space to the sphere's entity local coordinate space
    var localHitPoint = Vector3.Transform(hitPointWorld, Matrix.Invert(sphereEntity.Transform.WorldMatrix));

    // Update the end vertex of the line's endpoint
    lineVertices[1] = localHitPoint.XYZ();

    // Re-upload the updated vertex data to the GPU
    vertexBuffer.SetData(game.GraphicsContext.CommandList, lineVertices);
}

// Calculates and applies an impulse to the sphere to simulate a physics interaction
void ApplyImpulseToSphere(Vector3 hitPointWorld)
{
    if (sphereEntity == null || sphereBody == null) return;

    // Calculate the direction vector from the sphere's center to the hit point
    var sphereCenter = sphereEntity.Transform.WorldMatrix.TranslationVector;
    var direction = hitPointWorld - sphereCenter;

    // Normalize the direction to ensure a consistent impulse strength regardless of distance
    direction.Normalize();

    // Determine the impulse vector
    var impulse = direction * ImpulseStrength;

    // Calculate an offset from the center so the impulse is applied at the sphere's surface,
    // which helps in inducing a rotational effect
    var offset = direction * SphereRadius;

    // Apply the calculated impulse to the physics body
    sphereBody.ApplyImpulse(impulse, offset);

    // Mark the body as awake to ensure the physics engine processes the change
    sphereBody.Awake = true;
}

// Creates a line entity to visualize the direction of the applied impulse
Entity CreateLineEntity(Game game)
{
    // Initialize the line vertices.
    // The start point is at the origin; the endpoint is set arbitrarily
    lineVertices[0] = Vector3.Zero;
    lineVertices[1] = new(-1, 1, 1);

    // Create a vertex buffer for the line, with start and end points
    vertexBuffer = Buffer.New(game.GraphicsDevice, lineVertices, BufferFlags.VertexBuffer);

    // Create an index buffer defining the line's two endpoints
    var indices = new ushort[] { 0, 1 };
    var indexBuffer = Buffer.New(game.GraphicsDevice, indices, BufferFlags.IndexBuffer);

    // Set up the mesh draw parameters for a line list
    var meshDraw = new MeshDraw
    {
        PrimitiveType = PrimitiveType.LineList,
        VertexBuffers = [new VertexBufferBinding(vertexBuffer, new VertexDeclaration(VertexElement.Position<Vector3>()), lineVertices.Length)],
        IndexBuffer = new IndexBufferBinding(indexBuffer, is32Bit: false, indices.Length),
        DrawCount = indices.Length
    };

    // Create the mesh
    var mesh = new Mesh { Draw = meshDraw };

    // The model is built from the mesh and a gizmo material, an emissive material for clear visualization
    var lineModelComponent = new ModelComponent { Model = new Model { mesh, GizmoEmissiveColorMaterial.Create(game.GraphicsDevice, Color.DarkMagenta) } };

    // Return a new entity that contains the line model component
    return new Entity { lineModelComponent };
}

// Displays on-screen instructions to guide the user
static void DisplayInstructions(Game game)
{
    game.DebugTextSystem.Print("Click the ground to apply a direction impulse", new(5, 30));
    game.DebugTextSystem.Print("Click the sphere to stop moving", new(5, 50));
}