Change Mouse Cursor Graphic


#1

Howdy guys,

Working on my own Raycasting demonstration, would like to change the mouse cursor to a custom sprite. Thought it would be fairly straight forward but not finding anything in help. I’m going to press on with the default mouse and get raycasting working, any way to easily update the mouse cursor graphic?

I’ll post a video once its complete.

Thanks,
Jarmo


#2

Bump.

I could see making the mouse invisible and then moving a sprite or UI along the mouse X, Y coordinates, though I’m not sure how I would do that without repackaging my scene into a different view, seems like I tough way to do it. Is there any easier way to do it?

Thanks,
Jarmo


#3

Did reading on this, looks like you can make a cursor file and load it through Input.Mouse. In terms of using a sprite based item I’m not sure, but would think you could turn the mouse invisible and move the sprite based off mouse X/Y in a repacked scene. If I ever do that I’ll try.


#4

You can use a sprite based cursor if you use a Custom Scene Renderer. I did it by creating a texture asset and the script below and adding it to the scene. This script could use some improvements. See comments for more details:

public class SpriteCursor : SyncScript
{
    private SpriteBatch spriteBatch;
    private DelegateSceneRenderer delegateRenderer;

    //In my example used a 16x16 texture with following settings
    // Generate mipmaps false
    // Compress false
    // Stream false
    public Texture Cursor { get; set; }

    public override void Start()
    {
        //If you are using a resizeable window and want to keep cursor size relative to window size,
        //should probably set VirtualResolution property.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        
        //Add renderer so we can draw cursor sprite last.
        delegateRenderer = new DelegateSceneRenderer(Draw);
        var renderers = GetSceneRenderers();
        renderers?.Add(delegateRenderer);


        Game.IsMouseVisible = false; //hide windows cursor.
    }

    public override void Update()
    {
        //Make sure SpriteCursor is the last renderer.
        //We really shouldn't do this each frame. Should setup game so this can be done in Start method, once.
        //If we don't need to do this and we don't need to do any other update logic should 
        //change script to inherit from StartupScript and delete this method.
        var renderers = GetSceneRenderers();
        if (renderers.Last() != delegateRenderer)
        {
            renderers.Remove(delegateRenderer);
            renderers.Add(delegateRenderer);
        }

        
    }
    private void Draw(RenderDrawContext context)
    {
        //Should probably only draw cursor if mouse is over window.
        //Have not worked out how to do that.
        if (Cursor != null)
        {
            spriteBatch.Begin(context.GraphicsContext,
                samplerState:context.GraphicsDevice.SamplerStates.PointClamp, //I wanted to make sure sprite stayed crisp/pixley
                depthStencilState: DepthStencilStates.None //Make sure cursor is always on top
                );

            //This assumes you are not using a VirtualResolution for the SpriteBatch. 
            //If using a VirtualResolution should probably use that rather than ViewSize.
            var cursorPosition = Input.MousePosition * context.RenderContext.RenderView.ViewSize;

            //Using whole texture as cursor. Assumes top-left corner of texture is cursor position.
            //If using cursor like a crosshair would have to do some off-setting.
            spriteBatch.Draw(Cursor, cursorPosition);

            spriteBatch.End();
        }
    }

    public override void Cancel()
    {
        //Remove from renderer.
        var renderers = GetSceneRenderers();
        renderers?.Remove(delegateRenderer);
        
        //clean up
        spriteBatch.Dispose();
        spriteBatch = null;

        //Opionally undo any game changes we made.
        Game.IsMouseVisible = true; //show windows cursor.
    }

    private List<ISceneRenderer> GetSceneRenderers()
    {
        //This asumes you are using the default Graphics Composition 
        var cameraRenderer = this.SceneSystem.GraphicsCompositor.Game as SceneCameraRenderer;
        var rendererCollection = cameraRenderer?.Child as SceneRendererCollection;
        return rendererCollection?.Children;
    }
}


#5

Regarding your question about where the Draw method is called. (Thought I would “answer” it here.):

The script adds a renderer to the graphics composition (rendering pipeline). For more details you should have a look at the documentation:

My first implementation was actually done via a custom renderer inheriting from SiliconStudio.Xenko.Rendering.Compositing.SceneRendererBase and adding it to the graphics composition via Game Studio. Like:

[Display("Sprite Cursor Renderer")]
public class SpriteCursorRenderer : SceneRendererBase
{
    private SpriteBatch spriteBatch;
    private InputManager inputManager;

    public Texture Cursor { get; set; }

    protected override void InitializeCore()
    {
        base.InitializeCore();

        spriteBatch = new SpriteBatch(GraphicsDevice);

        inputManager = this.Services.GetService<InputManager>();

        this.Services.GetService<IGame>().IsMouseVisible = false;
    }

    protected override void DrawCore(RenderContext context, RenderDrawContext drawContext)
    {
        if(Cursor != null)
        {
            spriteBatch.Begin(drawContext.GraphicsContext, depthStencilState: DepthStencilStates.None);

            spriteBatch.Draw(Cursor, inputManager.MousePosition * context.RenderView.ViewSize);

            spriteBatch.End();
        }
    }

    protected override void Destroy()
    {
        base.Destroy();

        spriteBatch.Dispose();
    }
}


#6

Thanks. I’m learning some things here from you but working with the engine framework hasn’t fully clicked yet. I’m currently trying to override the OnExiting() method in the GameBase so that I can put some network handling in and encourage network game clients to gracefully disconnect on exit. I’ve created the derived class and overriden the OnExiting() method, but I can’t figure out where to go from there. How do I add this override to the game? Do I need to register it somewhere?

Any help is appreciated, going to table the item and work on connection handling inside the multiplayer instance.