1273 lines
39 KiB
C#
1273 lines
39 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using UnityEngine.Assertions;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.Sprites;
|
|
using Maskable.Extensions;
|
|
|
|
namespace Maskable
|
|
{
|
|
/// <summary>
|
|
/// Contains some predefined combinations of mask channel weights.
|
|
/// </summary>
|
|
public static class MaskChannel
|
|
{
|
|
public static Color alpha = new Color(0, 0, 0, 1);
|
|
public static Color red = new Color(1, 0, 0, 0);
|
|
public static Color green = new Color(0, 1, 0, 0);
|
|
public static Color blue = new Color(0, 0, 1, 0);
|
|
public static Color gray = new Color(1, 1, 1, 0) / 3.0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Masking is a component that can be added to UI elements for masking the children. It works
|
|
/// like a standard Unity's <see cref="Mask"/> but supports alpha.
|
|
/// </summary>
|
|
[ExecuteInEditMode]
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("UI/Masking", 14)]
|
|
[RequireComponent(typeof(RectTransform))]
|
|
[HelpURL("https://docs.google.com/document/d/1xFZQGn_odhTCokMFR0LyCPXWtqWXN-bBGVS9GETglx8")]
|
|
public class Masking : UIBehaviour, IMasking, ICanvasRaycastFilter
|
|
{
|
|
//
|
|
// How it works:
|
|
//
|
|
// Masking overrides Shader used by child elements. To do it, Masking spawns invisible
|
|
// Maskable components on them on the fly. Maskable implements IMaterialOverride,
|
|
// which allows it to override the shader that performs actual rendering. Use of
|
|
// IMaterialOverride is transparent to the user: a material assigned to Graphic in the
|
|
// inspector is left untouched.
|
|
//
|
|
// Management of Maskables is fully automated. Maskables are kept on the child
|
|
// objects while any Masking parent present. When something changes and Masking parent
|
|
// no longer exists, Maskable is destroyed automatically. So, a user of Masking
|
|
// doesn't have to worry about any component changes in the hierarchy.
|
|
//
|
|
// The replacement shader samples the mask texture and multiply the resulted color
|
|
// accordingly. Masking has the predefined replacement for Unity's default UI shader
|
|
// (and its ETC1-version in Unity 5.4+). So, when Masking 'sees' a material that uses a
|
|
// known shader, it overrides shader by the predefined one. If Masking encounters a
|
|
// material with an unknown shader, it can't do anything reasonable (because it doesn't know
|
|
// what that shader should do). In such a case, Masking will not work and a warning will
|
|
// be displayed in Console. If you want Masking to work with a custom shader, you can
|
|
// manually add support to this shader. For reference how to do it, see
|
|
// CustomWithMasking.shader from included samples.
|
|
//
|
|
// All replacements are cached in Masking instances. By default Unity draws UI with a
|
|
// very small number of material instances (they are spawned one per masking/clipping layer),
|
|
// so, Masking creates a relatively small number of overrides.
|
|
//
|
|
|
|
[SerializeField]
|
|
Shader _defaultShader = null;
|
|
[SerializeField]
|
|
Shader _defaultETC1Shader = null;
|
|
[SerializeField]
|
|
MaskSource _source = MaskSource.Graphic;
|
|
[SerializeField]
|
|
RectTransform _separateMask = null;
|
|
[SerializeField]
|
|
Sprite _sprite = null;
|
|
[SerializeField]
|
|
BorderMode _spriteBorderMode = BorderMode.Simple;
|
|
[SerializeField]
|
|
float _spritePixelsPerUnitMultiplier = 1f;
|
|
[SerializeField]
|
|
Texture _texture = null;
|
|
[SerializeField]
|
|
Rect _textureUVRect = DefaultUVRect;
|
|
[SerializeField]
|
|
Color _channelWeights = MaskChannel.alpha;
|
|
[SerializeField]
|
|
float _raycastThreshold = 0f;
|
|
[SerializeField]
|
|
bool _invertMask = false;
|
|
[SerializeField]
|
|
bool _invertOutsides = false;
|
|
|
|
MaterialReplacements _materials;
|
|
MaterialParameters _parameters;
|
|
WarningReporter _warningReporter;
|
|
Rect _lastMaskRect;
|
|
bool _maskingWasEnabled;
|
|
bool _destroyed;
|
|
bool _dirty;
|
|
|
|
// Cached components
|
|
RectTransform _maskTransform;
|
|
Graphic _graphic;
|
|
Canvas _canvas;
|
|
|
|
public Masking()
|
|
{
|
|
var materialReplacer =
|
|
new MaterialReplacerChain(
|
|
MaterialReplacer.globalReplacers,
|
|
new MaterialReplacerImpl(this));
|
|
_materials = new MaterialReplacements(materialReplacer, m => _parameters.Apply(m));
|
|
_warningReporter = new WarningReporter(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Source of the mask's image.
|
|
/// </summary>
|
|
[Serializable]
|
|
public enum MaskSource
|
|
{
|
|
/// <summary>
|
|
/// The mask image should be taken from the Graphic component of the containing
|
|
/// GameObject. Only Image and RawImage components are supported. If there is no
|
|
/// appropriate Graphic on the GameObject, a solid rectangle of the RectTransform
|
|
/// dimensions will be used.
|
|
/// </summary>
|
|
Graphic,
|
|
/// <summary>
|
|
/// The mask image should be taken from an explicitly specified Sprite. When this mode
|
|
/// is used, spriteBorderMode can also be set to determine how to process Sprite's
|
|
/// borders. If the sprite isn't set, a solid rectangle of the RectTransform dimensions
|
|
/// will be used. This mode is analogous to using an Image with according sprite and
|
|
/// type set.
|
|
/// </summary>
|
|
Sprite,
|
|
/// <summary>
|
|
/// The mask image should be taken from an explicitly specified Texture2D or
|
|
/// RenderTexture. When this mode is used, textureUVRect can also be set to determine
|
|
/// which part of the texture should be used. If the texture isn't set, a solid rectangle
|
|
/// of the RectTransform dimensions will be used. This mode is analogous to using a
|
|
/// RawImage with according texture and uvRect set.
|
|
/// </summary>
|
|
Texture
|
|
}
|
|
|
|
/// <summary>
|
|
/// How Sprite's borders should be processed. It is a reduced set of Image.Type values.
|
|
/// </summary>
|
|
[Serializable]
|
|
public enum BorderMode
|
|
{
|
|
/// <summary>
|
|
/// Sprite should be drawn as a whole, ignoring any borders set. It works the
|
|
/// same way as Unity's Image.Type.Simple.
|
|
/// </summary>
|
|
Simple,
|
|
/// <summary>
|
|
/// Sprite borders should be stretched when the drawn image is larger that the
|
|
/// source. It works the same way as Unity's Image.Type.Sliced.
|
|
/// </summary>
|
|
Sliced,
|
|
/// <summary>
|
|
/// The same as Sliced, but border fragments will be repeated instead of
|
|
/// stretched. It works the same way as Unity's Image.Type.Tiled.
|
|
/// </summary>
|
|
Tiled
|
|
}
|
|
|
|
/// <summary>
|
|
/// Errors encountered during Masking diagnostics. Used by MaskingEditor to display
|
|
/// hints relevant to the current state.
|
|
/// </summary>
|
|
[Flags]
|
|
[Serializable]
|
|
public enum Errors
|
|
{
|
|
NoError = 0,
|
|
UnsupportedShaders = 1 << 0,
|
|
NestedMasks = 1 << 1,
|
|
TightPackedSprite = 1 << 2,
|
|
AlphaSplitSprite = 1 << 3,
|
|
UnsupportedImageType = 1 << 4,
|
|
UnreadableTexture = 1 << 5,
|
|
UnreadableRenderTexture = 1 << 6
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a Shader that should be used as a replacement of the Unity's default UI
|
|
/// shader. If you add Masking in play-time by AddComponent(), you should set
|
|
/// this property manually.
|
|
/// </summary>
|
|
public Shader defaultShader
|
|
{
|
|
get { return _defaultShader; }
|
|
set { SetShader(ref _defaultShader, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a Shader that should be used as a replacement of the Unity's default UI
|
|
/// shader with ETC1 (alpha-split) support. If you use ETC1 textures in UI and
|
|
/// add Masking in play-time by AddComponent(), you should set this property manually.
|
|
/// </summary>
|
|
public Shader defaultETC1Shader
|
|
{
|
|
get { return _defaultETC1Shader; }
|
|
set { SetShader(ref _defaultETC1Shader, value, warnIfNotSet: false); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines from where the mask image should be taken.
|
|
/// </summary>
|
|
public MaskSource source
|
|
{
|
|
get { return _source; }
|
|
set { if (_source != value) Set(ref _source, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a RectTransform that defines the bounds of the mask. Use of a separate
|
|
/// RectTransform allows moving or resizing the mask bounds without affecting children.
|
|
/// When null, the RectTransform of this GameObject is used.
|
|
/// Default value is null.
|
|
/// </summary>
|
|
public RectTransform separateMask
|
|
{
|
|
get { return _separateMask; }
|
|
set
|
|
{
|
|
if (_separateMask != value)
|
|
{
|
|
Set(ref _separateMask, value);
|
|
// We should search them again
|
|
_graphic = null;
|
|
_maskTransform = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a Sprite that should be used as the mask image. This property takes
|
|
/// effect only when source is MaskSource.Sprite.
|
|
/// </summary>
|
|
/// <seealso cref="source"/>
|
|
public Sprite sprite
|
|
{
|
|
get { return _sprite; }
|
|
set { if (_sprite != value) Set(ref _sprite, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies how to draw sprite borders. This property takes effect only when
|
|
/// source is MaskSource.Sprite.
|
|
/// </summary>
|
|
/// <seealso cref="source"/>
|
|
/// <seealso cref="sprite"/>
|
|
public BorderMode spriteBorderMode
|
|
{
|
|
get { return _spriteBorderMode; }
|
|
set { if (_spriteBorderMode != value) Set(ref _spriteBorderMode, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A multiplier that is applied to the pixelsPerUnit property of the selected sprite.
|
|
/// Default value is 1. This property takes effect only when source is MaskSource.Sprite.
|
|
/// </summary>
|
|
/// <seealso cref="source"/>
|
|
/// <seealso cref="sprite"/>
|
|
public float spritePixelsPerUnitMultiplier
|
|
{
|
|
get { return _spritePixelsPerUnitMultiplier; }
|
|
set
|
|
{
|
|
if (_spritePixelsPerUnitMultiplier != value)
|
|
Set(ref _spritePixelsPerUnitMultiplier, ClampPixelsPerUnitMultiplier(value));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a Texture2D that should be used as the mask image. This property takes
|
|
/// effect only when the source is MaskSource.Texture. This and <see cref="renderTexture"/>
|
|
/// properties are mutually exclusive.
|
|
/// </summary>
|
|
/// <seealso cref="renderTexture"/>
|
|
public Texture2D texture
|
|
{
|
|
get { return _texture as Texture2D; }
|
|
set { if (_texture != value) Set(ref _texture, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a RenderTexture that should be used as the mask image. This property takes
|
|
/// effect only when the source is MaskSource.Texture. This and <see cref="texture"/>
|
|
/// properties are mutually exclusive.
|
|
/// </summary>
|
|
/// <seealso cref="texture"/>
|
|
public RenderTexture renderTexture
|
|
{
|
|
get { return _texture as RenderTexture; }
|
|
set { if (_texture != value) Set(ref _texture, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies a normalized UV-space rectangle defining the image part that should be used as
|
|
/// the mask image. This property takes effect only when the source is MaskSource.Texture.
|
|
/// A value is set in normalized coordinates. The default value is (0, 0, 1, 1), which means
|
|
/// that the whole texture is used.
|
|
/// </summary>
|
|
public Rect textureUVRect
|
|
{
|
|
get { return _textureUVRect; }
|
|
set { if (_textureUVRect != value) Set(ref _textureUVRect, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies weights of the color channels of the mask. The color sampled from the mask
|
|
/// texture is multiplied by this value, after what all components are summed up together.
|
|
/// That is, the final mask value is calculated as:
|
|
/// color = `pixel-from-mask` * channelWeights
|
|
/// value = color.r + color.g + color.b + color.a
|
|
/// The `value` is a number by which the resulting pixel's alpha is multiplied. As you
|
|
/// can see, the result value isn't normalized, so, you should account it while defining
|
|
/// custom values for this property.
|
|
/// Static class MaskChannel contains some useful predefined values. You can use they
|
|
/// as example of how mask calculation works.
|
|
/// The default value is MaskChannel.alpha.
|
|
/// </summary>
|
|
public Color channelWeights
|
|
{
|
|
get { return _channelWeights; }
|
|
set { if (_channelWeights != value) Set(ref _channelWeights, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies the minimum mask value that the point should have for an input event to pass.
|
|
/// If the value sampled from the mask is greater or equal this value, the input event
|
|
/// is considered 'hit'. The mask value is compared with raycastThreshold after
|
|
/// channelWeights applied.
|
|
/// The default value is 0, which means that any pixel belonging to RectTransform is
|
|
/// considered in input events. If you specify the value greater than 0, the mask's
|
|
/// texture should be readable and it should be not a RenderTexture.
|
|
/// Accepts values in range [0..1].
|
|
/// </summary>
|
|
public float raycastThreshold
|
|
{
|
|
get { return _raycastThreshold; }
|
|
set { _raycastThreshold = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// If set, mask values inside the mask rectangle will be inverted. In this case mask's
|
|
/// zero value (taking <see cref="channelWeights"/> into account) will be treated as one
|
|
/// and vice versa. The mask rectangle is the RectTransform of the GameObject this
|
|
/// component is attached to or <see cref="separateMask"/> if it's not null.
|
|
/// The default value is false.
|
|
/// </summary>
|
|
/// <seealso cref="invertOutsides"/>
|
|
public bool invertMask
|
|
{
|
|
get { return _invertMask; }
|
|
set { if (_invertMask != value) Set(ref _invertMask, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// If set, mask values outside the mask rectangle will be inverted. By default, everything
|
|
/// outside the mask rectangle has zero mask value. When this property is set, the mask
|
|
/// outsides will have value one, which means that everything outside the mask will be
|
|
/// visible. The mask rectangle is the RectTransform of the GameObject this component
|
|
/// is attached to or <see cref="separateMask"/> if it's not null.
|
|
/// The default value is false.
|
|
/// </summary>
|
|
/// <seealso cref="invertMask"/>
|
|
public bool invertOutsides
|
|
{
|
|
get { return _invertOutsides; }
|
|
set { if (_invertOutsides != value) Set(ref _invertOutsides, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if Masking does raycast filtering, that is if the masked areas are
|
|
/// transparent to input.
|
|
/// </summary>
|
|
public bool isUsingRaycastFiltering
|
|
{
|
|
get { return _raycastThreshold > 0f; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if masking is currently active.
|
|
/// </summary>
|
|
public bool isMaskingEnabled
|
|
{
|
|
get { return isActiveAndEnabled && canvas; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks for errors and returns them as flags. It is used in the editor to determine
|
|
/// which warnings should be displayed.
|
|
/// </summary>
|
|
public Errors PollErrors() { return new Diagnostics(this).PollErrors(); }
|
|
|
|
// ICanvasRaycastFilter
|
|
public bool IsRaycastLocationValid(Vector2 sp, Camera cam)
|
|
{
|
|
Vector2 localPos;
|
|
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(maskTransform, sp, cam, out localPos)) return false;
|
|
if (!Mathr.Inside(localPos, LocalMaskRect(Vector4.zero))) return _invertOutsides;
|
|
if (!_parameters.texture) return true;
|
|
if (!isUsingRaycastFiltering) return true;
|
|
float mask;
|
|
var sampleResult = _parameters.SampleMask(localPos, out mask);
|
|
_warningReporter.TextureRead(_parameters.texture, sampleResult);
|
|
if (sampleResult != MaterialParameters.SampleMaskResult.Success)
|
|
return true;
|
|
if (_invertMask)
|
|
mask = 1 - mask;
|
|
return mask >= _raycastThreshold;
|
|
}
|
|
|
|
protected override void Start()
|
|
{
|
|
base.Start();
|
|
WarnIfDefaultShaderIsNotSet();
|
|
}
|
|
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
SubscribeOnWillRenderCanvases();
|
|
SpawnMaskablesInChildren(transform);
|
|
FindGraphic();
|
|
if (isMaskingEnabled)
|
|
UpdateMaskParameters();
|
|
NotifyChildrenThatMaskMightChanged();
|
|
}
|
|
|
|
protected override void OnDisable()
|
|
{
|
|
base.OnDisable();
|
|
UnsubscribeFromWillRenderCanvases();
|
|
if (_graphic)
|
|
{
|
|
_graphic.UnregisterDirtyVerticesCallback(OnGraphicDirty);
|
|
_graphic.UnregisterDirtyMaterialCallback(OnGraphicDirty);
|
|
_graphic = null;
|
|
}
|
|
NotifyChildrenThatMaskMightChanged();
|
|
DestroyMaterials();
|
|
}
|
|
|
|
protected override void OnDestroy()
|
|
{
|
|
base.OnDestroy();
|
|
_destroyed = true;
|
|
NotifyChildrenThatMaskMightChanged();
|
|
}
|
|
|
|
protected virtual void LateUpdate()
|
|
{
|
|
var maskingEnabled = isMaskingEnabled;
|
|
if (maskingEnabled)
|
|
{
|
|
if (_maskingWasEnabled != maskingEnabled)
|
|
SpawnMaskablesInChildren(transform);
|
|
var prevGraphic = _graphic;
|
|
FindGraphic();
|
|
if (_lastMaskRect != maskTransform.rect
|
|
|| !ReferenceEquals(_graphic, prevGraphic))
|
|
_dirty = true;
|
|
}
|
|
_maskingWasEnabled = maskingEnabled;
|
|
}
|
|
|
|
protected override void OnRectTransformDimensionsChange()
|
|
{
|
|
base.OnRectTransformDimensionsChange();
|
|
_dirty = true;
|
|
}
|
|
|
|
protected override void OnDidApplyAnimationProperties()
|
|
{
|
|
base.OnDidApplyAnimationProperties();
|
|
_dirty = true;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
protected override void OnValidate() {
|
|
base.OnValidate();
|
|
_spritePixelsPerUnitMultiplier = ClampPixelsPerUnitMultiplier(_spritePixelsPerUnitMultiplier);
|
|
_dirty = true;
|
|
_maskTransform = null;
|
|
_graphic = null;
|
|
}
|
|
#endif
|
|
|
|
static float ClampPixelsPerUnitMultiplier(float value)
|
|
{
|
|
return Mathf.Max(value, 0.01f);
|
|
}
|
|
|
|
protected override void OnTransformParentChanged()
|
|
{
|
|
base.OnTransformParentChanged();
|
|
_canvas = null;
|
|
_dirty = true;
|
|
}
|
|
|
|
protected override void OnCanvasHierarchyChanged()
|
|
{
|
|
base.OnCanvasHierarchyChanged();
|
|
_canvas = null;
|
|
_dirty = true;
|
|
NotifyChildrenThatMaskMightChanged();
|
|
}
|
|
|
|
void OnTransformChildrenChanged()
|
|
{
|
|
SpawnMaskablesInChildren(transform);
|
|
}
|
|
|
|
void SubscribeOnWillRenderCanvases()
|
|
{
|
|
// To get called when layout and graphics update is finished we should
|
|
// subscribe after CanvasUpdateRegistry. CanvasUpdateRegistry subscribes
|
|
// in his constructor, so we force its execution.
|
|
Touch(CanvasUpdateRegistry.instance);
|
|
Canvas.willRenderCanvases += OnWillRenderCanvases;
|
|
}
|
|
|
|
void UnsubscribeFromWillRenderCanvases()
|
|
{
|
|
Canvas.willRenderCanvases -= OnWillRenderCanvases;
|
|
}
|
|
|
|
void OnWillRenderCanvases()
|
|
{
|
|
// To be sure that mask will match the state of another drawn UI elements,
|
|
// we update material parameters when layout and graphic update is done,
|
|
// just before actual rendering.
|
|
if (isMaskingEnabled)
|
|
UpdateMaskParameters();
|
|
}
|
|
|
|
static T Touch<T>(T obj) { return obj; }
|
|
|
|
static readonly Rect DefaultUVRect = new Rect(0, 0, 1, 1);
|
|
|
|
RectTransform maskTransform
|
|
{
|
|
get
|
|
{
|
|
return
|
|
_maskTransform
|
|
? _maskTransform
|
|
: (_maskTransform = _separateMask ? _separateMask : GetComponent<RectTransform>());
|
|
}
|
|
}
|
|
|
|
Canvas canvas
|
|
{
|
|
get { return _canvas ? _canvas : (_canvas = NearestEnabledCanvas()); }
|
|
}
|
|
|
|
bool isBasedOnGraphic { get { return _source == MaskSource.Graphic; } }
|
|
|
|
bool IMasking.isAlive { get { return this && !_destroyed; } }
|
|
|
|
Material IMasking.GetReplacement(Material original)
|
|
{
|
|
Assert.IsTrue(isActiveAndEnabled);
|
|
return _materials.Get(original);
|
|
}
|
|
|
|
void IMasking.ReleaseReplacement(Material replacement)
|
|
{
|
|
_materials.Release(replacement);
|
|
}
|
|
|
|
void IMasking.UpdateTransformChildren(Transform transform)
|
|
{
|
|
SpawnMaskablesInChildren(transform);
|
|
}
|
|
|
|
void OnGraphicDirty()
|
|
{
|
|
if (isBasedOnGraphic) // TODO is this check neccessary?
|
|
_dirty = true;
|
|
}
|
|
|
|
void FindGraphic()
|
|
{
|
|
if (!_graphic && isBasedOnGraphic)
|
|
{
|
|
_graphic = maskTransform.GetComponent<Graphic>();
|
|
if (_graphic)
|
|
{
|
|
_graphic.RegisterDirtyVerticesCallback(OnGraphicDirty);
|
|
_graphic.RegisterDirtyMaterialCallback(OnGraphicDirty);
|
|
}
|
|
}
|
|
}
|
|
|
|
Canvas NearestEnabledCanvas()
|
|
{
|
|
// It's a rare operation, so I do not optimize it with static lists
|
|
var canvases = GetComponentsInParent<Canvas>(false);
|
|
for (int i = 0; i < canvases.Length; ++i)
|
|
if (canvases[i].isActiveAndEnabled)
|
|
return canvases[i];
|
|
return null;
|
|
}
|
|
|
|
void UpdateMaskParameters()
|
|
{
|
|
Assert.IsTrue(isMaskingEnabled);
|
|
if (_dirty || maskTransform.hasChanged)
|
|
{
|
|
CalculateMaskParameters();
|
|
maskTransform.hasChanged = false;
|
|
_lastMaskRect = maskTransform.rect;
|
|
_dirty = false;
|
|
}
|
|
_materials.ApplyAll();
|
|
}
|
|
|
|
void SpawnMaskablesInChildren(Transform root)
|
|
{
|
|
using (new ClearListAtExit<Maskable>(s_maskables))
|
|
for (int i = 0; i < root.childCount; ++i)
|
|
{
|
|
var child = root.GetChild(i);
|
|
child.GetComponents(s_maskables);
|
|
Assert.IsTrue(s_maskables.Count <= 1);
|
|
if (s_maskables.Count == 0)
|
|
child.gameObject.AddComponent<Maskable>();
|
|
}
|
|
}
|
|
|
|
void InvalidateChildren()
|
|
{
|
|
ForEachChildMaskable(x => x.Invalidate());
|
|
}
|
|
|
|
void NotifyChildrenThatMaskMightChanged()
|
|
{
|
|
ForEachChildMaskable(x => x.MaskMightChanged());
|
|
}
|
|
|
|
void ForEachChildMaskable(Action<Maskable> f)
|
|
{
|
|
transform.GetComponentsInChildren(s_maskables);
|
|
using (new ClearListAtExit<Maskable>(s_maskables))
|
|
for (int i = 0; i < s_maskables.Count; ++i)
|
|
{
|
|
var maskable = s_maskables[i];
|
|
if (maskable && maskable.gameObject != gameObject)
|
|
f(maskable);
|
|
}
|
|
}
|
|
|
|
void DestroyMaterials()
|
|
{
|
|
_materials.DestroyAllAndClear();
|
|
}
|
|
|
|
struct SourceParameters
|
|
{
|
|
public Image image;
|
|
public Sprite sprite;
|
|
public BorderMode spriteBorderMode;
|
|
public float spritePixelsPerUnit;
|
|
public Texture texture;
|
|
public Rect textureUVRect;
|
|
}
|
|
|
|
const float DefaultPixelsPerUnit = 100f;
|
|
|
|
SourceParameters DeduceSourceParameters()
|
|
{
|
|
var result = new SourceParameters();
|
|
switch (_source)
|
|
{
|
|
case MaskSource.Graphic:
|
|
if (_graphic is Image)
|
|
{
|
|
var image = (Image)_graphic;
|
|
var sprite = image.sprite;
|
|
result.image = image;
|
|
result.sprite = sprite;
|
|
result.spriteBorderMode = ImageTypeToBorderMode(image.type);
|
|
if (sprite)
|
|
{
|
|
#if UNITY_2019_2_OR_NEWER
|
|
result.spritePixelsPerUnit = sprite.pixelsPerUnit * image.pixelsPerUnitMultiplier;
|
|
#else
|
|
result.spritePixelsPerUnit = sprite.pixelsPerUnit;
|
|
#endif
|
|
result.texture = sprite.texture;
|
|
}
|
|
else
|
|
result.spritePixelsPerUnit = DefaultPixelsPerUnit;
|
|
}
|
|
else if (_graphic is RawImage)
|
|
{
|
|
var rawImage = (RawImage)_graphic;
|
|
result.texture = rawImage.texture;
|
|
result.textureUVRect = rawImage.uvRect;
|
|
}
|
|
break;
|
|
case MaskSource.Sprite:
|
|
result.sprite = _sprite;
|
|
result.spriteBorderMode = _spriteBorderMode;
|
|
if (_sprite)
|
|
{
|
|
result.spritePixelsPerUnit = _sprite.pixelsPerUnit * _spritePixelsPerUnitMultiplier;
|
|
result.texture = _sprite.texture;
|
|
}
|
|
else
|
|
result.spritePixelsPerUnit = DefaultPixelsPerUnit;
|
|
break;
|
|
case MaskSource.Texture:
|
|
result.texture = _texture;
|
|
result.textureUVRect = _textureUVRect;
|
|
break;
|
|
default:
|
|
Debug.LogAssertionFormat(this, "Unknown MaskSource: {0}", _source);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static BorderMode ImageTypeToBorderMode(Image.Type type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case Image.Type.Simple: return BorderMode.Simple;
|
|
case Image.Type.Sliced: return BorderMode.Sliced;
|
|
case Image.Type.Tiled: return BorderMode.Tiled;
|
|
default:
|
|
return BorderMode.Simple;
|
|
}
|
|
}
|
|
|
|
public static bool IsImageTypeSupported(Image.Type type)
|
|
{
|
|
return type == Image.Type.Simple
|
|
|| type == Image.Type.Sliced
|
|
|| type == Image.Type.Tiled;
|
|
}
|
|
|
|
void CalculateMaskParameters()
|
|
{
|
|
var sourceParams = DeduceSourceParameters();
|
|
_warningReporter.ImageUsed(sourceParams.image);
|
|
var spriteErrors = Diagnostics.CheckSprite(sourceParams.sprite);
|
|
_warningReporter.SpriteUsed(sourceParams.sprite, spriteErrors);
|
|
if (sourceParams.sprite)
|
|
{
|
|
if (spriteErrors == Errors.NoError)
|
|
CalculateSpriteBased(sourceParams.sprite, sourceParams.spriteBorderMode, sourceParams.spritePixelsPerUnit);
|
|
else
|
|
CalculateSolidFill();
|
|
}
|
|
else if (sourceParams.texture)
|
|
CalculateTextureBased(sourceParams.texture, sourceParams.textureUVRect);
|
|
else
|
|
CalculateSolidFill();
|
|
}
|
|
|
|
void CalculateSpriteBased(Sprite sprite, BorderMode borderMode, float spritePixelsPerUnit)
|
|
{
|
|
FillCommonParameters();
|
|
var inner = DataUtility.GetInnerUV(sprite);
|
|
var outer = DataUtility.GetOuterUV(sprite);
|
|
var padding = DataUtility.GetPadding(sprite);
|
|
var fullMaskRect = LocalMaskRect(Vector4.zero);
|
|
_parameters.maskRectUV = outer;
|
|
if (borderMode == BorderMode.Simple)
|
|
{
|
|
var normalizedPadding = Mathr.Div(padding, sprite.rect.size);
|
|
_parameters.maskRect = Mathr.ApplyBorder(fullMaskRect, Mathr.Mul(normalizedPadding, Mathr.Size(fullMaskRect)));
|
|
}
|
|
else
|
|
{
|
|
var spriteToCanvasScale = SpriteToCanvasScale(spritePixelsPerUnit);
|
|
_parameters.maskRect = Mathr.ApplyBorder(fullMaskRect, padding * spriteToCanvasScale);
|
|
var adjustedBorder = AdjustBorders(sprite.border * spriteToCanvasScale, fullMaskRect);
|
|
_parameters.maskBorder = LocalMaskRect(adjustedBorder);
|
|
_parameters.maskBorderUV = inner;
|
|
}
|
|
_parameters.texture = sprite.texture;
|
|
_parameters.borderMode = borderMode;
|
|
if (borderMode == BorderMode.Tiled)
|
|
_parameters.tileRepeat = MaskRepeat(sprite, spritePixelsPerUnit, _parameters.maskBorder);
|
|
}
|
|
|
|
static Vector4 AdjustBorders(Vector4 border, Vector4 rect)
|
|
{
|
|
// Copied from Unity's Image.
|
|
var size = Mathr.Size(rect);
|
|
for (int axis = 0; axis <= 1; axis++)
|
|
{
|
|
// If the rect is smaller than the combined borders, then there's not room for
|
|
// the borders at their normal size. In order to avoid artefacts with overlapping
|
|
// borders, we scale the borders down to fit.
|
|
float combinedBorders = border[axis] + border[axis + 2];
|
|
if (size[axis] < combinedBorders && combinedBorders != 0)
|
|
{
|
|
float borderScaleRatio = size[axis] / combinedBorders;
|
|
border[axis] *= borderScaleRatio;
|
|
border[axis + 2] *= borderScaleRatio;
|
|
}
|
|
}
|
|
return border;
|
|
}
|
|
|
|
void CalculateTextureBased(Texture texture, Rect uvRect)
|
|
{
|
|
FillCommonParameters();
|
|
_parameters.maskRect = LocalMaskRect(Vector4.zero);
|
|
_parameters.maskRectUV = Mathr.ToVector(uvRect);
|
|
_parameters.texture = texture;
|
|
_parameters.borderMode = BorderMode.Simple;
|
|
}
|
|
|
|
void CalculateSolidFill()
|
|
{
|
|
CalculateTextureBased(null, DefaultUVRect);
|
|
}
|
|
|
|
void FillCommonParameters()
|
|
{
|
|
_parameters.worldToMask = WorldToMask();
|
|
_parameters.maskChannelWeights = _channelWeights;
|
|
_parameters.invertMask = _invertMask;
|
|
_parameters.invertOutsides = _invertOutsides;
|
|
}
|
|
|
|
float SpriteToCanvasScale(float spritePixelsPerUnit)
|
|
{
|
|
var canvasPixelsPerUnit = canvas ? canvas.referencePixelsPerUnit : 100;
|
|
return canvasPixelsPerUnit / spritePixelsPerUnit;
|
|
}
|
|
|
|
Matrix4x4 WorldToMask()
|
|
{
|
|
return maskTransform.worldToLocalMatrix * canvas.rootCanvas.transform.localToWorldMatrix;
|
|
}
|
|
|
|
Vector4 LocalMaskRect(Vector4 border)
|
|
{
|
|
return Mathr.ApplyBorder(Mathr.ToVector(maskTransform.rect), border);
|
|
}
|
|
|
|
Vector2 MaskRepeat(Sprite sprite, float spritePixelsPerUnit, Vector4 centralPart)
|
|
{
|
|
var textureCenter = Mathr.ApplyBorder(Mathr.ToVector(sprite.rect), sprite.border);
|
|
return Mathr.Div(Mathr.Size(centralPart) * SpriteToCanvasScale(spritePixelsPerUnit), Mathr.Size(textureCenter));
|
|
}
|
|
|
|
void WarnIfDefaultShaderIsNotSet()
|
|
{
|
|
if (!_defaultShader)
|
|
Debug.LogWarning("Masking may not work because its defaultShader is not set", this);
|
|
}
|
|
|
|
|
|
void Set<T>(ref T field, T value)
|
|
{
|
|
field = value;
|
|
_dirty = true;
|
|
}
|
|
|
|
void SetShader(ref Shader field, Shader value, bool warnIfNotSet = true)
|
|
{
|
|
if (field != value)
|
|
{
|
|
field = value;
|
|
if (warnIfNotSet)
|
|
WarnIfDefaultShaderIsNotSet();
|
|
DestroyMaterials();
|
|
InvalidateChildren();
|
|
}
|
|
}
|
|
|
|
static readonly List<Masking> s_masks = new List<Masking>();
|
|
static readonly List<Maskable> s_maskables = new List<Maskable>();
|
|
|
|
class MaterialReplacerImpl : IMaterialReplacer
|
|
{
|
|
readonly Masking _owner;
|
|
|
|
public MaterialReplacerImpl(Masking owner)
|
|
{
|
|
// Pass whole owner instead of just shaders because they can be changed dynamically.
|
|
_owner = owner;
|
|
}
|
|
|
|
public int order { get { return 0; } }
|
|
|
|
public Material Replace(Material original)
|
|
{
|
|
if (original == null || original.HasDefaultUIShader())
|
|
return Replace(original, _owner._defaultShader);
|
|
#if UNITY_5_4_OR_NEWER
|
|
else if (original.HasDefaultETC1UIShader())
|
|
return Replace(original, _owner._defaultETC1Shader);
|
|
#endif
|
|
else if (original.SupportsMasking())
|
|
return new Material(original);
|
|
else
|
|
return null;
|
|
}
|
|
|
|
static Material Replace(Material original, Shader defaultReplacementShader)
|
|
{
|
|
var replacement = defaultReplacementShader
|
|
? new Material(defaultReplacementShader)
|
|
: null;
|
|
if (replacement && original)
|
|
replacement.CopyPropertiesFromMaterial(original);
|
|
return replacement;
|
|
}
|
|
}
|
|
|
|
// Various operations on a Rect represented as Vector4 (xMin, yMin, xMax, yMax).
|
|
static class Mathr
|
|
{
|
|
public static Vector4 ToVector(Rect r) { return new Vector4(r.xMin, r.yMin, r.xMax, r.yMax); }
|
|
public static Vector4 Div(Vector4 v, Vector2 s) { return new Vector4(v.x / s.x, v.y / s.y, v.z / s.x, v.w / s.y); }
|
|
public static Vector2 Div(Vector2 v, Vector2 s) { return new Vector2(v.x / s.x, v.y / s.y); }
|
|
public static Vector4 Mul(Vector4 v, Vector2 s) { return new Vector4(v.x * s.x, v.y * s.y, v.z * s.x, v.w * s.y); }
|
|
public static Vector2 Size(Vector4 r) { return new Vector2(r.z - r.x, r.w - r.y); }
|
|
public static Vector4 Move(Vector4 v, Vector2 o) { return new Vector4(v.x + o.x, v.y + o.y, v.z + o.x, v.w + o.y); }
|
|
|
|
public static Vector4 BorderOf(Vector4 outer, Vector4 inner)
|
|
{
|
|
return new Vector4(inner.x - outer.x, inner.y - outer.y, outer.z - inner.z, outer.w - inner.w);
|
|
}
|
|
|
|
public static Vector4 ApplyBorder(Vector4 v, Vector4 b)
|
|
{
|
|
return new Vector4(v.x + b.x, v.y + b.y, v.z - b.z, v.w - b.w);
|
|
}
|
|
|
|
public static Vector2 Min(Vector4 r) { return new Vector2(r.x, r.y); }
|
|
public static Vector2 Max(Vector4 r) { return new Vector2(r.z, r.w); }
|
|
|
|
public static Vector2 Remap(Vector2 c, Vector4 from, Vector4 to)
|
|
{
|
|
var fromSize = Max(from) - Min(from);
|
|
var toSize = Max(to) - Min(to);
|
|
return Vector2.Scale(Div((c - Min(from)), fromSize), toSize) + Min(to);
|
|
}
|
|
|
|
public static bool Inside(Vector2 v, Vector4 r)
|
|
{
|
|
return v.x >= r.x && v.y >= r.y && v.x <= r.z && v.y <= r.w;
|
|
}
|
|
}
|
|
|
|
struct MaterialParameters
|
|
{
|
|
public Vector4 maskRect;
|
|
public Vector4 maskBorder;
|
|
public Vector4 maskRectUV;
|
|
public Vector4 maskBorderUV;
|
|
public Vector2 tileRepeat;
|
|
public Color maskChannelWeights;
|
|
public Matrix4x4 worldToMask;
|
|
public Texture texture;
|
|
public BorderMode borderMode;
|
|
public bool invertMask;
|
|
public bool invertOutsides;
|
|
|
|
public Texture activeTexture { get { return texture ? texture : Texture2D.whiteTexture; } }
|
|
|
|
public enum SampleMaskResult { Success, NonReadable, NonTexture2D }
|
|
|
|
public SampleMaskResult SampleMask(Vector2 localPos, out float mask)
|
|
{
|
|
mask = 0;
|
|
var texture2D = texture as Texture2D;
|
|
if (!texture2D)
|
|
return SampleMaskResult.NonTexture2D;
|
|
var uv = XY2UV(localPos);
|
|
try
|
|
{
|
|
mask = MaskValue(texture2D.GetPixelBilinear(uv.x, uv.y));
|
|
return SampleMaskResult.Success;
|
|
}
|
|
catch (UnityException)
|
|
{
|
|
return SampleMaskResult.NonReadable;
|
|
}
|
|
}
|
|
|
|
public void Apply(Material mat)
|
|
{
|
|
mat.SetTexture(Ids.Masking, activeTexture);
|
|
mat.SetVector(Ids.Masking_Rect, maskRect);
|
|
mat.SetVector(Ids.Masking_UVRect, maskRectUV);
|
|
mat.SetColor(Ids.Masking_ChannelWeights, maskChannelWeights);
|
|
mat.SetMatrix(Ids.Masking_WorldToMask, worldToMask);
|
|
mat.SetFloat(Ids.Masking_InvertMask, invertMask ? 1 : 0);
|
|
mat.SetFloat(Ids.Masking_InvertOutsides, invertOutsides ? 1 : 0);
|
|
mat.EnableKeyword("Masking_SIMPLE", borderMode == BorderMode.Simple);
|
|
mat.EnableKeyword("Masking_SLICED", borderMode == BorderMode.Sliced);
|
|
mat.EnableKeyword("Masking_TILED", borderMode == BorderMode.Tiled);
|
|
if (borderMode != BorderMode.Simple)
|
|
{
|
|
mat.SetVector(Ids.Masking_BorderRect, maskBorder);
|
|
mat.SetVector(Ids.Masking_UVBorderRect, maskBorderUV);
|
|
if (borderMode == BorderMode.Tiled)
|
|
mat.SetVector(Ids.Masking_TileRepeat, tileRepeat);
|
|
}
|
|
}
|
|
|
|
// The following functions performs the same logic as functions from Masking.cginc.
|
|
// They implemented it a bit different way, because there is no such convenient
|
|
// vector operations in Unity/C# and conditions are much cheaper here.
|
|
|
|
Vector2 XY2UV(Vector2 localPos)
|
|
{
|
|
switch (borderMode)
|
|
{
|
|
case BorderMode.Simple: return MapSimple(localPos);
|
|
case BorderMode.Sliced: return MapBorder(localPos, repeat: false);
|
|
case BorderMode.Tiled: return MapBorder(localPos, repeat: true);
|
|
default:
|
|
Debug.LogAssertion("Unknown BorderMode");
|
|
return MapSimple(localPos);
|
|
}
|
|
}
|
|
|
|
Vector2 MapSimple(Vector2 localPos)
|
|
{
|
|
return Mathr.Remap(localPos, maskRect, maskRectUV);
|
|
}
|
|
|
|
Vector2 MapBorder(Vector2 localPos, bool repeat)
|
|
{
|
|
return
|
|
new Vector2(
|
|
Inset(
|
|
localPos.x,
|
|
maskRect.x, maskBorder.x, maskBorder.z, maskRect.z,
|
|
maskRectUV.x, maskBorderUV.x, maskBorderUV.z, maskRectUV.z,
|
|
repeat ? tileRepeat.x : 1),
|
|
Inset(
|
|
localPos.y,
|
|
maskRect.y, maskBorder.y, maskBorder.w, maskRect.w,
|
|
maskRectUV.y, maskBorderUV.y, maskBorderUV.w, maskRectUV.w,
|
|
repeat ? tileRepeat.y : 1));
|
|
}
|
|
|
|
float Inset(float v, float x1, float x2, float u1, float u2, float repeat = 1)
|
|
{
|
|
var w = (x2 - x1);
|
|
return Mathf.Lerp(u1, u2, w != 0.0f ? Frac((v - x1) / w * repeat) : 0.0f);
|
|
}
|
|
|
|
float Inset(float v, float x1, float x2, float x3, float x4, float u1, float u2, float u3, float u4, float repeat = 1)
|
|
{
|
|
if (v < x2)
|
|
return Inset(v, x1, x2, u1, u2);
|
|
else if (v < x3)
|
|
return Inset(v, x2, x3, u2, u3, repeat);
|
|
else
|
|
return Inset(v, x3, x4, u3, u4);
|
|
}
|
|
|
|
float Frac(float v) { return v - Mathf.Floor(v); }
|
|
|
|
float MaskValue(Color mask)
|
|
{
|
|
var value = mask * maskChannelWeights;
|
|
return value.a + value.r + value.g + value.b;
|
|
}
|
|
|
|
static class Ids
|
|
{
|
|
public static readonly int Masking = Shader.PropertyToID("_Masking");
|
|
public static readonly int Masking_Rect = Shader.PropertyToID("_Masking_Rect");
|
|
public static readonly int Masking_UVRect = Shader.PropertyToID("_Masking_UVRect");
|
|
public static readonly int Masking_ChannelWeights = Shader.PropertyToID("_Masking_ChannelWeights");
|
|
public static readonly int Masking_WorldToMask = Shader.PropertyToID("_Masking_WorldToMask");
|
|
public static readonly int Masking_BorderRect = Shader.PropertyToID("_Masking_BorderRect");
|
|
public static readonly int Masking_UVBorderRect = Shader.PropertyToID("_Masking_UVBorderRect");
|
|
public static readonly int Masking_TileRepeat = Shader.PropertyToID("_Masking_TileRepeat");
|
|
public static readonly int Masking_InvertMask = Shader.PropertyToID("_Masking_InvertMask");
|
|
public static readonly int Masking_InvertOutsides = Shader.PropertyToID("_Masking_InvertOutsides");
|
|
}
|
|
}
|
|
|
|
struct Diagnostics
|
|
{
|
|
Masking _Masking;
|
|
|
|
public Diagnostics(Masking Masking) { _Masking = Masking; }
|
|
|
|
public Errors PollErrors()
|
|
{
|
|
var Masking = _Masking; // for use in lambda
|
|
var result = Errors.NoError;
|
|
Masking.GetComponentsInChildren(s_maskables);
|
|
using (new ClearListAtExit<Maskable>(s_maskables))
|
|
if (s_maskables.Any(m => ReferenceEquals(m.mask, Masking) && m.shaderIsNotSupported))
|
|
result |= Errors.UnsupportedShaders;
|
|
if (ThereAreNestedMasks())
|
|
result |= Errors.NestedMasks;
|
|
result |= CheckSprite(sprite);
|
|
result |= CheckImage();
|
|
result |= CheckTexture();
|
|
return result;
|
|
}
|
|
|
|
public static Errors CheckSprite(Sprite sprite)
|
|
{
|
|
var result = Errors.NoError;
|
|
if (!sprite) return result;
|
|
if (sprite.packed && sprite.packingMode == SpritePackingMode.Tight)
|
|
result |= Errors.TightPackedSprite;
|
|
if (sprite.associatedAlphaSplitTexture)
|
|
result |= Errors.AlphaSplitSprite;
|
|
return result;
|
|
}
|
|
|
|
Image image { get { return _Masking.DeduceSourceParameters().image; } }
|
|
Sprite sprite { get { return _Masking.DeduceSourceParameters().sprite; } }
|
|
Texture texture { get { return _Masking.DeduceSourceParameters().texture; } }
|
|
|
|
bool ThereAreNestedMasks()
|
|
{
|
|
var Masking = _Masking; // for use in lambda
|
|
var result = false;
|
|
using (new ClearListAtExit<Masking>(s_masks))
|
|
{
|
|
Masking.GetComponentsInParent(false, s_masks);
|
|
result |= s_masks.Any(x => AreCompeting(Masking, x));
|
|
Masking.GetComponentsInChildren(false, s_masks);
|
|
result |= s_masks.Any(x => AreCompeting(Masking, x));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Errors CheckImage()
|
|
{
|
|
var result = Errors.NoError;
|
|
if (!_Masking.isBasedOnGraphic) return result;
|
|
if (image && !IsImageTypeSupported(image.type))
|
|
result |= Errors.UnsupportedImageType;
|
|
return result;
|
|
}
|
|
|
|
Errors CheckTexture()
|
|
{
|
|
var result = Errors.NoError;
|
|
if (_Masking.isUsingRaycastFiltering && texture)
|
|
{
|
|
var texture2D = texture as Texture2D;
|
|
if (!texture2D)
|
|
result |= Errors.UnreadableRenderTexture;
|
|
else if (!IsReadable(texture2D))
|
|
result |= Errors.UnreadableTexture;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool AreCompeting(Masking Masking, Masking other)
|
|
{
|
|
Assert.IsNotNull(other);
|
|
return Masking.isMaskingEnabled
|
|
&& Masking != other
|
|
&& other.isMaskingEnabled
|
|
&& Masking.canvas.rootCanvas == other.canvas.rootCanvas
|
|
&& !SelectChild(Masking, other).canvas.overrideSorting;
|
|
}
|
|
|
|
static T SelectChild<T>(T first, T second) where T : Component
|
|
{
|
|
Assert.IsNotNull(first);
|
|
Assert.IsNotNull(second);
|
|
return first.transform.IsChildOf(second.transform) ? first : second;
|
|
}
|
|
|
|
static bool IsReadable(Texture2D texture)
|
|
{
|
|
try
|
|
{
|
|
texture.GetPixel(0, 0);
|
|
return true;
|
|
}
|
|
catch (UnityException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct WarningReporter
|
|
{
|
|
UnityEngine.Object _owner;
|
|
Texture _lastReadTexture;
|
|
Sprite _lastUsedSprite;
|
|
Sprite _lastUsedImageSprite;
|
|
Image.Type _lastUsedImageType;
|
|
|
|
public WarningReporter(UnityEngine.Object owner)
|
|
{
|
|
_owner = owner;
|
|
_lastReadTexture = null;
|
|
_lastUsedSprite = null;
|
|
_lastUsedImageSprite = null;
|
|
_lastUsedImageType = Image.Type.Simple;
|
|
}
|
|
|
|
public void TextureRead(Texture texture, MaterialParameters.SampleMaskResult sampleResult)
|
|
{
|
|
if (_lastReadTexture == texture)
|
|
return;
|
|
_lastReadTexture = texture;
|
|
switch (sampleResult)
|
|
{
|
|
case MaterialParameters.SampleMaskResult.NonReadable:
|
|
Debug.LogErrorFormat(_owner,
|
|
"Raycast Threshold greater than 0 can't be used on Masking with texture '{0}' because "
|
|
+ "it's not readable. You can make the texture readable in the Texture Import Settings.",
|
|
texture.name);
|
|
break;
|
|
case MaterialParameters.SampleMaskResult.NonTexture2D:
|
|
Debug.LogErrorFormat(_owner,
|
|
"Raycast Threshold greater than 0 can't be used on Masking with texture '{0}' because "
|
|
+ "it's not a Texture2D. Raycast Threshold may be used only with regular 2D textures.",
|
|
texture.name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void SpriteUsed(Sprite sprite, Errors errors)
|
|
{
|
|
if (_lastUsedSprite == sprite)
|
|
return;
|
|
_lastUsedSprite = sprite;
|
|
if ((errors & Errors.TightPackedSprite) != 0)
|
|
Debug.LogError("Masking doesn't support tight packed sprites", _owner);
|
|
if ((errors & Errors.AlphaSplitSprite) != 0)
|
|
Debug.LogError("Masking doesn't support sprites with an alpha split texture", _owner);
|
|
}
|
|
|
|
public void ImageUsed(Image image)
|
|
{
|
|
if (!image)
|
|
{
|
|
_lastUsedImageSprite = null;
|
|
_lastUsedImageType = Image.Type.Simple;
|
|
return;
|
|
}
|
|
if (_lastUsedImageSprite == image.sprite && _lastUsedImageType == image.type)
|
|
return;
|
|
_lastUsedImageSprite = image.sprite;
|
|
_lastUsedImageType = image.type;
|
|
if (!image)
|
|
return;
|
|
if (IsImageTypeSupported(image.type))
|
|
return;
|
|
Debug.LogErrorFormat(_owner,
|
|
"Masking doesn't support image type {0}. Image type Simple will be used.",
|
|
image.type);
|
|
}
|
|
}
|
|
}
|
|
}
|