Skip to content

Instantly share code, notes, and snippets.

@edwrodrig
Last active October 6, 2023 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edwrodrig/34b667e021b2552013abe707f458eccb to your computer and use it in GitHub Desktop.
Save edwrodrig/34b667e021b2552013abe707f458eccb to your computer and use it in GitHub Desktop.
Información util para Unity

Información util para Unity

Game loop de Unity

Última fecha: 2023-09-24

Configurar Remote Controller

Última fecha: 2023-09-24

  • Menú Edit > Project Settings > Editor en Any Android Device.
  • Menú Build Settings . Cambiar Platform a Android. Instalar con botón Install with Unity Hub si es necesario.
  • Revisar que dispositivo Android tenga depuración USB activada y el modo de conexión esté en transferencia de archivos.
  • Adicionalmente se puede revisar por consola la conexión con el comando adb devices.

Descargar Unity Recorder

Última fecha: 2023-09-24

  • Menú File > Package Manager.
  • En menú Packages elegir Unity Registry.
  • En el input buscar escribir Recorder
  • Pulsar Install

Usar Unity Recorder

Última fecha: 2023-09-24

  • Menú Window > General > Recorder Window
  • Presionar Add recorder y seleccionar Movie.

Interpolación de strings

Última fecha: 2023-09-24

O como hacer strings como printf. Más detalles en el siguiente link

var name = "Mark";
var date = DateTime.Now;

// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.

Animación esqueletal rápida

Preparar sprite

Última fecha: 2023-09-24

  • Arrastrar una imagen desde el explorador de archivos a la pantalla de Projects.
  • Seleccionar la imagen y en Inspector presionar Sprite Editor.
  • Cambiar modo a Skinning Editor.
  • Presionar Auto Geometry para generar geometría del personaje.
  • Crear huesos con Create Bone.
  • Apretar Visibility para revisar la jerarquía y propiedades de cada hueso.
  • Apretar Auto Weights para dar una influencia de los huesos sobre la geometría.

Asignar animación esqueletal a sprite en el juego

Última fecha: 2023-09-24

  • Arrastrar el Sprite a la escena. Debería agregar un game object con un Sprite Renderer con al sprite referenciado.
  • En el Inspector, presionar Add Component > 2D Animation > Sprite Skin.
  • En el componente Sprite Skin, presionar Create Bones. Debería agregar huesos al game object. Los huesos se puede animar como cualquier otro objeto.

Tipos de Update

Game loop de unity

Última fecha: 2023-09-24

En este link muestra el orden de ejecución (Execution order) que tiene el game loop.

Variables de tiempo

Última fecha: 2023-09-24

Estas variables pueden ser utilizadas en los métodos update. Para más información ver el siguiente link.

  • Time.fixedDeltaTime : siempre es igual. Marca el tiempo del intervalo de la física.
  • Time.fixedTime : es el tiempo en que ocurrió el últimp fixedTime.
  • Time.timeSinceLevelLoad : el tiempo desde que se cargó la escena.
  • Time.time : es el tiempo absoluto, o tiempo de reloj.

Funciones y métodos de actualización

Última fecha: 2023-09-24

  • Corutina WaitForFixedUpdate. link

Controlador touch simple

Última fecha: 2023-09-24

Este script convierte un objeto de draggeable mediante un touch

using UnityEngine;

public class TouchControl : MonoBehaviour
{

    private Vector3 offset;
    private Camera mainCamera;
    private bool dragging;

    void Start()
    {
        mainCamera = Camera.main;
    }

    void Update()
    {
        // si hay más de un toque
        // recordar que s pantallas son multitouch y pueden tener más de un toque
        if (Input.touchCount > 0)
        {
            //obtener el primer toque, 0 indica el primero, 1 el segundo y así consecutivamente
            Touch touch = Input.GetTouch(0);

            //si tocamos la pantalla
            if (touch.phase == TouchPhase.Began)
            {
                //convertir una posicion de la pantalla a una posicion del mundo
                Vector3 touchPosition = mainCamera.ScreenToWorldPoint(touch.position);
                
                //elimina la profundidad, estamos en un juego 2D
                touchPosition.z = 0;
                
                //calculamos desde donde empezamos a considerar el movimiento
                //offset tendra la posicion inicial del objeto menos donde empezamos a mover el dedo
                offset = transform.position - touchPosition;
                //marcamos que vamos a empezar un arrastre
                dragging = true;
            }
            
            //si estamos moviendo nuestro dedo y hemos marcado que empezamos el arrase
            if (touch.phase == TouchPhase.Moved && dragging)
            {
                //convertir una posicion de la pantalla a una posicion del mundo
                Vector3 touchPosition = mainCamera.ScreenToWorldPoint(touch.position);
                //elimina la profundidad, estamos en un juego 2D
                touchPosition.z = 0;
                
                //actualizamos la posicion
                transform.position = touchPosition + offset;
                
                //fit in the screen
                Vector3 pos = mainCamera.WorldToViewportPoint (transform.position);
                pos.x = Mathf.Clamp01(pos.x);
                pos.y = Mathf.Clamp01(pos.y);
                transform.position = mainCamera.ViewportToWorldPoint(pos);
            }

            if (touch.phase == TouchPhase.Ended)
            {
                dragging = false;
            }
        }
    }
}

Para cargar una escena de forma rápida, utilizar el siguiente script.

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneSwitcher : MonoBehaviour
{
    public void LoadScene(string sceneName)
    {
        SceneManager.LoadScene(sceneName);
    }
}
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class LoadingSceneController : MonoBehaviour
{
    public Slider progressBar;  // Referencia al componente Slider (Barra de progreso)
    
    // Método para iniciar la carga de la escena
    public void LoadScene(string sceneName)
    {
        StartCoroutine(LoadSceneAsync(sceneName));
    }

    private IEnumerator LoadSceneAsync(string sceneName)
    {
        // Inicia la carga de la escena de manera asíncrona
        AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(sceneName);
        
        // Evita que la escena se active automáticamente al terminar la carga
        asyncOperation.allowSceneActivation = false;

        while (!asyncOperation.isDone)
        {
            // Actualiza la barra de progreso
            float progress = Mathf.Clamp01(asyncOperation.progress / 0.9f);
            progressBar.value = progress;

            // Checa si la escena ha terminado de cargar (el valor de progress llega hasta 0.9)
            if (asyncOperation.progress >= 0.9f)
            {
                // Activa la escena cuando esté lista
                asyncOperation.allowSceneActivation = true;
            }

            yield return null;
        }
    }
}
``
public class Mover : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {
        Vector3 left = new Vector3(-1, 0, 0);
        transform.Translate(left * speed * Time.deltaTime, Space.World);
    }
}
  • Ambos objetos tienen que tener un Collider 2D.
  • Uno objeto debe tener un Rigid Body 2D.
  • Uno o ambos objetos deben tener un script con OnCollisionEnter2D.

docs

Edit -> Project Settings -> Physics 2D.

public class EnemyCollider : MonoBehaviour
{
    void OnCollisionEnter2D(Collision2D collision)
    {
        //get name of gameObject
        string name = collision.gameObject.name;
        Debug.Log("Collision detected enemy: " + name);
        
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyCollider : MonoBehaviour
{
    public int health = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

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

    void OnCollisionEnter2D(Collision2D collision)
    {
        //get name of gameObject
        string name = collision.gameObject.name;
        Debug.Log("Collision detected enemy: " + name);
       
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        //get name of gameObject

        string tag = other.gameObject.tag;

        if (tag == "bullet")
        {
            Destroy(other.gameObject);
            Debug.Log("Bullet destroyed");
            health--;
            if (health <= 0)
            {
                //change the parameter of the current animator state machine
                GetComponent<Animator>().SetBool("isAlive", false);

            }
        }

        

    }

    void ExplodeFinished() {
            Debug.Log("Exploded Finished");
            Destroy(gameObject);
    }
}
public class CoroutineAnimation : MonoBehaviour
{
    public Vector3 targetPosition;
    public float duration = 2.0f;

    private void Start()
    {
        StartCoroutine(AnimateToPosition(transform, targetPosition, duration));
    }

    private IEnumerator AnimateToPosition(Transform transform, Vector3 target, float duration)
    {
        float startTime = Time.time;
        Vector3 startPosition = transform.position;

        while (Time.time < startTime + duration)
        {
            float t = (Time.time - startTime) / duration;
            transform.position = Vector3.Lerp(startPosition, target, t);
            yield return null; // Wait for the next frame
        }

        transform.position = target;  // Ensure the final position is set accurately
    }
}

Pillarbox vs Letterbox

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


public class ScreenAdjust : MonoBehaviour
{
    public float targetWidth = 1080f;
    public float targetHeight = 1920f;
    public float pixelPerWorldUnits = 10.0f;

    [Tooltip("La camara que se va a ajustar")]
    public Camera camera;

    // determine the game window's current aspect ratio
    //es mayor entre mas ancho sea
    public float GetWindowAspect() {
        return (float)Screen.width / (float)Screen.height;
    }

    // desired aspect ratio
    // es mayor entre mas ancho sea
    public float GetTargetAspect() {
        return targetWidth / targetHeight;
    }

    public float GetScaleHeight() {
        return (float)Screen.height / targetHeight;

    }

    public float GetScaleWidth() {
        return (float)Screen.width / targetWidth;
    }

    public Rect GetPillarBoxRect() {
        
        float scaleHeight = GetScaleHeight();
        
        Rect rect = new Rect();

        rect.width = GetScaleHeight();
        rect.height = 1.0f;
        rect.x = (1.0f - rect.width) / 2.0f;
        rect.y = 0;

        return rect;
        
    }

    public Rect GetLetterBoxRect() {
        float scaleWidth = GetScaleWidth();

        Rect rect = new Rect();
        //en letter box el ancho es el mismo que la pantalla
        rect.width = 1.0f;

        //pero el alto es menor
        rect.height = GetScaleHeight();
        rect.x = 0;
        rect.y = (1.0f - rect.height) / 2.0f;

        return rect;
    }

    public bool ShouldBeLetterBox() {
        // si la pantalla es menos ancha que la esperada
        return GetWindowAspect() < GetTargetAspect();
    }

    public bool ShouldBePillarBox() {
        return !ShouldBeLetterBox();
    }

    public void AdjustCameraToScreen() {
        if (camera == null) {
            Debug.LogError("Camera should be set");
            return;
        }

        if (ShouldBeLetterBox()) {
            var newTargetHeight = (float)Screen.height / (float)Screen.width * targetWidth;
            Debug.Log("LetterBox");
            camera.orthographicSize = (newTargetHeight / pixelPerWorldUnits) / 2.0f;

        } else {

            var newTargetHeight = targetHeight;
            Debug.Log("PillarBox");
            camera.orthographicSize = (newTargetHeight / pixelPerWorldUnits) / 2.0f;
        }

    }

    [ContextMenu("Set Camera Default Parameters")]
    public void SetCameraDefaultParams() {
        if (camera == null) {
            Debug.LogError("Camera should be set");
            return;
        }

        camera.orthographic = true;
        camera.orthographicSize = (targetHeight / pixelPerWorldUnits) / 2.0f;
        camera.nearClipPlane = -0.3f;
        camera.farClipPlane = 1000.0f;
        camera.transform.position = new Vector3(
            (targetWidth / pixelPerWorldUnits) / 2.0f,
            (targetHeight / pixelPerWorldUnits) / 2.0f,
            0);
    }

    void Start()
    {
       AdjustCameraToScreen();
    }

 }
 ``
using UnityEngine;
using UnityEngine.Events;
using UnityEditor;

public class Waypoint : MonoBehaviour
{

    #if UNITY_EDITOR
    //draw as a cross in editor mode
    void OnDrawGizmos()
    {
        Gizmos.color = Color.yellow;
        //draw cross with lines
        Gizmos.DrawLine(transform.position + new Vector3(-5f, 0, 0), transform.position + new Vector3(5f, 0, 0));
        Gizmos.DrawLine(transform.position + new Vector3(0, -5f, 0), transform.position + new Vector3(0, 5f, 0));
        //draw label
        Handles.Label(transform.position + new Vector3 (1.0f, -1.0f, 0), gameObject.name);

    }

    #endif
}
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System;


[Serializable]
public class DelayedEvent {
    public float delay;
    public UnityEvent action;

    public IEnumerator Invoke() {
        yield return new WaitForSeconds(delay);
        action.Invoke();
    }
}

public class TriggerArea : MonoBehaviour
{
    public DelayedEvent[] onLanderPass;
    public DelayedEvent[] onBecameVisible;
    Collider2D landerCollider;
    public bool disabled = false;

    void Awake() {
        landerCollider = GetComponent<Collider2D>();
    }

    private void OnTriggerEnter2D(Collider2D other) {
        if ( disabled )
            return;
         if(other.gameObject.tag == "Player")
         {
            foreach (var e in onLanderPass) {
        
                StartCoroutine(e.Invoke());
                disabled  = true;
            }
            


         } else if ( other.gameObject.tag == "Camera" ) {
                foreach ( var delayedEvent in onBecameVisible ) {
                    StartCoroutine(delayedEvent.Invoke());
                    disabled  = true;
                }

        
         }
    }

    void OnDrawGizmos() {
         Gizmos.color = Color.green;
         //get offset
         var collider = GetComponent<Collider2D>();
        var center = transform.position - collider.bounds.center;
        Gizmos.DrawWireCube(center, collider.bounds.size);
    }
}

Los resource tienes que estar en la carpeta Resources

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

public class Blinker : MonoBehaviour
{

    [HideInInspector]
    public Animator animator;

    private AudioSource bubbleAudioSource;
    private AudioSource burstAudioSource;

    public AudioClip customBurstSound;

    void Awake() {
        animator = GetComponent<Animator>();
    }

    void Start()
    {
        bubbleAudioSource = gameObject.AddComponent<AudioSource>();
        bubbleAudioSource.clip = Resources.Load<AudioClip>("Sounds/bubble");

        burstAudioSource = gameObject.AddComponent<AudioSource>();
        if ( customBurstSound != null ) {
            burstAudioSource.clip = customBurstSound;
        } else {
            burstAudioSource.clip = Resources.Load<AudioClip>("Sounds/score");
        }

    }

}

Persistent Data Path mas informacion

Some issues Json serialization problem

Serializaició de Dicts en JSON request

using System;
using System.IO;
using UnityEngine;

public class AppDataStorage : MonoBehaviour {


    public AppData appData;

    public string getPath() {
        return Application.persistentDataPath + "/appData.json";
    }

    public void LoadGame()
    {
        string path = getPath();
        if ( !File.Exists(getPath()) ) {
            Debug.Log("No existe el archivo [" + path + "]");
            appData = new AppData();
            return;
        }

        string jsonData = File.ReadAllText(getPath());
        appData = JsonUtility.FromJson<AppData>(jsonData);
        Debug.Log("Loaded game" + jsonData + " from " + path);
    }

    public void SaveGame() {
        string path = getPath();
        string jsonData = JsonUtility.ToJson(appData);
        File.WriteAllText(path , jsonData);
        Debug.Log("Saved game" + jsonData + " to " + path);
    }

    public static T LoadFile<T>(string path) {
        if ( !File.Exists(path) ) {
            Debug.Log("No existe el archivo [" + path + "]");
            return default(T);
        }

        string jsonData = File.ReadAllText(path);
        T data = JsonUtility.FromJson<T>(jsonData);
        Debug.Log("Loaded game" + jsonData + " from " + path);
        return data;
    }

    public static void SaveFile<T>(string path, T data) {
        string jsonData = JsonUtility.ToJson(data);
        File.WriteAllText(path , jsonData);
        Debug.Log("Saved game" + jsonData + " to " + path);
    }


}

Serializable data

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

[Serializable]
public class LanderData {
    public string landerId = "audacia";
    public int numberCompleted = 0;
    public int currentPoints = 0;
    public int numberSpecies = 0;

    public string currentRadar = null;
    public string currentSensor = null;
    public string currentSampler = null;

    public bool visibleRadius = false;

    [NonSerialized]
    public LanderDataStatic dataStatic;

    public LanderData() {}



    public int GetUnlockLevel() {
        return dataStatic.unlockLevel;
    }

    public string GetLanderId() {
        return landerId;
    }


    public int GetLives() {
        return dataStatic.lives;
    }

    public string GetLanderName() {
        return dataStatic.landerName;
    }

    public BaseProgress GetLevelProgress() {
        return new BaseProgress(currentPoints);
    }

    public string GetLanderDescription() {
        return "Resistencia: " + dataStatic.lives;
    }

    public string GetUnlockablePortraitPath() {
        return "Sprites/Pictures/lander/lander_" + GetLanderId() + "/sprite";
    }

    public string GetLanderSprite() {
        return "Sprites/Pictures/lander/lander_" + GetLanderId() + "/sprite";
    }
}
using UnityEngine;
using System;
using System.Linq;
using System.Collections;
using UnityEngine.UI;
using System.Collections.Generic;

public class EnemyBehaviour : MonoBehaviour
{

    public enum Type
     {
            GoToTarget,
            GoToTargetNoRotate,
            RotateAndGoToTarget,
            Turn,
            Rotate,
            GoToStart,
            Stay,
            Repeat
    }

    [System.Serializable]
    public class WaypointParam
    {
        public float duration;
        public Type type;
        public GameObject target;
        public bool smooth = true;
        public int value;
    }

    public WaypointParam[] waypointParamList;
    public Vector3 startPosition;

    public Enemy graphics;
    public int repetitions = 1;
    public bool clearAtEnd = false;


    public List<WaypointParam> GetWaypointParamList() {
        return waypointParamList.ToList();
    }

    public void SlotStart() {
        if ( !graphics.gameObject.activeSelf ) {
            graphics.gameObject.SetActive(true);
        }
        graphics.SetBehaviour(this);
        graphics.SetCullUpdateTransform(true);
        StartCoroutine(StartMovement());
    }

    IEnumerator StartGoToStart(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        Vector3 startPosition = transform.position;
        Vector3 endPosition = this.startPosition;

        float startTime = Time.time;

        graphics?.SlotChangeOrientation(endPosition.x - startPosition.x);

        while (Time.time - startTime < param.duration)
        {
            float t = (Time.time - startTime) / param.duration;
            if ( param.smooth ) {
                t = Mathf.SmoothStep(0.0f, 1.0f, t);
            }

            transform.position = Vector3.Lerp(startPosition, endPosition, t);
            yield return null;
        }
    }

    IEnumerator StartGoToTarget(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        Vector3 startPosition = transform.position;
        Vector3 endPosition = param.target.transform.position;

        float startTime = Time.time;

        graphics?.SlotChangeOrientation(90 - (endPosition.x - startPosition.x) );

        while (Time.time - startTime < param.duration)
        {
            float t = (Time.time - startTime) / param.duration;
            if ( param.smooth ) {
                t = Mathf.SmoothStep(0.0f, 1.0f, t);
            }

            transform.position = Vector3.Lerp(startPosition, endPosition, t);
            yield return null;
        }
    }    

    IEnumerator StartGoToTargetNoRotate(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        Vector3 startPosition = transform.position;
        Vector3 endPosition = param.target.transform.position;

        float startTime = Time.time;

        while (Time.time - startTime < param.duration)
        {
            float t = (Time.time - startTime) / param.duration;
            if ( param.smooth ) {
                t = Mathf.SmoothStep(0.0f, 1.0f, t);
            }

            transform.position = Vector3.Lerp(startPosition, endPosition, t);
            yield return null;
        }
    }

    IEnumerator StartRotate(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        var startPosition = graphics.transform.rotation;
        var endPosition = param.target.transform.rotation;

        float startTime = Time.time;

        while (Time.time - startTime < param.duration)
        {
            float t = (Time.time - startTime) / param.duration;
            if ( param.smooth ) {
                t = Mathf.SmoothStep(0.0f, 1.0f, t);
            }

            graphics.transform.rotation = Quaternion.Lerp(startPosition, endPosition, t);
            yield return null;
        }
    }

    IEnumerator StartRotateAndGoToTarget(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        {
            var startPosition = graphics.transform.rotation;
            var endPosition = param.target.transform.rotation;

            float startTime = Time.time;

            while (Time.time - startTime < 1.0f)
            {
                float t = (Time.time - startTime) / 1.0f;
                if ( param.smooth ) {
                    t = Mathf.SmoothStep(0.0f, 1.0f, t);
                }

                graphics.transform.rotation = Quaternion.Lerp(startPosition, endPosition, t);
                yield return null;
            }
        }

        {
            Vector3 startPosition = transform.position;
            Vector3 endPosition = param.target.transform.position;

            float startTime = Time.time;

            while (Time.time - startTime < (param.duration - 1.0f))
            {
                float t = (Time.time - startTime) / (param.duration - 1.0f);
                if ( param.smooth ) {
                    t = Mathf.SmoothStep(0.0f, 1.0f, t);
                }

                transform.position = Vector3.Lerp(startPosition, endPosition, t);
                yield return null;
            }
        }
    }


    IEnumerator StartTurn(WaypointParam param) {
        if ( graphics == null ) {
            yield break;
        }
        
        graphics.FlipHorizontallyIngame();

        yield return null;
    }

    IEnumerator StartStay(WaypointParam param) {
        yield return new WaitForSeconds(param.duration);
    }

    IEnumerator StartMovement() {

        for ( int i = 0 ; i < repetitions ; i++ ) {

            foreach ( WaypointParam waypointParam in GetWaypointParamList() ) {
                switch ( waypointParam.type ) {
                    case Type.GoToStart:
                        yield return StartGoToStart(waypointParam);
                        break;
                    case Type.GoToTarget:
                        yield return StartGoToTarget(waypointParam);
                        break;
                    case Type.Stay:
                        yield return StartStay(waypointParam);
                        break;
                    case Type.Turn:
                        yield return StartTurn(waypointParam);
                        break;
                    case Type.GoToTargetNoRotate:
                        yield return StartGoToTargetNoRotate(waypointParam);
                        break;
                    case Type.Rotate:
                        yield return StartRotate(waypointParam);
                        break;
                    case Type.RotateAndGoToTarget:
                        yield return StartRotateAndGoToTarget(waypointParam);
                        break;
                }
            }
        }
        
        graphics?.SetCullUpdateTransform(false);

        if ( clearAtEnd ) {
            graphics?.SlotClear();
        }
        
  
    }


}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment