O CZYM PISZEMY?

Jak debugować kod w Unity

Opublikowano: 22.01.2021  Czas czytania: 3 minuty
Jak debugować kod w Unity

Czasami zdarza się, że coś dzieje się inaczej, niż się tego spodziewamy. W takich sytuacjach często konieczne jest dokładne zbadanie przyczyn i przebiegu działania naszego kodu. Zwykle pierwszym krokiem jest dodanie gdzieś Debug.Log w miejscu, w którym spodziewamy się wystąpienia problemu. Co jednak, gdy to nie wystarczy?

Kolejny parametr w Debug.Log

Problem: Masz wiele obiektów o tej samej nazwie, które trudno odróżnić.

Rozwiązanie: Pierwszym krokiem do wzmocnienia komunikatu w logu jest dodanie dodatkowego parametru do wywołania. Zgodnie z dokumentacją:

public static void Log(object message, Object context);

Jeśli przekażesz GameObject lub Component jako opcjonalny argument context, Unity podświetli ten obiekt w oknie Hierarchy, gdy klikniesz komunikat w konsoli.

Co to oznacza w praktyce? Załóżmy, że nasz debug wygląda tak:

public class FindMe : MonoBehaviour {
    void Start() {
        Debug.Log("ヘ(・ω| Gdzie jestem?", this);
    }
}

Wystarczy kliknąć w komunikat, aby odpowiadający mu obiekt został podświetlony. W tym przykładzie przekazaliśmy Component (this odnosi się do klasy dziedziczącej po Component). Podobnie możemy przekazać dowolny GameObject.

Z oficjalnego opisu może wynikać, że działają tylko obiekty widoczne w Hierarchy. Jednak nagłówek metody wskazuje, że możemy użyć dowolnego UnityEngine.Object. Dzięki temu możemy zlokalizować cokolwiek, co ma Instance ID – m.in. Material, ScriptableObject czy Mesh.

Bonus: Możemy też użyć EditorGUIUtility.PingObject, aby skorzystać z tej funkcji bez zapisywania logów. Link

Spraw, żeby logi się wyróżniały

Problem: Musisz logować wiele rzeczy i szybko je kategoryzować. Wyszukiwarka i zwijanie logów to za mało – kolejność wiadomości jest ważna, a wyszukiwany tekst może być trudno zauważalny.

Rozwiązanie: Uatrakcyjnij logi wizualnie. Komunikaty mogą zawierać tagi Rich Text.

Przerwanie wykonywania

Problem: Specjalny przypadek oznaczony kolorem nie jest wystarczająco zrozumiały po jego wystąpieniu.

Rozwiązanie: Najpierw znana metoda – breakpoint. Unity i Visual Studio dobrze współpracują w tym zakresie. Aby to działało w Unity:

  1. Kliknij na lewym marginesie wiersza, w którym chcesz breakpoint.
  2. Załącz debugger do Unity.
  3. Wciśnij Play.

Możesz też użyć Debug.Break – działa jak naciśnięcie pauzy. Alternatywnie Debug.LogError przy opcji „pause on error”.

Dostosowywanie okna Konsoli

Problem: Dane nie mieszczą się w jednej linii logu.

Rozwiązanie: Włącz multiline logs lub znaczniki czasu.

Co właśnie zbudowałem?

Problem: Komunikat Build completed with a result of ‚Succeeded’ nie mówi wiele.

Rozwiązanie: „Open Editor Log” i przeskocz do sekcji Build Report.

Logowanie z użyciem dyrektyw preprocesora

Problem: Masz wiele komunikatów debugowych w wersji deweloperskiej.

Rozwiązanie: Użyj własnej klasy z dyrektywą:

using UnityEngine;

#if NET_LOGGER && (UNITY_EDITOR || DEVELOPMENT_BUILD)
public static class DebugNet {

    public static void Log(string message) {
        Debug.Log($"[NET] {message}");
    }

    public static void LogWarning(string message) {
        Debug.LogWarning($"[NET] {message}");
    }

    public static void LogError(string message) {
        Debug.LogError($"[NET] {message}");
    }
}
#else
public static class DebugNet {
    public static void Log(string message) { }
    public static void LogWarning(string message) { }
    public static void LogError(string message) { }
}
#endif

Liczby to za mało?

Problem: Potrzebujesz czegoś więcej niż tylko cyferek.

Debug.DrawLine
[SerializeField] float hitDist = 1000;

void Update() {
    Ray ray = new Ray(transform.position, transform.forward);
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, hitDist)) {
        Debug.DrawLine(ray.origin, hit.point, Color.green);
        // obsługa trafienia
    }
    else {
        Debug.DrawLine(ray.origin, ray.GetPoint(hitDist), Color.red);
    }
}

Gizmos i Handles
public class BoundingBox : MonoBehaviour {
    [SerializeField] Bounds bounds;
    [SerializeField] bool filled;

    void OnDrawGizmosSelected() {
        Gizmos.matrix = transform.localToWorldMatrix;
        if (filled) {
            Gizmos.DrawCube(bounds.center, bounds.size);
        }
        Gizmos.DrawWireCube(bounds.center, bounds.size);
    }
}
void OnDrawGizmos() {
    Handles.Label(transform.position,
        $"{gameObject.name} {transform.position}", 
        EditorStyles.miniButton);
}

Podsumowanie…

Na koniec warto wspomnieć, że czasami do wsparcia kodu debugowania przydają się narzędzia lub skrypty firm trzecich. Należy jednak zachować ostrożność przy ich dodawaniu, ponieważ mogą oferować funkcje zbędne w projekcie, a nawet go utrudniać. Mimo to mam nadzieję, że poznałeś techniki, które możesz wykorzystać przy usuwaniu nowych błędów lub pogłębieniu zrozumienia systemu.