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

Montag, 13. April 2020

Unity - how to collect item

The item to collect is a prefab.
This prefab got a collider
The collider of the prefab is checked as 'isTrigger'!
The gameobject of this collectable item is tagged as 'Coin'.

The prefab of the player actor checks for collisions in its script:

private void OnTriggerEnter2D(Collider collision)
{
       if (collision.gameObject.CompareTag("Coin"))
       {
                Destroy(collision.gameObject);
                MyPersistentManager.Instance.DoCollectCoinAction(); // todo

        }
}

Sonntag, 12. April 2020

Unity - modify Parameters of animator transactions via scripting

There are animations.
Each animation got an animator.
Drop as many other animation animator to the current animator optionaly.
Connect each animator element through new transactions. (right click on the element).
Add a parameter to the transaction of type float, bool etc.
Add a condition to the transaction to be true or false.
The condition is the result of a comparison of such Parameters.

How to feed the Parameters from code in order to let the animator decide which condition is true and finally to change the desired animation?

Make a public class member of type animator:

public Animator animator;

Feed this public Animator variable in the UI of  Unity by draging and dropping the animator of the same gameObject over there. Now is the specific gameObject animator linked to the gameObject public script variable. 

Set the Parameter of the Animator:

animator.SetFloat("MyTransactionFloatParameter", 0.01f); // for instance
animator.SetBool("MyTransactionBoolPara",true);

Unity - quickest way to add colliders to all tiles within the tile map.

Select the TileMap in the hierarchy and add a "TileMap Collider 2D" property.

Unity - 2d movement and jumping and character facing to left, right direction (Variant 20200412)

There is a Player GameObject with a rigid body property and a 2d collider property.
In the hierarchy is a another gameobject as a child of Player GameObject. This chlid got only a 2d collider property.
The collider of the parent player game object checks the normal bounderies.
The collider of the child player object is only used to detect if the parent game object is grounded or not. It is not grounded, when it currently jumps.

(What is the trick to let the character face to the right (default) and then to the left by the movement to the left of course: Whenever the character changes the movement to the left, flip the value of gameObject.transform.localScale.x from +1 to -1 and the other way around by a multiplying with just * -1;)
Snippets (1 script of the parent and another one for this child object)

Important is only to allow jumping when the character is currently not jumping. That is because the collider detecting if the character is grounded or not is a few pixel large and it takes a couple of frames to leave this zone. If you are ignoring to check the flag if it is currently jumping you will get a inconstince result with various jump heights!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerCode : MonoBehaviour
{
    public float PlayerSpeed = 5f;
    public float PlayerJumpForce = 0.6f;
    public bool isGrounded = false;

    private bool IsJumping = false;
    private bool isIdle = true;
    private bool isMoving = false;
    private float lockRotation = 0f;
    private bool isFacingRight = false;

    // Update is called once per frame
    void Update()
    {
        move();
        jump();
        gameObject.transform.rotation = Quaternion.Euler(lockRotation, lockRotation, lockRotation);
         
    }

    private void move()
    {
        // @see https://docs.unity3d.com/ScriptReference/Input.GetAxis.html
        // @see https://www.youtube.com/watch?v=L6Q6VHueWnU
        float horizontalMovementRate = Input.GetAxis("Horizontal");
        if (horizontalMovementRate != 0)
        {
            isMoving = true;
            Vector3 movement = new Vector3(horizontalMovementRate, 0f, 0f);
            transform.position += movement * Time.deltaTime * PlayerSpeed;
            if (horizontalMovementRate>0 & !isFacingRight ||       horizontalMovementRate<0 & isFacingRight)
            {
                isFacingRight = !isFacingRight;
                Vector3 newScale = transform.localScale;
                newScale.x = newScale.x * -1;
                transform.localScale = newScale;

            }
        }
        else
            isMoving = false;
    }


    void jump()
    {
        if (Input.GetButton("Jump") & isGrounded & !IsJumping)
        {
            gameObject.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, PlayerJumpForce), ForceMode2D.Impulse);
            IsJumping = true;
        }
    }
}

child object script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GroundedCode : MonoBehaviour
{
    GameObject Player;

    // Start is called before the first frame update
    void Start()
    {
        Player = gameObject.transform.parent.gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.tag == "Ground")
        {
            Player.GetComponent<PlayerCode>().isGrounded = true;
Player.GetComponent<PlayerCode>().IsJumping = false;
        }
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        if (collision.collider.tag == "Ground")
        {
            Player.GetComponent<PlayerCode>().isGrounded = false;
        }
    }

}





Unity - avoid physical rotation of a rigid body - object should remain parallel to the horizont

Prefer to set the checkmarks in the RigidBody Settings at.
Constraints
-> Freeze Rotation Z

Otherwise by code simply reset any calculated rotation to 0 in each frame within the Update function:

float lockRotation = 0f;
gameObject.transform.rotation = Quaternion.Euler(lockRotation, lockRotation, lockRotation);

TIP:


            
Constraints
-> Freeze Position X 
will prevent the character from sliding down physically from a slant platform.

Donnerstag, 9. April 2020

Unity - concept of loading and saving (InputField) data

First experience about how to structure saving and loading of data - e.g. of an InputField

- create class of UserData with the properties to save and load
- create class of SaveSystem which covers LoadData() and SaveData() functions
- create empty GameObject and call it UserGameObject
- create class User and assign it per drag and drop to the UserGameObject
- add public InputField property to the User class to be linked with the InputField control.
- add the callback function of the On End Edit InpuField event to the User class.
- There you are going to put the new value of the InputField to the UserData object and call the SaveData() function to save the UserData object.

Snippets (different conversation)

using System.IO;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System;

public static class SaveSystem
{

    public static void SavePlayer(Player player)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        string path = Application.persistentDataPath + "/player.bin";
        try
        {
            if (File.Exists(path))
                File.Delete(path);
            FileStream fileStream = new FileStream(path, FileMode.Create);
            formatter.Serialize(fileStream, player.UserData);
            fileStream.Close();
            fileStream.Dispose();
            Debug.Log("Okay, saved settings");
        }
        catch (Exception ex)
        {
            Debug.LogError("Unable to save settings! " + ex.Message);
        }
    }

    public static PlayerData LoadPlayer()
    {
        PlayerData player = new PlayerData();
        string path = Application.persistentDataPath + "/player.bin";
        try
        {
            FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate);
            BinaryFormatter formatter = new BinaryFormatter();
            player = (PlayerData)formatter.Deserialize(fileStream);
            fileStream.Close();
            fileStream.Dispose();
            Debug.Log("Okay, Loaded settings.");
        }
        catch (Exception ex)
        {
            Debug.LogError("Unable to load settings! " + ex.Message);
        }
        return player;
    }


}


Data class

[System.Serializable]
public class PlayerData 
{
    public string Partnertoken;
    public string TempPartnerToken;

    public PlayerData()
    {
        this.Partnertoken = "";
        this.TempPartnerToken = "";
    }

}

GameObject class

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Player : MonoBehaviour
{
    public PlayerData UserData;
    public InputField PartnertokenInputField;

    // Start is called before the first frame update
    void Start()
    {
        UserData = new PlayerData();
        UserData = SaveSystem.LoadPlayer();
        if (UserData == null)
            Debug.LogError("Code 202004100825 - UserData object is still null");
        else
            this.PartnertokenInputField.text = UserData.Partnertoken;
    }

    #region InputField Partnertoken

    public void OnPartnertokenChanged(string newToken)
    {
        UserData.TempPartnerToken = newToken;
    }

    public void OnPartnertokenEndEdit(string newToken)
    {
        UserData.TempPartnerToken = newToken;
        this.ShiftTempPartnertoken();
        SaveSystem.SavePlayer(this);
    }

    public void ShiftTempPartnertoken()
    {
        UserData.Partnertoken = UserData.TempPartnerToken;
    }

    #endregion
}


Samstag, 21. März 2020

Unity 3d - change Object Position by script

how to change the position of an unity object?

- add a script to the object
- apply the "transform.position" property of type Vector3


Freitag, 14. Februar 2020

Unity 3d - change color - get current gameObject and change its color

image - see https://www.youtube.com/watch?v=_RIsfVOqTaE

using UnityEngine.UI;
..
public Image img;
...
img.color = Color.red; //



object - see https://learn.unity.com/tutorial/scripts-as-behaviour-components#

GetComponent<Renderer>().material.color = Color.red;





Unity 3d - get and set screen resolution

get:

          Debug.Log("Screen Width : " + Screen.width);
    Debug.Log("Screen Height : " + Screen.height);

set:

    Screen.SetResolution(1600,900,false);

...where 1600 is the width, 900 the height and finally a boolean to signalize the fullscreen mode.

Dienstag, 11. Februar 2020

Unity 3d - c# Awake and Start methods

see https://www.youtube.com/watch?v=4QdjoV63wjM

Awake() initializes private variables of an object once a startup.

When later on the object is gonna be enabled it will benefit of all settings made in Awake().
This will be unattended handled by Unity when the Start() script is being called.



Unity 3d - wait a few seconds / sleep

C# - How to wait a while in Unity 3d see https://docs.unity3d.com/ScriptReference/WaitForSeconds.html
...
StartCoroutine(TimeRoutine());
...
IEnumerator TimeRoutine()
{
       yield return new WaitForSeconds(3.0f); // wait 3 seconds within this TimeRoutine thread
       //load the next scene then for instance
}



Unity 3d - change scence how to

C#: how to switch to another scene in Unity 3d

using UnityEngine.SceneManagement;
..
SceneManager.LoadScene("myNextScene");

get current scene name:

Debug.Log(SceneManager.GetActiveScene().name.ToString());

get Scene:

private Scene view;
...
this.view = SceneManager.GetActiveScene();