Author: Saman

  • How to Set Up 2D Movement with Rigidbody Collisions in Unity

    How to Set Up 2D Movement with Rigidbody Collisions in Unity

    When creating 2D games in Unity, setting up smooth player movement that respects game world collisions is essential. In this guide, we’ll walk through the process of building a basic 2D character controller that handles movement and rigidbody collision detection using Unity’s physics system.

    Prerequisites

    • Unity installed (version 2020 or newer recommended)
    • Familiarity with Unity’s basic components
    • Understanding of the Unity physics system (especially Rigidbody2D)

    We will also utilize the new Input System package, so make sure your project has it installed and set up.


    Step 1: Adding Components to the Player

    To begin, ensure your player GameObject has the following components:

    1. Rigidbody2D: Set the body type to Kinematic, as we are moving the player through scripts.
    2. Collider2D: Use a suitable collider such as BoxCollider2D or CapsuleCollider2D to define the player’s collision shape.
    3. PlayerInput: This component integrates with Unity’s new Input System.

    These components ensure that your player GameObject can detect and interact with collision shapes.


    Step 2: Writing the Player Script

    Here’s a complete script for handling player movement and collision detection:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.InputSystem;
    
    public class Player : MonoBehaviour
    {
        public float moveSpeed = 1f;
        public float collisionOffset = 0.05f;
        public ContactFilter2D movementFilter;
    
        private Vector2 moveInput;
        private List<RaycastHit2D> castCollisions = new List<RaycastHit2D>();
        private Rigidbody2D rb;
    
        public void Start()
        {
            rb = GetComponent<Rigidbody2D>();
        }
    
        public void FixedUpdate()
        {
            bool success = MovePlayer(moveInput);
            if (!success)
            {
                success = MovePlayer(new Vector2(moveInput.x, 0));
                if (!success)
                {
                    success = MovePlayer(new Vector2(0, moveInput.y));
                }
            }
        }
    
        public bool MovePlayer(Vector2 direction)
        {
            int count = rb.Cast(
                direction,
                movementFilter,
                castCollisions,
                moveSpeed * Time.fixedDeltaTime + collisionOffset);
    
            if (count == 0)
            {
                Vector2 moveVector = direction * moveSpeed * Time.fixedDeltaTime;
                rb.MovePosition(rb.position + moveVector);
                return true;
            }
            else
            {
                return false;
            }
        }
    
        public void OnMove(InputValue value)
        {
            moveInput = value.Get<Vector2>();
        }
    
        public void OnFire()
        {
            Debug.Log("Shots fired");
        }
    }
    

    Step 3: Understanding the Key Script Components

    Collision Detection

    The script uses Rigidbody2D.Cast() to check for potential collisions before moving the player.

    • Direction: Determines where to cast the detection ray.
    • Movement Filter: Specifies what layers can block movement.
    • Collision Offset: A small buffer to prevent the player from getting stuck.

    Handling Smooth Movement

    When diagonal movement causes a collision, the player checks horizontal and vertical paths independently, ensuring fluid sliding behavior along obstacles.

    FixedUpdate vs Update

    We use FixedUpdate instead of Update because physics interactions should be handled in FixedUpdate for consistent and accurate results.


    Step 4: Setting Up the Movement Filter

    In Unity, configure the ContactFilter2D by selecting the appropriate collision layers under the Player component’s settings. This ensures the player interacts only with specified objects.


    Step 5: Testing the Movement

    1. Attach the script to your player GameObject.
    2. Set the Rigidbody2D to Kinematic.
    3. Assign movement values for speed and configure the collision offset as needed.
    4. Press Play and test movement.

    Troubleshooting Tips

    • Ensure your player and obstacles have colliders.
    • Verify that the Rigidbody2D component is set to Kinematic.
    • Double-check the layers in the ContactFilter2D settings.

    Conclusion

    By following these steps, you now have a basic player movement setup with collision handling in Unity. This approach provides a smooth and responsive movement system, allowing characters to slide along obstacles and ensuring a more polished gameplay experience. Happy coding!

  • Creating an Animation from Sprite Sheets in Unity

    Creating an Animation from Sprite Sheets in Unity

    Quick Overview

    • Enable 2D Sprite Support: Install the 2D Sprite package via Window > Package Manager.
    • Download the Asset: Grab the free fire animation from Brullov on Itch.io.
    • Import & Configure: Drag the chosen sprite sheet (e.g., burning loop 3) into your project, set its Texture Type to Sprite (2D and UI), and switch Sprite Mode to Multiple.
    • Slice the Sheet: Open the Sprite Editor, choose Grid by Cell Count, enter 6 for columns, then slice and apply.
    • Create the Animation: Drag all the sliced sprites into the scene; Unity will prompt you to save a new animation (e.g., fire_animation.anim).
    • Adjust & Test: Modify the animation speed via the Animator Controller and position/scale the object as needed.

    In-Depth Step-by-Step Guide

    1. Enable the 2D Sprite Package

    Before working with sprite sheets, ensure Unity has the necessary tools:

    1. Navigate to Window > Package Manager.
    2. Select the Unity Registry tab.
    3. Find and install the 2D Sprite package if it’s not already installed.

    2. Download the Fire Animation Asset

    For this tutorial, we use a fire animation asset by Brullov, available for free on Itch.io. You can also support the developer by naming your own price.

    After downloading, unzip the file and locate the orange color folder under Loops.

    3. Importing and Configuring the Sprite Sheet

    1. Import: Drag the chosen sprite sheet (e.g., burning loop 3) into your project’s Materials folder.
    2. Configure: With the sprite sheet selected:
      • Set Texture Type to Sprite (2D and UI).
      • Change Sprite Mode to Multiple.
      • Click Apply in the Inspector.

    4. Slicing the Sprite Sheet

    To prepare your sprite sheet for animation:

    1. Click the Sprite Editor button (visible after configuring the sprite).
    2. If prompted, click OK to apply the settings.
    3. In the Sprite Editor, choose Slice > Grid by Cell Count.
    4. Enter the number of columns as 6 (for our fire animation).
    5. Click Slice and then Apply to finalize the cuts.

    5. Creating the Animation

    1. Drag and Drop: Select all the sliced sprites and drag them into your scene.
    2. Save Animation: Unity will prompt you to create a new animation. Name the file (e.g., fire_animation.anim) and save it in your Animations folder.
    3. Unity automatically creates a looping animation using the provided frames.

    6. Adjusting Animation Properties

    For fine-tuning:

    1. Open the Animator Controller linked with your new animation.
    2. Select the animation clip and adjust the Speed parameter as desired:
      • Increase the speed value to animate faster.
      • Decrease it for a slower, more dramatic effect.
    3. Test your changes by playing the animation in the scene.

    7. Positioning and Testing in Your Scene

    1. Position: Move your animated object to the desired location in your scene (e.g., just above the ground).
    2. Scale: Adjust the Transform component if you need to scale the fire effect (a scaling factor of 3 is a good starting point).
    3. Camera Alignment: Align your camera to focus on the animation via GameObject > Align with View.
    4. Test: Hit the Play button to preview your animation in action.

    Final Thoughts

    This guide provides both a quick reference for experienced developers and a detailed walkthrough for those needing step-by-step instructions. By following these steps, you can efficiently create a looping fire animation from sprite sheets in Unity. Enjoy enhancing your game with dynamic effects, and feel free to experiment further with the animation settings to suit your project’s needs.

    Happy animating!

  • How to use generics in structs and interfaces in Golang?

    How to use generics in structs and interfaces in Golang?

    Golang 1.18 introduced support for generics, allowing developers to write code that is independent of specific types. This means that functions and types can now be written to work with any set of types. In this article, we’ll explore how to use Golang generics in a struct and with interfaces.

    Generic function to handle multiple types

    Type parameters in programming have constraints that define which types they can accept. These constraints serve as guidelines. During compilation, the actual type provided must comply with the constraint; otherwise, you’ll get a compilation error.

    func IsGreater[V float64 | int](a, b V) bool {
    	return a > b
    }

    Your generic code can only perform operations that are supported by the type parameter’s constraints. Attempting string operations on a numeric-only type parameter will cause a compilation error.

    Using generics in a struct

    type Model[T any] struct {
    	Data []T
    }

    A generic type must be instantiated when used, and instantiation requires a type parameter list. Here’s an example:

    func main() {
    	// passing int as type parameter
    	modelInt := Model[int]{Data: []int{1, 2, 3}}
    	fmt.Println(modelInt.Data) // [1 2 3]
    	// passing string as type parameter
    	modelStr := Model[string]{Data: []string{"a", "b", "c"}}
    	fmt.Println(modelStr.Data) // [a b c]
    }

    Define methods on generic types

    If you declare methods on a generic type, you must repeat the type parameter declaration on the receiver, even if the type parameters are not used in the method scope. For example:

    type Model[T any] struct {
    	Data []T
    }
    
    func (m *Model[T]) Push(item T) {
    	m.Data = append(m.Data, item)
    }

    Using interfaces with Golang Generics

    Types don’t actually implement generic interfaces, they implement instantiations of generic interfaces. You can’t use a generic type (including interfaces) without instantiation.

    Here’s an example:

    type Getter[T any] interface {
    	Get() T
    }
    
    type Model[T any] struct {
    	Data []T
    } 
    
    // implements Getter
    func (m *Model[T]) Get(i int) T {
    	return m.Data[i]
    }

    Full example

    Declare a type constraint as interface

    In the next section, we’ll extract the previously defined constraint into its own interface for reuse in multiple places. Using interfaces as type constraints allows any implementing type to satisfy the constraint. For instance, if an interface has three methods and is used as a type parameter in a generic function, all type arguments must have those methods. This section also explores constraint interfaces that refer to specific types. Example:

    type Number interface {
    	int | float64
    }
    type Getter[T Number] interface {
    	Get() T
    }
    type Model[T Number] struct {
    	Data []T
    }
    
    func (m *Model[T]) Push(item T) {
    	m.Data = append(m.Data, item)
    }
    
    func (m *Model[T]) Get(i int) T {
    	return m.Data[i]
    }
    
    func main() {
    	// passing int as type parameter
    	modelInt := Model[int]{Data: []int{1, 2, 3}}
    	fmt.Println(modelInt.Data) // [1 2 3] // passing float64 as type parameter
    	modelFloat := Model[float64]{Data: []float64{1.1, 2.2, 0.02}}
    	fmt.Println(modelFloat.Data) // [1.1 2.2 0.02] modelInt.Push(4)
    	fmt.Println(modelInt.Data)   // [1 2 3 4] itemAtOne := modelFloat.Get(1)
    	fmt.Println(itemAtOne)       // 2.2
    }

    In this example, if we try to use a type that does not satisfy the Number interface, you will get a compile error.

    _ = Model[string]{Data: []string{"a", "b"}}
    // string does not satisfy Number (string missing in int | float64)

    Full example

    Express ‘Family of Types’ constraints

    To express a constraint covering all types with an underlying type we can use the `~` operator. Here is an example from the slices package Clip function:

    func Clip[S ~[]E, E any](s S) S {
    	return s[:len(s):len(s)]
    }

    Essentially, this constraint ensures that S can only be a slice that can hold elements of any data type. This allows the Clip function to be generic and work with various slice types like []int, []string, or even slices of custom structs.

    Here is a more straightforward example:

    func Hello[S ~string](s S) S {
    	return fmt.Sprintf("Hello, %s", s)
    }

    Full example

    Return zero (default) values

    The *new(T) idiom

    The preferred option, as suggested in golang-nuts, involves using new(T). While potentially less readable, this approach simplifies finding and replacing instances if a zero-value builtin is introduced in the language. It also allows for concise one-line assignments.

    The new builtin function allocates memory for a variable of any type T and returns a pointer to it. Dereferencing the result (*new(T)) effectively yields the zero value for the type T. This approach also works with type parameters.

    func Zero[T any]() T {
        return *new(T)
    }

    var of type T

    Straightforward and easier to read, though it always requires one line more:

    func Zero[T any]() T {
      var zero T
      return zero
    }

    Named return types

    You can use named returns to avoid explicitly declaring variables. While not everyone loves this style, it can be helpful in complex functions or when using defer statements. Here’s a simple example:

    func Zero[T any]() (ret T) {
      return
    }

    Full example

    In conclusion, Golang generics provide developers with the flexibility to write code that is independent of specific types. This allows for more reusable and maintainable code.

  • Understanding Interfaces in Go

    Introduction

    In this tutorial, we will dive into interfaces in Go, which are powerful tools for grouping types based on shared behaviors. By understanding interfaces, you can write cleaner, more flexible, and more maintainable code.

    What Are Interfaces?

    An interface in Go defines a set of method signatures. Any type that implements these methods automatically satisfies the interface. This allows you to treat different types uniformly when they share common behaviors.


    Example Scenario

    To explain interfaces, we’ll create a simple program that works with shapes like squares and circles. Each shape will have methods to calculate its area and circumference.

    Here’s a breakdown:

    • Square: Defined by a length property.
    • Circle: Defined by a radius property.
    • Both shapes will implement methods Area() and Circumference() that return float64 values.

    Step 1: Define Shape Types

    package main
    
    import (
    	"math"
    )
    
    type Square struct {
    	Length float64
    }
    
    func (s Square) Area() float64 {
    	return s.Length * s.Length
    }
    
    func (s Square) Circumference() float64 {
    	return 4 * s.Length
    }
    
    type Circle struct {
    	Radius float64
    }
    
    func (c Circle) Area() float64 {
    	return math.Pi * c.Radius * c.Radius
    }
    
    func (c Circle) Circumference() float64 {
    	return 2 * math.Pi * c.Radius
    }
    

    Step 2: Define the Shape Interface

    An interface groups types based on methods they implement. Here, the Shape interface requires both an Area() and a Circumference() method.

    type Shape interface {
    	Area() float64
    	Circumference() float64
    }

    Any type that implements these methods will automatically be of type Shape.


    Step 3: Write a Function to Print Shape Information

    We can create a single function that works for both squares and circles by using the Shape interface.

    func PrintShapeInfo(shape Shape) {
    	fmt.Printf("Area: %.2f\n", shape.Area())
    	fmt.Printf("Circumference: %.2f\n", shape.Circumference())
    }

    This function can accept any type that satisfies the Shape interface.


    Step 4: Use the Interface in the Main Function

    Create a slice of shapes and print their information.

    func main() {
    	shapes := []Shape{
    		Square{Length: 5},
    		Circle{Radius: 3},
    	}
    
    	for _, shape := range shapes {
    		PrintShapeInfo(shape)
    		fmt.Println("---") // Separator between shapes
    	}
    }

    Running the Code

    To run the program, use the following command:

    go run main.go

    You should see output similar to:

    Area: 25.00
    Circumference: 20.00
    ---
    Area: 28.27
    Circumference: 18.85
    ---

    Key Takeaways

    • Interfaces group types: They allow you to define behavior without specifying how types should implement it.
    • Automatic satisfaction: If a type implements all methods of an interface, it automatically satisfies that interface.
    • Flexible functions: Interfaces enable you to write general functions that work for different types.

    Full code


    Conclusion

    By leveraging interfaces, your Go programs can be more modular and easier to extend. This tutorial has shown how to define interfaces, implement them, and use them to create flexible functions.

    Happy coding!