Cursors: Krea-Grafiken in voller Auflösung, Blank-Cursor beim Malen, normalisierte Hotspots

- Cursor_Normal/Paint/Pipette: generierte Grafiken jetzt in Vollauflösung (766-829 px, Max Size im Importer steuert Anzeigegröße), Paint-Ring-Inneres freigestellt
- Neuer Cursor_Blank (transparent): wird während eines Strokes auf dem Mesh gesetzt statt Cursor.visible zu togglen (fixt Flicker-Kampf mit dem Orbit-Input)
- CursorController: Hotspots normalisiert (0-1, unabhängig von Import Max Size); Zustandskette Pipette > (Stroke ? Blank : Paint) > Normal; off-mesh während Stroke -> Normal
- MeshPainter: StrokeInProgress-Flag (Maustaste auf Paint-Pfad gehalten)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-11 21:35:36 +02:00
1 parent 653bc79236
commit 423a2d15ec
10 files changed
+160 -20

No files matched your search

@@ -424,6 +424,8 @@ public partial class MeshPainter : MonoBehaviour
// --- preview overlay ---
/// <summary>True while the brush preview decal is actually drawn this frame (hover hits the paintable surface).</summary>
public bool BrushPreviewVisible { get; private set; }
/// <summary>True while the paint button is held on the paint path (stroke running). The cursor hides entirely.</summary>
public bool StrokeInProgress { get; private set; }
public Shader previewShader; // optional; else Shader.Find("Hidden/MeshPaintPreview")
private Material _previewMat;
private Mesh _previewQuad; // unit quad in the tangent plane (flat decal mode)
@@ -855,14 +857,20 @@ public partial class MeshPainter : MonoBehaviour
ResolveActiveMask();
if (!TryGetMouse(out bool pressed, out Vector2 screenPos))
{
StrokeInProgress = false;
return;
}
// Don't paint through the in-game UI (a HUD click must not also stamp the mesh behind it).
if (pressed && InputUtil.PointerOverUi())
pressed = false;
// Brush preview at the hover point - independent of painting AND of the pipette, so the
// footprint stays visible before pressing, while the pipette holds, and during wheel resize.
// While the button is held on the paint path the hardware cursor is hidden entirely
// (CursorController); the preview decal itself stays visible during the stroke.
StrokeInProgress = pressed && !pipette;
// Brush preview at the hover point.
if (previewMode != BrushPreviewMode.Off && _previewMat && paintCamera)
UpdatePreview(screenPos);
@@ -13,14 +13,17 @@ public class CursorController : MonoBehaviour
[Header("Cursors (import type: Cursor)")]
[SerializeField] private Texture2D normalCursor;
[Tooltip("Click point inside the texture, in pixels from the top-left.")]
[Tooltip("Click point inside the texture, normalized 0-1 from the top-left. Independent of the importer's Max Size.")]
[SerializeField] private Vector2 normalHotspot = Vector2.zero;
[SerializeField] private Texture2D paintCursor;
[SerializeField] private Vector2 paintHotspot = new(16f, 16f);
[SerializeField] private Vector2 paintHotspot = new(0.5f, 0.5f);
[SerializeField] private Texture2D pipetteCursor;
[SerializeField] private Vector2 pipetteHotspot = new(1f, 30f);
[SerializeField] private Vector2 pipetteHotspot = new(0f, 1f);
[Tooltip("Fully transparent texture shown while a stroke is running - swapping the cursor " +
"image avoids fighting the camera rigs over Cursor.visible (which caused flicker).")]
[SerializeField] private Texture2D blankCursor;
private enum State { None, Normal, Paint, Pipette }
private enum State { None, Normal, Paint, Pipette, Blank }
private State _applied = State.None;
private CursorMode _appliedMode = CursorMode.Auto;
@@ -68,8 +71,11 @@ public class CursorController : MonoBehaviour
{
if (painter.pipette) want = State.Pipette;
// Paint cursor only while painting is actually possible, i.e. the brush preview
// decal is drawn at the hover point this frame; hovering off the mesh -> normal.
else if (painter.BrushPreviewVisible) want = State.Paint;
// decal is drawn at the hover point this frame (hovering off the mesh -> normal,
// also mid-stroke). While the stroke touches the mesh the decal alone marks the
// brush position -> blank cursor.
else if (painter.BrushPreviewVisible)
want = painter.StrokeInProgress ? State.Blank : State.Paint;
}
var mode = CursorMode.Auto;
@@ -83,12 +89,21 @@ public class CursorController : MonoBehaviour
switch (want)
{
case State.Paint: Cursor.SetCursor(paintCursor, paintHotspot, mode); break;
case State.Pipette: Cursor.SetCursor(pipetteCursor, pipetteHotspot, mode); break;
default: Cursor.SetCursor(normalCursor, normalHotspot, mode); break;
case State.Blank: Apply(blankCursor, Vector2.zero, mode); break;
case State.Paint: Apply(paintCursor, paintHotspot, mode); break;
case State.Pipette: Apply(pipetteCursor, pipetteHotspot, mode); break;
default: Apply(normalCursor, normalHotspot, mode); break;
}
}
private static void Apply(Texture2D cursor, Vector2 normalizedHotspot, CursorMode mode)
{
var hotspot = cursor
? Vector2.Scale(normalizedHotspot, new Vector2(cursor.width, cursor.height))
: Vector2.zero;
Cursor.SetCursor(cursor, hotspot, mode);
}
private void OnDisable()
{
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); // back to the OS cursor
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 B

@@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: f924c6157db044a4bd2feac9d02cf38f
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 7
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

After

Width:  |  Height:  |  Size: 286 KiB

@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 334 KiB

@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 255 KiB

@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 0
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0