Собственный курсор: изображение и hotspot
Настроить слот курсора, проверить изображение и вернуть системный указатель.
Для обычного указателя меняйте системный курсор через Input.SetCustomMouseCursor(). Изображение спрайта, перемещаемого за мышью, остаётся игровым объектом: оно рисуется в кадрах игры и не становится системным указателем.
Подготовьте изображение
- Используйте небольшое изображение с прозрачностью, например PNG 32×32 или 64×64; импортируйте без потерь, избегайте VRAM-сжатия.
- Задайте hotspot — пиксель, который обозначает точку клика. Для стрелки это её кончик, для прицела — центр.
- В общей реализации размер ограничен 256×256. Это не гарантия такого размера на любой целевой платформе: проверяйте экспорт и используйте минимальный нужный размер.
- Держите hotspot внутри изображения. Например, для 32×32 допустимые целые координаты — 0–31; точка (32, 0) уже не пиксель изображения.
Готовая обвязка установки и сброса
Класс владеет одним слотом курсора: создаёт текстуру из непустого Image, проверяет размеры/hotspot и устанавливает изображение. Dispose() сбрасывает этот слот к системному виду и освобождает созданную текстуру.
using System;
using Reckfell;
public sealed class CursorTheme : IDisposable
{
private readonly ImageTexture _texture;
private readonly Input.CursorShape _shape;
private bool _disposed;
public CursorTheme(Image image, Vector2 hotspot, Input.CursorShape shape = Input.CursorShape.Arrow)
{
if (image is null || image.IsEmpty() || image.GetWidth() > 256 || image.GetHeight() > 256)
throw new ArgumentException("Cursor requires a nonempty image no larger than 256 by 256.", nameof(image));
if (!hotspot.IsFinite() || hotspot.X < 0 || hotspot.Y < 0 ||
hotspot.X >= image.GetWidth() || hotspot.Y >= image.GetHeight())
throw new ArgumentOutOfRangeException(nameof(hotspot));
if (shape < Input.CursorShape.Arrow || shape > Input.CursorShape.Help)
throw new ArgumentOutOfRangeException(nameof(shape));
_shape = shape;
_texture = ImageTexture.CreateFromImage(image);
Input.SetCustomMouseCursor(_texture, shape, hotspot);
}
public void Dispose()
{
if (_disposed) return;
Input.SetCustomMouseCursor(null!, _shape);
_texture.Dispose();
_disposed = true;
}
}Получите изображение из импортированной текстуры через ResourceLoader.Load<Texture2D>(path).GetImage() и передайте его конструктору CursorTheme. Создавайте тему один раз при входе в контекст интерфейса; сохраните её в поле и вызовите Dispose() при выходе. Не применяйте курсор каждый кадр.
null! в сбросе намеренно передаёт пустой ресурс: нативный API использует его для возврата системного изображения. Оператор ! здесь подавляет предупреждение C# о nullable, а не создаёт объект.
Сброс не восстанавливает предыдущую пользовательскую тему. Пример рассчитан на одного владельца слота; если разные экраны меняют курсор, управляйте ими из одного сервиса и явно храните текущую тему.
Изображение слота и активная форма — разные настройки
SetCustomMouseCursor заменяет изображение выбранного Input.CursorShape, по умолчанию Arrow. Например, настройка Ibeam не заменяет стрелку. SetDefaultCursorShape выбирает форму по умолчанию, но элемент интерфейса может запросить другую форму.
Если курсор меняется над полем ввода или кнопкой, проверьте выбранный слот и настройки Control, а не переустанавливайте изображение каждый кадр. В режиме скрытого или захваченного курсора установка картинки сама по себе не делает указатель видимым; координаты мыши.
Где задавать настройку
Для одной постоянной темы можно использовать настройки проекта display/mouse_cursor/custom_image и display/mouse_cursor/custom_image_hotspot. Программная установка нужна для переключения контекстов во время игры. Не рассчитывайте на SetCustomMouseCursor как на способ менять курсор самого редактора: нативный Input пропускает вызов при Engine.IsEditorHint().
Если указатель неверный
Смещение клика — проверьте hotspot, а не положение объекта в мире. Чёрный фон — проверьте альфа-канал исходного изображения и импорт. Стрелка правильная, I-beam старый — настроен только слот Arrow. После выхода потерялась чужая тема — несколько владельцев сбрасывают один слот. Нет картинки в захвате — проверьте режим мыши.
Проверены отказы для пустого/большого изображения, неверного слота и отрицательного, граничного или нечислового hotspot. Установка RGBA-изображения и повторный сброс выполнились через Windows backend без ошибок. Скриншот указателя, вид на разных DPI, переопределения GUI, реальные клики и другие платформы не проверялись.