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;

Sonntag, 26. April 2020

Unity - animation prefab creation

The challenge is to take over the transform position of the parent object. Approach:

Creation:

- create an empty gameObject in the hierarchy
- drop a resource like a sprite png onto the empty gameObject in the hierachy
- the gameObject covering the sprite png is now a child object of the empty one
- if not available create a subfolder in the Assets folder named prefabs
- create a subfolder in the new folder prefabs called mySampleAnimationObject
- drop the empty gameobject of the hierarchy in the prefab subfolder just created
- add a animator component to the child of the empty gameobject in the prefab
- you should be asked to create the animation
- record the animation
- create a new c# script and drop it onto the child of the empty gameobject. The gameobject including the animator now!
- add a public function like : public void AnimFinished() {  Destroy(gameObject); }
- add a event to the end of the animation / onto the last key frame add the event.
- assign the AnimFinished() method to the event.
- save

Usage:

- open the main actor script
- add a public GameObject variable to host the created prefab including the upper mentioned stuff
       public GameObject MySampleAnimPrefab;
- drop this prefab within the inspector on this new public member of the script component
- now is the prefab in general linked to the script of the main actor;
- the main actor script can now fire and forget as many prefab animation objects as desired
- this will be done using for example:
        Instantiate(MySampleAnimPrefab, collision.gameObject.transform.position, Quaternion.identity); // the second arg is any Vector3 type position