Freitag, 26. November 2021

tilemap coloring learning script sample....

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class TileMapColorShakerByVanden : MonoBehaviour
{
    private enum ColorShakerType : int
    {
        SolidColor = 1,
        SpectrumCycling1 = 2
    }
    [SerializeField]
    [Header("Basic input")]
    [Tooltip("Tilemap to change colors of")]
    private Tilemap _tilemap = default;
    [SerializeField]
    [Tooltip("preferred Color")]
    private Color _baseColor = default;
    [Header("Type specifics")]
    [SerializeField]
    [Tooltip("The method used to change the color")]
    private ColorShakerType _colorShakerType = default;
    [SerializeField]
    [Tooltip("Speed of color change according the chosen method")]
    [Range(0.01f, 1.0f)]
    private float _speed = default;  
    [SerializeField]
    [Tooltip("Color change refresh rate per second ; 50 = smooth ; 1=rough,slow")]
    [Range(1f, 50f)]
    private float _refreshRate = 50f;
    
    BoundsInt _bounds;
    Color _cyclingColor;
    float _r,_g,_b = 0.5f;
    float _a = 1.0f;
    float _prevR, _prevG, _prevB = 0.5f;
    float _deltaR, _deltaG, _deltaB = 0.5f;
    float[] _colorMatrix;
    int _offset = 0;
    bool _isApplyColor = false;
    float _frames = 0f;
    List<TileBase>[,] _tileArray;

    /*
     *  https://docs.unity3d.com/ScriptReference/Color.html
     *  Color : This structure is used throughout Unity to pass colors around. Each color component is a floating point
     *  value with a range from 0 to 1.
     */
    private void Awake()
    {
        _refreshRate = 50f - _refreshRate; // opposite : 0=fast, 50=slow,rough
        _tilemap.CompressBounds(); // cleaned up tilemap without invisble outside deleted but remaining null tiles
        _bounds = _tilemap.cellBounds; // tilemap coordinate e.g. -20 ... +20, -10 ... +10 
        if (_colorShakerType == ColorShakerType.SpectrumCycling1)
            // each following _colorMatrix line for a R, G, B fade indicator of 0f or 1f ; if 0f then fade it to become darker ; if 1f to become brighter
            _colorMatrix = new float[] {
            0f,0f,1f,
            0f,1f,0f,
            0f,1f,1f,
            1f,0f,0f,
            1f,0f,1f,
            1f,1f,0f,
            1f,1f,1f }; // ... modulo of 3 required
        _r = Mathf.Max(0.1f,Mathf.Min(_baseColor.r,0.9f));
        _g = Mathf.Max(0.1f,Mathf.Min(_baseColor.g,0.9f));
        _b = Mathf.Max(0.1f,Mathf.Min(_baseColor.b,0.9f));
        _a = Mathf.Max(_baseColor.a,0.5f);
        _cyclingColor = new Color(_r, _g, _b, _a);
        _prevR = _r;
        _prevG = _g;
        _prevB = _b;
    }

    private void Start()
    {
        //_offsetX = System.Math.Abs(_bounds.xMin); // offset to be added to each local x position to ensure an x value between 0.._bounds.size.x
        //_offsetY = System.Math.Abs(_bounds.yMin);  // offset to be added to each local y position to ensure an y value between 0.._bounds.size.y
        //_tileArray = new List<TileBase>[_bounds.size.x, _bounds.size.y];
        //int x, y;
        if (_colorShakerType == ColorShakerType.SolidColor)
            _tilemap.color = _cyclingColor; // tint the complete tilemap ; all at once ; not each individual tile
        if (_colorShakerType == ColorShakerType.SpectrumCycling1)
            foreach (var pos in _bounds.allPositionsWithin)
            {
                //x = pos.x + _offsetX; 
                //y = pos.y + _offsetY;
                if (_tilemap.HasTile(pos))
                {
                    //_tileArray[x,y]=_tilemap.GetTile(pos); // put TileBase obj. into array
                    _tilemap.SetTileFlags(pos, TileFlags.None);
                    _tilemap.SetColor(pos, _cyclingColor); // open for more individual coloring... feel free to change :-)
                }
            }
    }

    private void FixedUpdate()
    {
        // 50 times per second by default
        if (_colorShakerType == ColorShakerType.SpectrumCycling1)
        {
            if (_colorMatrix[_offset + 0] == 1f && _r < 0.95f)
                _r += _speed * Time.fixedDeltaTime;
            if (_colorMatrix[_offset + 0] == 0f && _r > 0.05f)
                _r -= _speed * Time.fixedDeltaTime;
            if (_colorMatrix[_offset + 1] == 1f && _g < 0.95f)
                _g += _speed * Time.fixedDeltaTime;
            if (_colorMatrix[_offset + 1] == 0f && _g > 0.05f)
                _g -= _speed * Time.fixedDeltaTime;
            if (_colorMatrix[_offset + 2] == 1f && _b < 0.95f)
                _b += _speed * Time.fixedDeltaTime;
            if (_colorMatrix[_offset + 2] == 0f && _b > 0.05f)
                _b -= _speed * Time.fixedDeltaTime;
            _deltaR = _r - _prevR;
            _deltaG = _g - _prevG;
            _deltaB = _b - _prevB;
            if (_deltaR == 0f && _deltaG == 0f && _deltaB == 0f)
            {
                _offset += 3;
                if (_offset >= _colorMatrix.Length - 1)
                    _offset = 0;
            }
        }
        _cyclingColor = new Color(_r,_g,_b,_a);
        _frames++;
        if (_frames > _refreshRate)
        {
            _frames = 0;
            _isApplyColor = true;
            //Debug.Log($"_offset={_offset}, _r={_r}, _g={_g}, _b={_b}, _deltaR={_deltaR}, _deltaG={_deltaG}, _deltaB={_deltaB}");
        }
        _prevR = _r;
        _prevG = _g;
        _prevB = _b;
    }

    private void Update()
    {
        if (_isApplyColor)
        { 
            if (_colorShakerType == ColorShakerType.SpectrumCycling1)
                foreach (var pos in _bounds.allPositionsWithin)
                    if (_tilemap.HasTile(pos))
                        _tilemap.SetColor(pos, _cyclingColor);
            _isApplyColor = false;
        }
    }
}


Samstag, 26. Dezember 2020

Unity Tilemap Bitmap Font Printing Example

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using UnityEditor;

/// <summary>
/// Class to print text onto a Tilemap by string, position and destiantion Tilemap based on a bitmap font asset. How to: Add an empty gameObject to the scene and call it for instance MyTilemapTextManager and then add this script to it. Move the gameObject into the prefabs Folder of your projects Assets subfolder.
/// </summary>
public class MyTilemapTextManagerScript : MonoBehaviour
{
    public static MyTilemapTextManagerScript Instance; // to refer the singleton instance of the class
    public Tilemap DestinationTilemap; // to provide a Tilemap surface to print on
    [System.Serializable]
    private struct TileItem
    {
        public int Index;
        public Tile Tile;
    }
    [System.Serializable]
    private struct FontItem
    {
        //public int Index;
        public string TileNamesStartWith;
        public string TilePaletteAssetPath;
        public List<TileItem> TileItems;
    }
    [SerializeField]
    private List<FontItem> _fontItems;
    private int _lastUsedFontIndex = 0;
    private void Awake()
    {
        DontDestroyOnLoad(gameObject); // keep the instance of the parent empty game object over several scenes after it was once initalized/instantiated
        if (Instance == null)
        {
            Instance = this; // make it resuable by referencing it statically using MyTilemapTextManagerScript.Instance.Print(...); Print is a public method defined in this class here!
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        fillTileItemCollectionByLoadingFromAssets(); // load the Tiles unattended and fill the _tileItems collection
    }

    private void fillTileItemCollectionByLoadingFromAssets()
    {
        for (int i = 0; i < _fontItems.Count; i++)
        {
            FontItem fontItem = _fontItems[i];
            fontItem.TileItems = new List<TileItem>();
            var assets = AssetDatabase.FindAssets(fontItem.TileNamesStartWith, new string[] { fontItem.TilePaletteAssetPath }); // for instance "kromagrad_16x16_" and "Assets/TilePalettes"
            int indexCount = 0;
            foreach (var guid in assets)
            {
                Tile assetTile = AssetDatabase.LoadAssetAtPath<Tile>(AssetDatabase.GUIDToAssetPath(guid));
                fontItem.TileItems.Add(new TileItem { Index = indexCount++, Tile = assetTile });
            }
            if (indexCount == 0)
                Debug.Log($"Code 2012251527 - Unable to find any asset using the folder '{fontItem.TilePaletteAssetPath}' and the search string mask of '{fontItem.TileNamesStartWith}'!");
            // foreach (TileItem tileItem in _tileItems)
            // {
            //     print($"Tile Index='{tileItem.Index}', Tile Object='{tileItem.Tile}'");
            // }
            _fontItems[i] = fontItem;
        }
    }

    private void Start()
    {
        Print("hello world", new Vector3Int(-5, -4, 0)); // ensure to assign a Tilemap in the inspector to DestinationTilemap before starting it
        SelectFont("kromasky");
        Print("RVDH was here", new Vector3Int(-6, 4, 0)); // ensure to assign a Tilemap in the inspector to DestinationTilemap before starting it
    }


    public void SelectFont(string fontName)
    {
        int index = 0;
        foreach (FontItem fontItem in _fontItems)
        {
            if (fontItem.TileNamesStartWith.ToUpper().Contains(fontName.ToUpper()))
            {
                _lastUsedFontIndex = index;
                break;
            }
            index++;
        }
        if (index == _fontItems.Count)
        {
            _lastUsedFontIndex = 0;
            Debug.Log($"Code 2012252159 - Unable to find a font containing the name of '{fontName}'!");
        }
    }

    /// <summary>
    /// Print a text on the Tilemap at a certain position.
    /// </summary>
    /// <param name="text"></param>
    /// <param name="position">negative is left,bottom orientation; positiv is right,top orientation</param>
    /// <param name="destinationTilemap"></param>
    public void Print(string text, Vector3Int position, int lastUsedFontIndex = -1, Tilemap destinationTilemap = null)
    {
        if (destinationTilemap != null)
            DestinationTilemap = destinationTilemap;
        if (text == null || DestinationTilemap == null)
        {
            Debug.Log("Code 2012251531 - Unable to print a text when the text or the destination Tilemap is null!");
            return;
        }
        if (text != "")
        {
            if (lastUsedFontIndex > -1)
                _lastUsedFontIndex = lastUsedFontIndex;
            FontItem fontItem = _fontItems[_lastUsedFontIndex];
            text = text.ToUpper();
            for (int i = 0; i < text.Length; i++)
            {
                char ch = text[i];
                int chOffset = Convert.ToInt16(ch) - 33; // 'A' is ASCII code 65 ; 'A' is index 32 represented by asset 'kromagrad_16x16_32' ; the delta is therefore 65-32=33
                if (chOffset < 0)
                    DestinationTilemap.SetTile(position, null);
                else
                {
                    Tile tile = fontItem.TileItems[chOffset].Tile;
                    if (tile == null)
                        Debug.Log($"Code 2012251551 - Unable to index character '{ch}' by the use of an appropriate Tile!");
                    else
                        DestinationTilemap.SetTile(position, tile);
                }
                position.x++;
            }
        }
    }
}

Mittwoch, 4. November 2020

Unity UI Element Button selection and click simulation via script

simulate select and click

using UnityEngine.EventSystems;
using UnityEngine.UI;

...

[SerializeField] private Button myButton;

...

// simulate click : 

ExecuteEvents.Execute(myButton.gameObject, new BaseEventData(EventSystem.current), ExecuteEvents.submitHandler);

// simulate selection : 

EventSystem.current.SetSelectedGameObject(myButton.gameObject);

// or:
myButton.Select();
myButton.OnSelect();

------------------------------------------------------------------------

List<Button> shuffling

   private GameObject[] _buttonGameObjects;
   private int _buttonIndex = -1;

..

_buttonGameObjects = GameObject.FindGameObjectsWithTag("MenuButton");

..

// select next button

            if (_buttonIndex + 1 == _buttonGameObjects.Length)
                _buttonIndex = 0;
            else
               _buttonIndex++;
  EventSystem.current.SetSelectedGameObject(_buttonGameObjects[_buttonIndex]);

..

// select previous button


            if (_buttonIndex - 1 < 0)
                _buttonIndex = _buttonGameObjects.Length - 1;
            else
                _buttonIndex--;
          EventSystem.current.SetSelectedGameObject(_buttonGameObjects[_buttonIndex]);


-----------------------------------------------------

Get all Buttons nested in a GameObject below the Canvas. The demo structure is ...Canvas, MenuPageLanguageSelection, MenuButton1...

    private Canvas myCanvas;
    private GameObject myMenuPageLanguage;
    GameObject[] myMenuButtonGameObjects;

    private void Awake()
    {
         myCanvas = GameObject.FindObjectOfType<Canvas>(); // only one in scene
        myMenuPageLanguage = myCanvas.transform.GetChild(0).gameObject; 
        myMenuButtonGameObjects = new                       GameObject[myMenuPageLanguage.transform.childCount];
        for (int i = 0; i < myMenuPageLanguage.transform.childCount; i++)
        {
            myMenuButtonGameObjects[i] =                      myMenuPageLanguage.transform.GetChild(i).gameObject;
        }
}


Freitag, 23. Oktober 2020

Unity and Visual Studio Keyboard Shortcuts

 Visual Studio Editor:

CTRL + ALT + M , then CTRL + H  : Will open Unity Scripting API Documentation inside VS

CTRL + Shift + M : Show dialog to implement new Method easily


Dienstag, 29. September 2020

Unity - InputSystem - Gamepad access etc.

2020-09-29 high level description:

open the project settings

enable preview packages

open the Package Manager

select the filter Unity Registrations 

Install the InputSystem Package

go to your project Assets folder of the inspector

choose Create the right mouse button click

choose Input Actions - this will add the InputSystem asset 'New Controls' to your project.

'New Controls' - rename it with Gameplay for instance

Doubleclick on 'Gameplay'

The Window to add the actions and bindings comes up.

Anchor the window next to the Console window etc. for a quick access.

Add a Input Map called 'MenuControl'

Add a action to the 'MenuControl' called 'moveLeft'

Add a bindung or more bindings to move the menu cursor finally to the left - so just add a binding of the left arrow key to this action 'moveLeft'.

Notice the save checkbox in top of the window. Activate it for saving. 
Also check the properties of the created asset. Doublecheck the checkbox overthere "create class".
This will generate a new proxy class for the inputSystem called 'Gameplay' here.

In your unity application create an instance of this class.

Important is to call the .enable() method of the instance of such created Input Action Asset. Just add the call of the .disable() method of the object in the OnDisable() method of the hosting gameobject.

To the instanced object register callbacks for each action of the input system map 'MenuControl' of the input system 'Gameplay'...

And / Or add direct request to the Keyboard.current or Gamepad.current objects if any key or button is pressed.

Sonntag, 2. August 2020

Unity Architecture best practice

Add a persistent manager class to the hierarchy. This class should be added to all scenes. Add this class to a scene common prefab. This class will be initiated only once in the complete lifecycle of the application. To achive this persistent behavior add the command "DontDestroyOnLoad(gameObject)" to the Awake() method of the script. The persistent manager is just an empty game object renamed to e.g. PersistentManager with such assigned characteristic script:

using UnityEngine;
using UnityEngine.SceneManagement;

public class PersistentManagerCode : MonoBehaviour
{
    public int Level;
    public delegate void GameEvent();

    public static PersistentManagerCode Instance { get; private set; }
    public static event GameEvent OnResetGame; // listeners can subscribe to this event

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this; // what creates the first instance? The gameobject which is called persistentGameManager - that is all - discover the static vars on top
            DontDestroyOnLoad(gameObject); // to be persistent, okay - is available in all scenes
            DoResetGame();
        }
        else
            Destroy(gameObject);
    }

    public void DoResetGame()
    {
        if (OnResetGame != null)
            OnResetGame(); // if there are any subscribers, then call the OnResetGame GameEvent delegate function of the subscriber
        this.Level = 0;
    }

    ...
}

to be continued...




Sonntag, 10. Mai 2020

TAG - FindGameObjectWithTag(..)

Assigning a GameObject to a script variable is often done using drag and drop in the inspector.
Another way to load a GameObject instance into a variable is to apply ...
private transform playerTransform
...
playerTransform = GameObject.FindGameObjectWithTag("MyPlayer").transform;