scroll background

 Here's how to make a scrolling background/plane in Unity:

Method 1: Scrolling Texture (Material UV Offset) — Best for 2D backgrounds

using UnityEngine;

public class ScrollingBackground : MonoBehaviour
{
    public float scrollSpeedX = 0.5f;
    public float scrollSpeedY = 0f;

    private Renderer rend;

    void Start()
    {
        rend = GetComponent<Renderer>();
    }

    void Update()
    {
        float offsetX = Time.time * scrollSpeedX;
        float offsetY = Time.time * scrollSpeedY;
        rend.material.mainTextureOffset = new Vector2(offsetX, offsetY);
    }
}

Setup: Attach this script to your Plane/Quad. Make sure the material's texture Wrap Mode is set to Repeat in the texture import settings.


Method 2: Moving the Object (Looping Plane) — Good for 3D ground scrolling

using UnityEngine;

public class LoopingGround : MonoBehaviour
{
    public float speed = 5f;
    public float resetPositionZ = -20f;  // when to reset
    public float startPositionZ = 20f;   // where to reset to

    void Update()
    {
        transform.Translate(Vector3.back * speed * Time.deltaTime);

        if (transform.position.z <= resetPositionZ)
        {
            Vector3 pos = transform.position;
            pos.z = startPositionZ;
            transform.position = pos;
        }
    }
}

Tip: Use two planes side by side so there's no visible gap when one resets.


Method 3: Parallax Scrolling (2D Camera-Based)

using UnityEngine;

public class ParallaxBackground : MonoBehaviour
{
    public float parallaxFactor = 0.5f; // 0 = moves with camera, 1 = fixed
    private Transform cam;
    private Vector3 lastCamPos;

    void Start()
    {
        cam = Camera.main.transform;
        lastCamPos = cam.position;
    }

    void Update()
    {
        Vector3 delta = cam.position - lastCamPos;
        transform.position += new Vector3(delta.x * parallaxFactor, delta.y * parallaxFactor, 0);
        lastCamPos = cam.position;
    }
}

Quick Summary

Method Best For Notes
UV Offset 2D/3D textured scroll Seamless, very performant
Moving Object 3D endless runner Needs 2+ planes to loop
Parallax 2D side-scrollers Creates depth effect

For most cases, Method 1 (UV Offset) is the simplest and most performant option. Just make sure your texture's Wrap Mode is set to Repeat.

Unity VFX for Complete Beginners 🎮✨

What are VFX? VFX stands for "Visual Effects" - basically all the cool visual stuff you see in games like fire, smoke, explosions, and magic spells.


🎨 The Two Ways to Make Effects

Think of it like cooking: you can use a microwave (quick and easy) or a full kitchen (more control but harder to learn).

Method 1: Particle System 🍕 (The Microwave)

Best for: Beginners, mobile games, simple effects

What it does: Makes lots of tiny images (particles) appear and move around to create effects

Method 2: VFX Graph 🍳 (The Full Kitchen)

Best for: Advanced users, console/PC games, super realistic effects

What it does: Same thing, but gives you way more control and can handle millions of particles


📖 Method 1: Particle System (Start Here!)

Getting Started

Step 1: Make Your First Effect

  1. Look at the left side of Unity (called "Hierarchy")
  2. Right-click anywhere in that list
  3. Choose EffectsParticle System
  4. You'll instantly see white dots spraying out like a fountain!

Step 2: Make It Look Different

Click on your new Particle System, then look at the right side panel (called "Inspector"). You'll see lots of options:

  • Duration = How long the whole effect runs (like how long a firework show lasts)
  • Start Lifetime = How long each individual particle lives (like how long one spark stays visible)
  • Start Speed = How fast particles shoot out (slow drip vs. rocket launch)
  • Start Size = How big each particle is
  • Start Color = What color the particles are (orange for fire, blue for magic)

Step 3: Change the Shape

Find the Shape section and click to open it:

  • Cone = Particles spray out like a volcano 🌋 (good for fire)
  • Sphere = Particles explode outward in all directions 💥
  • Box = Particles fall straight down 🌧 (good for rain)

Step 4: Make It Pretty with Textures

Right now your particles are just white dots. Let's make them look like actual smoke or fire:

  1. Right-click in the bottom panel (Project window)
  2. Create → Material (this is like the "skin" for your particles)
  3. Click your new material
  4. Where it says "Shader", choose Particles/Standard Unlit
  5. Find a smoke or fire image online and drag it into the texture slot
  6. Drag your material onto the Particle System

Now it actually looks like the effect you wanted! 🎉


🚀 Method 2: VFX Graph (Advanced)

Only try this after you're comfortable with Particle Systems!

Installation

Step 1: Add the Tool

  1. Go to WindowPackage Manager (top menu)
  2. Type "Visual Effect Graph" in the search
  3. Click Install
  4. Wait for it to download

Step 2: Create Your Effect

  1. Right-click in the Project window (bottom panel)
  2. Choose CreateVisual EffectsVisual Effect Graph
  3. Name it something like "MyExplosion"

Step 3: Put It in Your Game

  1. Right-click in Hierarchy → Create Empty (makes a blank object)
  2. With that object selected, click Add Component
  3. Search for "Visual Effect" and add it
  4. Drag your VFX Graph file into the empty slot

Step 4: Edit the Effect

Double-click your VFX Graph file. You'll see a window full of boxes connected by lines (like a flowchart):

  • Spawn = When/how many particles appear
  • Initialize = What particles look like when born
  • Update = How particles change over time
  • Output = Final appearance before particles die

You connect these boxes to create your effect. It's like connecting LEGO blocks, but with visual effects!


💻 Making Effects Play with Code

Want an explosion when you click? Here's how:

using UnityEngine;
using UnityEngine.VFX;

public class PlayVFX : MonoBehaviour
{
    public VisualEffect vfx;  // Your effect goes here

    void Update()
    {
        // When you click the mouse...
        if (Input.GetMouseButtonDown(0))
        {
            vfx.Play();  // Play the effect!
        }
    }
}

How to use this:

  1. Create a new C# script with this code
  2. Attach it to any object in your scene
  3. Drag your VFX into the empty "vfx" slot in the Inspector

🤔 Which One Should I Use?

What You're Making Use This
Mobile game Particle System
Learning Unity for the first time Particle System
Quick prototype Particle System
High-end PC/console game VFX Graph
Need super realistic effects VFX Graph

Rule of thumb: Start with Particle System. Only use VFX Graph when Particle System can't do what you need.


💡 Pro Tips for Better Effects

  1. Fade smoothly: Use "Color over Lifetime" to make particles fade out instead of disappearing suddenly
  2. Size changes: Use "Size over Lifetime" to make particles shrink or grow (makes smoke look more realistic)
  3. Layer effects: Combine 2-3 particle systems together for complex effects (explosion = flash + smoke + debris)
  4. Add juice: Pair your VFX with sounds and screen shake for maximum impact!

🎓 What's Next?

Now that you understand the basics, you can try making:

  • 🔥 A campfire with flickering flames
  • 💥 An explosion when enemies die
  • ✨ Sparkles when you collect coins
  • 🌧 Rain falling from the sky

Remember: Every expert started as a beginner. Play around, experiment, and don't be afraid to break things. That's how you learn! 🚀

Sudoku Game in Unity

 

Sudoku Game in Unity - Complete Guide

Project Setup

1. Create New Unity Project

  • Open Unity Hub
  • Create new 2D project named "SudokuGame"
  • Unity version: 2021.3 LTS or newer recommended

2. Project Structure

Create the following folder structure in Assets:

Assets/
├── Scripts/
├── Prefabs/
├── Scenes/
├── UI/
└── Materials/

Scripts

Script 1: SudokuCell.cs

This script handles individual cell behavior.

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SudokuCell : MonoBehaviour
{
    [Header("UI References")]
    public TextMeshProUGUI numberText;
    public Image background;
    
    [Header("Colors")]
    public Color normalColor = Color.white;
    public Color selectedColor = new Color(0.73f, 0.87f, 0.98f, 1f);
    public Color highlightedColor = new Color(1f, 0.98f, 0.77f, 1f);
    public Color givenColor = new Color(0.96f, 0.96f, 0.96f, 1f);
    public Color errorColor = new Color(1f, 0.8f, 0.8f, 1f);
    
    private int row;
    private int col;
    private int value;
    private bool isGiven;
    private bool isSelected;
    private bool hasError;
    
    private SudokuManager manager;
    
    public void Initialize(int r, int c, SudokuManager mgr)
    {
        row = r;
        col = c;
        manager = mgr;
        value = 0;
        isGiven = false;
        isSelected = false;
        hasError = false;
    }
    
    public void SetValue(int val, bool given = false)
    {
        value = val;
        isGiven = given;
        
        if (val == 0)
        {
            numberText.text = "";
        }
        else
        {
            numberText.text = val.ToString();
        }
        
        UpdateAppearance();
    }
    
    public int GetValue()
    {
        return value;
    }
    
    public bool IsGiven()
    {
        return isGiven;
    }
    
    public int GetRow() { return row; }
    public int GetCol() { return col; }
    
    public void Select()
    {
        isSelected = true;
        UpdateAppearance();
    }
    
    public void Deselect()
    {
        isSelected = false;
        UpdateAppearance();
    }
    
    public void SetHighlighted(bool highlighted)
    {
        if (!isSelected)
        {
            background.color = highlighted ? highlightedColor : (isGiven ? givenColor : normalColor);
        }
    }
    
    public void SetError(bool error)
    {
        hasError = error;
        UpdateAppearance();
    }
    
    private void UpdateAppearance()
    {
        if (isSelected)
        {
            background.color = selectedColor;
        }
        else if (hasError)
        {
            background.color = errorColor;
        }
        else if (isGiven)
        {
            background.color = givenColor;
            numberText.color = Color.black;
        }
        else
        {
            background.color = normalColor;
            numberText.color = new Color(0.09f, 0.46f, 0.82f, 1f); // Blue for user input
        }
    }
    
    public void OnCellClicked()
    {
        if (!isGiven)
        {
            manager.SelectCell(this);
        }
    }
}

Script 2: SudokuGenerator.cs

This script generates Sudoku puzzles.

using System.Collections.Generic;
using UnityEngine;

public class SudokuGenerator
{
    private System.Random random = new System.Random();
    
    public int[,] GenerateSolution()
    {
        int[,] grid = new int[9, 9];
        
        // Fill diagonal 3x3 boxes
        for (int box = 0; box < 9; box += 3)
        {
            FillBox(grid, box, box);
        }
        
        // Solve the rest
        SolveSudoku(grid);
        
        return grid;
    }
    
    private void FillBox(int[,] grid, int row, int col)
    {
        List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(nums);
        
        int idx = 0;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                grid[row + i, col + j] = nums[idx++];
            }
        }
    }
    
    private void Shuffle<T>(List<T> list)
    {
        int n = list.Count;
        while (n > 1)
        {
            n--;
            int k = random.Next(n + 1);
            T value = list[k];
            list[k] = list[n];
            list[n] = value;
        }
    }
    
    public bool SolveSudoku(int[,] grid)
    {
        int row = -1;
        int col = -1;
        bool isEmpty = false;
        
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (grid[i, j] == 0)
                {
                    row = i;
                    col = j;
                    isEmpty = true;
                    break;
                }
            }
            if (isEmpty) break;
        }
        
        if (!isEmpty) return true;
        
        List<int> nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(nums);
        
        foreach (int num in nums)
        {
            if (IsValid(grid, num, row, col))
            {
                grid[row, col] = num;
                
                if (SolveSudoku(grid))
                    return true;
                
                grid[row, col] = 0;
            }
        }
        
        return false;
    }
    
    public bool IsValid(int[,] grid, int num, int row, int col)
    {
        // Check row
        for (int x = 0; x < 9; x++)
        {
            if (grid[row, x] == num)
                return false;
        }
        
        // Check column
        for (int x = 0; x < 9; x++)
        {
            if (grid[x, col] == num)
                return false;
        }
        
        // Check 3x3 box
        int boxRow = (row / 3) * 3;
        int boxCol = (col / 3) * 3;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                if (grid[boxRow + i, boxCol + j] == num)
                    return false;
            }
        }
        
        return true;
    }
    
    public int[,] CreatePuzzle(int[,] solution, int cellsToRemove)
    {
        int[,] puzzle = (int[,])solution.Clone();
        
        int removed = 0;
        while (removed < cellsToRemove)
        {
            int row = random.Next(9);
            int col = random.Next(9);
            
            if (puzzle[row, col] != 0)
            {
                puzzle[row, col] = 0;
                removed++;
            }
        }
        
        return puzzle;
    }
}

Script 3: SudokuManager.cs

Main game manager script.

using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class SudokuManager : MonoBehaviour
{
    [Header("UI References")]
    public Transform gridParent;
    public GameObject cellPrefab;
    public TMP_Dropdown difficultyDropdown;
    public TextMeshProUGUI timerText;
    public TextMeshProUGUI remainingText;
    public TextMeshProUGUI mistakesText;
    public TextMeshProUGUI hintsText;
    
    [Header("Number Buttons")]
    public Button[] numberButtons;
    public Button clearButton;
    
    [Header("Game Buttons")]
    public Button newGameButton;
    public Button checkButton;
    public Button hintButton;
    public Button clearAllButton;
    public Button solveButton;
    
    private SudokuCell[,] cells = new SudokuCell[9, 9];
    private int[,] puzzle = new int[9, 9];
    private int[,] solution = new int[9, 9];
    private int[,] userGrid = new int[9, 9];
    
    private SudokuCell selectedCell;
    private SudokuGenerator generator;
    
    private int mistakes = 0;
    private int hintsUsed = 0;
    private float elapsedTime = 0f;
    private bool isTimerRunning = false;
    
    void Start()
    {
        generator = new SudokuGenerator();
        SetupGrid();
        SetupButtons();
        NewGame();
    }
    
    void Update()
    {
        if (isTimerRunning)
        {
            elapsedTime += Time.deltaTime;
            UpdateTimer();
        }
        
        HandleKeyboardInput();
    }
    
    void SetupGrid()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                GameObject cellObj = Instantiate(cellPrefab, gridParent);
                SudokuCell cell = cellObj.GetComponent<SudokuCell>();
                cell.Initialize(row, col, this);
                cells[row, col] = cell;
            }
        }
    }
    
    void SetupButtons()
    {
        newGameButton.onClick.AddListener(NewGame);
        checkButton.onClick.AddListener(CheckSolution);
        hintButton.onClick.AddListener(GetHint);
        clearAllButton.onClick.AddListener(ClearAllUserInputs);
        solveButton.onClick.AddListener(SolvePuzzle);
        
        for (int i = 0; i < numberButtons.Length; i++)
        {
            int num = i + 1;
            numberButtons[i].onClick.AddListener(() => PlaceNumber(num));
        }
        
        clearButton.onClick.AddListener(() => PlaceNumber(0));
    }
    
    public void NewGame()
    {
        // Generate solution
        solution = generator.GenerateSolution();
        
        // Get difficulty
        int cellsToRemove = GetCellsToRemove();
        puzzle = generator.CreatePuzzle(solution, cellsToRemove);
        userGrid = (int[,])puzzle.Clone();
        
        // Display puzzle
        DisplayPuzzle();
        
        // Reset stats
        mistakes = 0;
        hintsUsed = 0;
        elapsedTime = 0f;
        isTimerRunning = true;
        UpdateStats();
    }
    
    int GetCellsToRemove()
    {
        switch (difficultyDropdown.value)
        {
            case 0: return 35; // Easy
            case 1: return 45; // Medium
            case 2: return 52; // Hard
            case 3: return 58; // Expert
            default: return 45;
        }
    }
    
    void DisplayPuzzle()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                int value = puzzle[row, col];
                cells[row, col].SetValue(value, value != 0);
            }
        }
        
        if (selectedCell != null)
        {
            selectedCell.Deselect();
            selectedCell = null;
        }
    }
    
    public void SelectCell(SudokuCell cell)
    {
        // Deselect previous cell
        if (selectedCell != null)
        {
            selectedCell.Deselect();
        }
        
        // Clear all highlights
        ClearHighlights();
        
        // Select new cell
        selectedCell = cell;
        cell.Select();
        
        // Highlight related cells
        HighlightRelatedCells(cell.GetRow(), cell.GetCol());
    }
    
    void ClearHighlights()
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                cells[i, j].SetHighlighted(false);
            }
        }
    }
    
    void HighlightRelatedCells(int row, int col)
    {
        // Highlight row, column, and 3x3 box
        for (int i = 0; i < 9; i++)
        {
            cells[row, i].SetHighlighted(true); // Row
            cells[i, col].SetHighlighted(true); // Column
        }
        
        // 3x3 box
        int boxRow = (row / 3) * 3;
        int boxCol = (col / 3) * 3;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                cells[boxRow + i, boxCol + j].SetHighlighted(true);
            }
        }
    }
    
    public void PlaceNumber(int num)
    {
        if (selectedCell == null)
        {
            Debug.Log("Please select a cell first!");
            return;
        }
        
        if (selectedCell.IsGiven())
            return;
        
        int row = selectedCell.GetRow();
        int col = selectedCell.GetCol();
        
        selectedCell.SetError(false);
        
        if (num == 0)
        {
            selectedCell.SetValue(0);
            userGrid[row, col] = 0;
        }
        else
        {
            selectedCell.SetValue(num);
            userGrid[row, col] = num;
            
            // Check if it's wrong
            if (num != solution[row, col])
            {
                selectedCell.SetError(true);
                mistakes++;
            }
        }
        
        UpdateStats();
        CheckIfComplete();
    }
    
    public void CheckSolution()
    {
        bool hasErrors = false;
        
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                cells[row, col].SetError(false);
                
                int userValue = userGrid[row, col];
                int correctValue = solution[row, col];
                
                if (userValue != 0 && userValue != correctValue)
                {
                    cells[row, col].SetError(true);
                    hasErrors = true;
                }
            }
        }
        
        if (!hasErrors)
        {
            bool complete = IsGridComplete();
            if (complete)
            {
                isTimerRunning = false;
                ShowMessage($"Congratulations! Solved in {FormatTime((int)elapsedTime)}!");
            }
            else
            {
                ShowMessage("No mistakes so far! Keep going!");
            }
        }
        else
        {
            ShowMessage("Some cells are incorrect (marked in red).");
        }
    }
    
    public void GetHint()
    {
        // Find empty cells
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (puzzle[row, col] == 0 && userGrid[row, col] == 0)
                {
                    // Fill with correct value
                    userGrid[row, col] = solution[row, col];
                    cells[row, col].SetValue(solution[row, col]);
                    cells[row, col].SetError(false);
                    
                    hintsUsed++;
                    UpdateStats();
                    CheckIfComplete();
                    return;
                }
            }
        }
        
        ShowMessage("No hints available!");
    }
    
    public void SolvePuzzle()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (!cells[row, col].IsGiven())
                {
                    cells[row, col].SetValue(solution[row, col]);
                    cells[row, col].SetError(false);
                    userGrid[row, col] = solution[row, col];
                }
            }
        }
        
        isTimerRunning = false;
        UpdateStats();
    }
    
    public void ClearAllUserInputs()
    {
        for (int row = 0; row < 9; row++)
        {
            for (int col = 0; col < 9; col++)
            {
                if (!cells[row, col].IsGiven())
                {
                    cells[row, col].SetValue(0);
                    cells[row, col].SetError(false);
                    userGrid[row, col] = 0;
                }
            }
        }
        
        if (selectedCell != null)
        {
            selectedCell.Deselect();
            selectedCell = null;
        }
        
        UpdateStats();
    }
    
    void UpdateStats()
    {
        int remaining = 0;
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (userGrid[i, j] == 0)
                    remaining++;
            }
        }
        
        remainingText.text = remaining.ToString();
        mistakesText.text = mistakes.ToString();
        hintsText.text = hintsUsed.ToString();
    }
    
    void UpdateTimer()
    {
        timerText.text = FormatTime((int)elapsedTime);
    }
    
    string FormatTime(int seconds)
    {
        int mins = seconds / 60;
        int secs = seconds % 60;
        return $"{mins:00}:{secs:00}";
    }
    
    bool IsGridComplete()
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (userGrid[i, j] != solution[i, j])
                    return false;
            }
        }
        return true;
    }
    
    void CheckIfComplete()
    {
        if (IsGridComplete())
        {
            isTimerRunning = false;
            ShowMessage($"Congratulations! Solved in {FormatTime((int)elapsedTime)}!");
        }
    }
    
    void ShowMessage(string message)
    {
        Debug.Log(message);
        // You can implement a proper UI popup here
    }
    
    void HandleKeyboardInput()
    {
        if (selectedCell == null)
            return;
        
        // Number input
        for (int i = 1; i <= 9; i++)
        {
            if (Input.GetKeyDown(KeyCode.Alpha0 + i) || Input.GetKeyDown(KeyCode.Keypad0 + i))
            {
                PlaceNumber(i);
            }
        }
        
        // Clear
        if (Input.GetKeyDown(KeyCode.Backspace) || Input.GetKeyDown(KeyCode.Delete))
        {
            PlaceNumber(0);
        }
        
        // Arrow key navigation
        int row = selectedCell.GetRow();
        int col = selectedCell.GetCol();
        
        if (Input.GetKeyDown(KeyCode.UpArrow) && row > 0)
        {
            SelectCell(cells[row - 1, col]);
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow) && row < 8)
        {
            SelectCell(cells[row + 1, col]);
        }
        else if (Input.GetKeyDown(KeyCode.LeftArrow) && col > 0)
        {
            SelectCell(cells[row, col - 1]);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow) && col < 8)
        {
            SelectCell(cells[row, col + 1]);
        }
    }
}

UI Setup Instructions

1. Create Canvas

  1. Right-click in Hierarchy → UI → Canvas
  2. Set Canvas Scaler to "Scale With Screen Size"
  3. Reference Resolution: 1920x1080

2. Create Sudoku Grid

  1. Create Empty GameObject under Canvas, name it "GridPanel"
  2. Add Grid Layout Group component:
    • Cell Size: 60x60
    • Spacing: 0, 0
    • Constraint: Fixed Column Count = 9
  3. Add Content Size Fitter:
    • Horizontal Fit: Preferred Size
    • Vertical Fit: Preferred Size

3. Create Cell Prefab

  1. Create UI → Image, name it "SudokuCell"
  2. Add Button component (remove navigation)
  3. Add child: UI → TextMeshPro - Text
    • Name it "NumberText"
    • Font Size: 36
    • Alignment: Center
  4. Add SudokuCell script to prefab
  5. Assign references in inspector
  6. Drag to Prefabs folder
  7. Delete from scene

4. Create Number Pad

  1. Create Panel under Canvas
  2. Add Grid Layout Group:
    • Cell Size: 80x80
    • Spacing: 10, 10
    • Columns: 5
  3. Create 9 buttons (1-9) + 1 clear button
  4. Add TextMeshPro text to each button

5. Create Control Buttons

Create buttons for:

  • New Game
  • Check
  • Hint
  • Clear All
  • Solve

6. Create Stats Panel

Create TextMeshPro texts for:

  • Timer
  • Remaining cells
  • Mistakes
  • Hints used

7. Create Difficulty Dropdown

  1. UI → Dropdown - TextMeshPro
  2. Add options: Easy, Medium, Hard, Expert

Assembly and Configuration

1. Setup SudokuManager GameObject

  1. Create empty GameObject, name it "SudokuManager"
  2. Add SudokuManager script
  3. Assign all references in inspector:
    • Grid Parent → GridPanel
    • Cell Prefab → SudokuCell prefab
    • All UI elements

2. Add Box Borders

To create the thick 3x3 box borders:

  1. Create 2 horizontal and 2 vertical UI Images
  2. Set color to dark gray/black
  3. Position them to create the grid lines
  4. Set width/height to 3-4 pixels

Additional Features (Optional)

Sound Manager Script

using UnityEngine;

public class SoundManager : MonoBehaviour
{
    public AudioClip buttonClick;
    public AudioClip numberPlace;
    public AudioClip errorSound;
    public AudioClip winSound;
    
    private AudioSource audioSource;
    
    void Awake()
    {
        audioSource = GetComponent<AudioSource>();
    }
    
    public void PlayButtonClick()
    {
        audioSource.PlayOneShot(buttonClick);
    }
    
    public void PlayNumberPlace()
    {
        audioSource.PlayOneShot(numberPlace);
    }
    
    public void PlayError()
    {
        audioSource.PlayOneShot(errorSound);
    }
    
    public void PlayWin()
    {
        audioSource.PlayOneShot(winSound);
    }
}

Build Settings

  1. File → Build Settings
  2. Add current scene
  3. Select platform (PC, Mac, Android, iOS)
  4. Player Settings:
    • Company Name
    • Product Name: Sudoku
    • Default Icon
  5. Build and Run

Tips for Improvement

  1. Add animations for cell selection
  2. Implement undo/redo functionality
  3. Add save/load game state
  4. Create different themes
  5. Add achievement system
  6. Implement daily challenges
  7. Add multiplayer functionality
  8. Create tutorial mode

Common Issues & Solutions

Issue: Cells not responding to clicks

  • Check EventSystem exists in scene
  • Verify Button component on cells
  • Check raycast target on Image

Issue: Grid layout not working

  • Verify Grid Layout Group settings
  • Check anchor points on parent
  • Ensure proper cell size

Issue: Numbers not displaying

  • Check TextMeshPro package installed
  • Verify font asset assigned
  • Check text size and color

Testing Checklist

  • [ ] New game generates valid puzzle
  • [ ] All difficulty levels work
  • [ ] Cell selection works
  • [ ] Number placement works
  • [ ] Error detection works
  • [ ] Hint system works
  • [ ] Timer counts correctly
  • [ ] Check solution validates correctly
  • [ ] Solve button completes puzzle
  • [ ] Clear all removes user input
  • [ ] Keyboard controls work
  • [ ] UI scales properly on different resolutions

This guide provides everything you need to build a complete Sudoku game in Unity!

Unity Ads Mediation

 

Perfect — let’s build a single, clean, production‑ready AdsManager that handles both Interstitial and Rewarded ads in one place.
This is the version most studios use in real projects because it’s simple, reliable, and reloads ads automatically.

Below is the full code, polished and ready to drop into your Unity project.

You will need Google ads plugin: https://github.com/googleads/googleads-mobile-unity/releases/tag/v10.4.1

🎮 AdsManager.cs (Interstitial + Rewarded in one script)

using UnityEngine;
using GoogleMobileAds.Api;

public class AdsManager : MonoBehaviour
{
    public static AdsManager Instance;

    [Header("Ad Unit IDs")]
    [SerializeField] private string interstitialAdUnitId = "YOUR_INTERSTITIAL_AD_UNIT_ID";
    [SerializeField] private string rewardedAdUnitId = "YOUR_REWARDED_AD_UNIT_ID";

    private InterstitialAd interstitialAd;
    private RewardedAd rewardedAd;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
    }

    private void Start()
    {
        MobileAds.Initialize(initStatus => { });

        LoadInterstitial();
        LoadRewarded();
    }

    // -----------------------------
    // INTERSTITIAL
    // -----------------------------
    public void LoadInterstitial()
    {
        if (interstitialAd != null)
        {
            interstitialAd.Destroy();
            interstitialAd = null;
        }

        InterstitialAd.Load(interstitialAdUnitId, new AdRequest(),
            (InterstitialAd ad, LoadAdError error) =>
            {
                if (error != null)
                {
                    Debug.LogError("Interstitial failed to load: " + error);
                    return;
                }

                interstitialAd = ad;
                RegisterInterstitialEvents();
                Debug.Log("Interstitial loaded");
            });
    }

    private void RegisterInterstitialEvents()
    {
        interstitialAd.OnAdFullScreenContentClosed += () =>
        {
            Debug.Log("Interstitial closed — loading next one");
            LoadInterstitial();
        };

        interstitialAd.OnAdFullScreenContentFailed += (AdError error) =>
        {
            Debug.LogError("Interstitial failed to show: " + error);
        };
    }

    public void ShowInterstitial()
    {
        if (interstitialAd != null && interstitialAd.CanShowAd())
        {
            interstitialAd.Show();
        }
        else
        {
            Debug.Log("Interstitial not ready");
        }
    }

    // -----------------------------
    // REWARDED
    // -----------------------------
    public void LoadRewarded()
    {
        if (rewardedAd != null)
        {
            rewardedAd.Destroy();
            rewardedAd = null;
        }

        RewardedAd.Load(rewardedAdUnitId, new AdRequest(),
            (RewardedAd ad, LoadAdError error) =>
            {
                if (error != null)
                {
                    Debug.LogError("Rewarded failed to load: " + error);
                    return;
                }

                rewardedAd = ad;
                RegisterRewardedEvents();
                Debug.Log("Rewarded loaded");
            });
    }

    private void RegisterRewardedEvents()
    {
        rewardedAd.OnAdFullScreenContentClosed += () =>
        {
            Debug.Log("Rewarded closed — loading next one");
            LoadRewarded();
        };

        rewardedAd.OnAdFullScreenContentFailed += (AdError error) =>
        {
            Debug.LogError("Rewarded failed to show: " + error);
        };
    }

    public void ShowRewarded(System.Action onRewardEarned)
    {
        if (rewardedAd != null && rewardedAd.CanShowAd())
        {
            rewardedAd.Show((Reward reward) =>
            {
                Debug.Log("User earned reward: " + reward.Amount);
                onRewardEarned?.Invoke();
            });
        }
        else
        {
            Debug.Log("Rewarded not ready");
        }
    }
}

🧩 How to Use It in Your Game

1. Add AdsManager to your first scene

  • Create an empty GameObject
  • Name it AdsManager
  • Attach the script
  • Fill in your Ad Unit IDs

2. Show an Interstitial

AdsManager.Instance.ShowInterstitial();

3. Show a Rewarded Ad

AdsManager.Instance.ShowRewarded(() =>
{
    // Give the player their reward
    Debug.Log("Reward granted!");
});

⚡ Why this version is production‑ready

  • Automatically reloads ads after showing
  • Uses a singleton for easy access
  • Handles all failure cases
  • Clean separation of interstitial vs rewarded
  • Works with any mediation network (Unity Ads, AdMob, MAX, LevelPlay, etc.)

Absolutely — let’s wire up a clean, flexible UI button system that triggers your Interstitial and Rewarded ads through the AdsManager you already have.

I’ll give you:

  1. A simple UI setup (Unity UI)
  2. A ButtonHandler script that connects UI buttons to your AdsManager
  3. Optional: a polished version with disabled buttons until ads are ready

Everything stays clean and production‑friendly.


🎮 1. Unity UI Setup

In your Unity scene:

  1. Create a Canvas
  2. Inside it, create two UI Buttons:
    • Show Interstitial
    • Show Rewarded
  3. Add a TextMeshProUGUI label to each button (optional but recommended)

Name them something like:

  • Btn_Interstitial
  • Btn_Rewarded

🧩 2. ButtonHandler.cs (Connects UI to AdsManager)

This script sits on a GameObject in your scene and simply calls the AdsManager.

using UnityEngine;

public class ButtonHandler : MonoBehaviour
{
    public void OnInterstitialButtonPressed()
    {
        AdsManager.Instance.ShowInterstitial();
    }

    public void OnRewardedButtonPressed()
    {
        AdsManager.Instance.ShowRewarded(() =>
        {
            Debug.Log("Reward granted from UI button!");
            // Add your reward logic here
        });
    }
}

🎛️ 3. Hooking Up the Buttons

For each button:

  1. Select the button in the Hierarchy
  2. Scroll to the Button (Script) component
  3. In OnClick():
    • Click +
    • Drag your ButtonHandler GameObject into the slot
    • Choose the appropriate function:
      • ButtonHandler → OnInterstitialButtonPressed
      • ButtonHandler → OnRewardedButtonPressed

Done. Your UI now triggers ads.


⭐ Optional Upgrade: Disable Buttons Until Ads Are Ready

If you want a polished UX, here’s a version that disables the buttons until ads are loaded.

ButtonHandler.cs (Enhanced)

using UnityEngine;
using UnityEngine.UI;

public class ButtonHandler : MonoBehaviour
{
    [Header("UI Buttons")]
    [SerializeField] private Button interstitialButton;
    [SerializeField] private Button rewardedButton;

    private void Start()
    {
        // Disable buttons until ads are ready
        interstitialButton.interactable = false;
        rewardedButton.interactable = false;

        // Poll ad readiness
        InvokeRepeating(nameof(CheckAdStatus), 0.5f, 0.5f);
    }

    private void CheckAdStatus()
    {
        interstitialButton.interactable =
            AdsManager.Instance != null &&
            AdsManager.Instance.IsInterstitialReady();

        rewardedButton.interactable =
            AdsManager.Instance != null &&
            AdsManager.Instance.IsRewardedReady();
    }

    public void OnInterstitialButtonPressed()
    {
        AdsManager.Instance.ShowInterstitial();
    }

    public void OnRewardedButtonPressed()
    {
        AdsManager.Instance.ShowRewarded(() =>
        {
            Debug.Log("Reward granted!");
        });
    }
}

Add these helper methods to your AdsManager:

public bool IsInterstitialReady()
{
    return interstitialAd != null && interstitialAd.CanShowAd();
}

public bool IsRewardedReady()
{
    return rewardedAd != null && rewardedAd.CanShowAd();
}


Alright — let’s build this out beautifully.
You’ll get two polished systems:

  1. A Reward Popup Window (appears after a rewarded ad finishes)
  2. A Cooldown Timer (e.g., “Watch again in 30s”) that disables the button until the timer ends

Everything plugs directly into your existing AdsManager.

I’ll keep the code clean, modular, and production‑ready.


🎁 1. Reward Popup Window (UI + Script)

A. Unity UI Setup

Inside your Canvas, create:

  • Panel → name it RewardPopup
  • Inside it:
    • Text (TMP) → “You earned: X”
    • Button → “OK”

Set the panel to inactive by default.


B. RewardPopup.cs

using UnityEngine;
using TMPro;

public class RewardPopup : MonoBehaviour
{
    public static RewardPopup Instance;

    [SerializeField] private GameObject popupRoot;
    [SerializeField] private TextMeshProUGUI rewardText;

    private void Awake()
    {
        Instance = this;
        popupRoot.SetActive(false);
    }

    public void Show(string rewardMessage)
    {
        rewardText.text = rewardMessage;
        popupRoot.SetActive(true);
    }

    public void Hide()
    {
        popupRoot.SetActive(false);
    }
}

C. Triggering the popup from AdsManager

Modify your Rewarded call:

AdsManager.Instance.ShowRewarded(() =>
{
    RewardPopup.Instance.Show("You earned 100 coins!");
});

⏳ 2. Cooldown Timer (e.g., “Watch again in 30s”)

This system:

  • Disables the Rewarded button
  • Shows a countdown
  • Re-enables the button when ready
  • Works even if the app pauses or loses focus

A. UI Setup

On your Rewarded button:

  • Add a TextMeshProUGUI child → name it CooldownText
  • Set it to something like “Watch Ad”
  • Disable Raycast Target on the text

B. CooldownTimer.cs

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections;

public class CooldownTimer : MonoBehaviour
{
    [Header("Rewarded Button")]
    [SerializeField] private Button rewardedButton;
    [SerializeField] private TextMeshProUGUI cooldownText;

    [Header("Cooldown Settings")]
    [SerializeField] private int cooldownSeconds = 30;

    private bool isCoolingDown = false;

    private void Start()
    {
        cooldownText.text = "Watch Ad";
    }

    public void StartCooldown()
    {
        if (!isCoolingDown)
            StartCoroutine(CooldownRoutine());
    }

    private IEnumerator CooldownRoutine()
    {
        isCoolingDown = true;
        rewardedButton.interactable = false;

        int remaining = cooldownSeconds;

        while (remaining > 0)
        {
            cooldownText.text = $"Watch again in {remaining}s";
            yield return new WaitForSeconds(1f);
            remaining--;
        }

        cooldownText.text = "Watch Ad";
        rewardedButton.interactable = true;
        isCoolingDown = false;
    }
}

🎯 C. Hooking It All Together

1. When the user taps the Rewarded button

In your ButtonHandler:

public CooldownTimer cooldownTimer;

public void OnRewardedButtonPressed()
{
    AdsManager.Instance.ShowRewarded(() =>
    {
        RewardPopup.Instance.Show("You earned 100 coins!");
        cooldownTimer.StartCooldown();
    });
}

🌟 Final Result

You now have:

✔ A Reward Popup Window

  • Shows the reward message
  • Clean, reusable, modular

✔ A Cooldown Timer

  • Disables the button
  • Shows “Watch again in 30s”
  • Re-enables automatically
  • Prevents spam watching

✔ Fully integrated with your AdsManager

  • No duplicated logic
  • No messy UI hacks
  • Works in any scene

If you want, I can also add:

  • Animated popup (scale-in, fade-in)
  • Sound effects
  • A reward inventory system
  • A “No Ads” IAP that disables interstitials

Just tell me how fancy you want it.


classic endless runner effect

Here's how to set it up:


Basic Setup Structure

Hierarchy:

- Player (stationary at world position)
- Environment Parent (empty GameObject - this moves)
  ├─ Ground
  ├─ Obstacles
  ├─ Walls
  └─ Props

1. Environment Mover Script

Attach this to your Environment Parent object:

using UnityEngine;

public class EnvironmentMover : MonoBehaviour
{
    public float moveSpeed = 10f;
    
    void Update()
    {
        // Move environment backward (toward camera/player)
        transform.position += Vector3.back * moveSpeed * Time.deltaTime;
        
        // Alternative: move left/right for side-scrolling
        // transform.position += Vector3.left * moveSpeed * Time.deltaTime;
    }
}

2. Player Controller (Stays in Place but can move left/right)

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float strafeSpeed = 5f;
    public float minX = -3f;
    public float maxX = 3f;
    
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        
        // Move player left/right only
        Vector3 newPosition = transform.position + Vector3.right * horizontal * strafeSpeed * Time.deltaTime;
        
        // Clamp position so player stays in bounds
        newPosition.x = Mathf.Clamp(newPosition.x, minX, maxX);
        
        transform.position = newPosition;
    }
}

3. Ground/Platform Looping (Essential for Endless Effect)

For continuous ground that never ends:

using UnityEngine;

public class GroundLoop : MonoBehaviour
{
    public float groundLength = 20f; // Length of your ground piece
    public Transform player;
    
    void Update()
    {
        // When ground piece passes behind player, move it forward
        if (transform.position.z < player.position.z - groundLength)
        {
            transform.position += Vector3.forward * groundLength * 2;
        }
    }
}

Alternative Method - Using multiple ground pieces:

using UnityEngine;

public class GroundLoopSystem : MonoBehaviour
{
    public GameObject groundPrefab;
    public Transform player;
    public float groundLength = 20f;
    public int numberOfGroundPieces = 3;
    
    private GameObject[] groundPieces;
    
    void Start()
    {
        groundPieces = new GameObject[numberOfGroundPieces];
        
        for (int i = 0; i < numberOfGroundPieces; i++)
        {
            Vector3 spawnPos = Vector3.forward * (groundLength * i);
            groundPieces[i] = Instantiate(groundPrefab, spawnPos, Quaternion.identity, transform);
        }
    }
    
    void Update()
    {
        foreach (GameObject ground in groundPieces)
        {
            if (ground.transform.position.z < player.position.z - groundLength)
            {
                // Find furthest ground piece
                float maxZ = float.MinValue;
                foreach (GameObject g in groundPieces)
                {
                    if (g.transform.position.z > maxZ)
                        maxZ = g.transform.position.z;
                }
                
                // Move this ground piece ahead
                ground.transform.position = new Vector3(0, 0, maxZ + groundLength);
            }
        }
    }
}

4. Complete Scene Setup Example

Step-by-step:

  1. Create Player:

    • Create Cube or Capsule
    • Position at (0, 1, 0)
    • Add the PlayerController script
    • Tag as "Player"
  2. Create Environment Parent:

    • Create Empty GameObject named "Environment"
    • Position at (0, 0, 0)
    • Add EnvironmentMover script
  3. Create Ground:

    • Create Plane (scale to 2, 1, 10 or larger)
    • Make it child of Environment
    • Position at (0, 0, 0)
    • Duplicate 2-3 times, space them along Z-axis
    • Add GroundLoop script to each
  4. Add Obstacles:

    • Create cubes/objects as children of Environment
    • They'll move with the environment automatically

5. Advanced: Speed Increase Over Time

using UnityEngine;

public class EnvironmentMoverAdvanced : MonoBehaviour
{
    public float startSpeed = 5f;
    public float maxSpeed = 20f;
    public float acceleration = 0.5f;
    
    private float currentSpeed;
    
    void Start()
    {
        currentSpeed = startSpeed;
    }
    
    void Update()
    {
        // Gradually increase speed
        currentSpeed = Mathf.Min(currentSpeed + acceleration * Time.deltaTime, maxSpeed);
        
        // Move environment
        transform.position += Vector3.back * currentSpeed * Time.deltaTime;
    }
}

6. Camera Setup

For best effect:

using UnityEngine;

public class CameraFollow : MonoBehaviour
{
    public Transform player;
    public Vector3 offset = new Vector3(0, 5, -10);
    
    void LateUpdate()
    {
        // Keep camera following player (even though player isn't really moving)
        transform.position = player.position + offset;
        transform.LookAt(player);
    }
}

Key Tips:

Player stays at fixed Z position (like Z = 0)
Environment moves in negative Z (backward)
Ground loops/respawns to create endless feeling
Obstacles are children of Environment so they move automatically
Player can only move left/right (X-axis)


Common Issues & Fixes:

Problem: Ground has visible seams
Fix: Make sure ground pieces are exactly the same length and positioned precisely

Problem: Objects fall through ground
Fix: Make sure Rigidbody on player has "Is Kinematic" checked, or use Character Controller

Problem: Movement feels jerky
Fix: Always use Time.deltaTime and consider using FixedUpdate() for physics-based movement


DETAILED CODE BREAKDOWN


1. ENVIRONMENT MOVER - Line by Line

using UnityEngine;

What it does: Imports Unity's core functionality so you can use classes like MonoBehaviour, Vector3, Time, etc.


public class EnvironmentMover : MonoBehaviour

What it does:

  • public class - Creates a new script component you can attach to GameObjects
  • EnvironmentMover - The name of your script (must match filename)
  • : MonoBehaviour - Inherits from Unity's base class, giving access to Update(), Start(), transform, etc.

public float moveSpeed = 10f;

What it does:

  • public - Makes this variable visible in Unity Inspector so you can adjust it without editing code
  • float - Decimal number (like 10.5, 5.0, etc.)
  • moveSpeed - Variable name
  • = 10f - Default value of 10, the f means "float" (not double)

Why: Controls how fast the environment moves. Higher number = faster movement.


void Update()

What it does: This function runs every single frame (typically 60 times per second)

Why: Perfect for continuous movement since it keeps updating position constantly


transform.position += Vector3.back * moveSpeed * Time.deltaTime;

This is the CORE line. Let's break it down piece by piece:


transform

  • Every GameObject has a transform component
  • Contains position, rotation, and scale
  • transform.position is the current location in 3D space (x, y, z coordinates)

transform.position +=

  • += means "add to the current value"
  • Example: If position is (0, 0, 0) and you add (0, 0, 1), new position becomes (0, 0, 1)
  • This creates relative movement (moving FROM where you are)

Alternative:

transform.position = new Vector3(0, 0, 0); // ABSOLUTE - teleports to exact location
transform.position += Vector3.back; // RELATIVE - moves from current position

Vector3.back

Vector3 is a structure representing direction and magnitude in 3D space (x, y, z)

Unity's built-in directions:

Vector3.forward  = (0, 0, 1)   // Positive Z - toward "north"
Vector3.back     = (0, 0, -1)  // Negative Z - toward "south"
Vector3.right    = (1, 0, 0)   // Positive X - toward "east"
Vector3.left     = (-1, 0, 0)  // Negative X - toward "west"
Vector3.up       = (0, 1, 0)   // Positive Y - toward sky
Vector3.down     = (0, -1, 0)  // Negative Y - toward ground

Why Vector3.back?

  • Player faces forward (positive Z)
  • Environment must come TOWARD player
  • So environment moves in NEGATIVE Z (backward)
  • This creates illusion player is moving forward

Visual:

Player at Z=0 (stays here)
    ↓
    🧍
    ←←←← Environment moving backward (negative Z)
[Ground][Obstacles][Trees]

* moveSpeed

  • Multiplies the direction by speed
  • Vector3.back * 10 = (0, 0, -10)
  • Without this, movement would be only 1 unit per frame (way too fast!)

Example:

Vector3.back * 5 = (0, 0, -5)  // Moves 5 units backward
Vector3.back * 20 = (0, 0, -20) // Moves 20 units backward (faster)

* Time.deltaTime

THIS IS CRITICAL - THE MOST IMPORTANT CONCEPT

Problem: Update() runs at different speeds on different computers

  • Fast PC: 120 frames per second (FPS)
  • Slow PC: 30 frames per second (FPS)

Without Time.deltaTime:

transform.position += Vector3.back * 10; // Moves 10 units PER FRAME
  • On 60 FPS: Moves 10 × 60 = 600 units per second
  • On 30 FPS: Moves 10 × 30 = 300 units per second
  • Result: Different speeds on different computers!

With Time.deltaTime:

transform.position += Vector3.back * 10 * Time.deltaTime;

What is Time.deltaTime?

  • Time elapsed since last frame (in seconds)
  • At 60 FPS: Time.deltaTime ≈ 0.0166 seconds
  • At 30 FPS: Time.deltaTime ≈ 0.0333 seconds

The Math:

  • 60 FPS: 10 * 0.0166 * 60 frames = 10 units per second
  • 30 FPS: 10 * 0.0333 * 30 frames = 10 units per second
  • Result: Same speed on all computers!

Think of it like this:

  • moveSpeed = units per second (not per frame)
  • Time.deltaTime converts it to units per frame

Complete Line Explained:

transform.position += Vector3.back * moveSpeed * Time.deltaTime;

In plain English: "Every frame, move this object backward (negative Z direction) at a speed of moveSpeed units per second"

Numerical Example:

  • Current position: (0, 0, 100)
  • moveSpeed: 10
  • Time.deltaTime: 0.016 (assuming 60 FPS)
  • Calculation: (0, 0, -1) × 10 × 0.016 = (0, 0, -0.16)
  • New position: (0, 0, 100) + (0, 0, -0.16) = (0, 0, 99.84)

After 1 second (60 frames): Position becomes approximately (0, 0, 90) The object moved 10 units backward in 1 second!


2. PLAYER CONTROLLER - Detailed Breakdown

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float strafeSpeed = 5f;
    public float minX = -3f;
    public float maxX = 3f;

What these variables do:

  • strafeSpeed - How fast player moves left/right (side-to-side movement is called "strafing")
  • minX - Leftmost boundary (-3 means 3 units to the left of origin)
  • maxX - Rightmost boundary (3 means 3 units to the right of origin)

Why we need boundaries: Without them, player could move infinitely left/right and go off-screen!


void Update()
{
    float horizontal = Input.GetAxis("Horizontal");

Input.GetAxis("Horizontal") - Gets keyboard/controller input:

  • Press A or Left Arrow: Returns -1.0 (move left)
  • Press D or Right Arrow: Returns +1.0 (move right)
  • Press nothing: Returns 0.0 (no movement)
  • Smoothed: Gradually transitions between values (not instant)

Example:

// If pressing D:
horizontal = 1.0f; // Move right

// If pressing A:
horizontal = -1.0f; // Move left

// If pressing nothing:
horizontal = 0.0f; // Don't move

Vector3 newPosition = transform.position + Vector3.right * horizontal * strafeSpeed * Time.deltaTime;

Breaking it down:

  1. transform.position - Current player position, e.g., (0, 1, 0)

  2. Vector3.right - Direction vector (1, 0, 0) - pointing right

  3. Vector3.right * horizontal

    • If horizontal = 1 (pressing D): (1, 0, 0) × 1 = (1, 0, 0) - move right
    • If horizontal = -1 (pressing A): (1, 0, 0) × -1 = (-1, 0, 0) - move left
    • If horizontal = 0 (no input): (1, 0, 0) × 0 = (0, 0, 0) - don't move
  4. * strafeSpeed * Time.deltaTime

    • Same concept as before - makes movement frame-rate independent
    • strafeSpeed controls how fast
  5. transform.position +

    • Adds movement to current position
    • Stores in temporary variable newPosition (doesn't apply yet)

Example Calculation:

  • Current position: (0, 1, 0)
  • Pressing D (horizontal = 1)
  • strafeSpeed = 5
  • Time.deltaTime = 0.016
  • Movement: (1, 0, 0) × 1 × 5 × 0.016 = (0.08, 0, 0)
  • newPosition: (0, 1, 0) + (0.08, 0, 0) = (0.08, 1, 0)

newPosition.x = Mathf.Clamp(newPosition.x, minX, maxX);

Mathf.Clamp(value, min, max) - Restricts a value between min and max

How it works:

Mathf.Clamp(5, 0, 10)   = 5   // Within range, stays same
Mathf.Clamp(15, 0, 10)  = 10  // Too high, clamped to max
Mathf.Clamp(-5, 0, 10)  = 0   // Too low, clamped to min

In our case:

// If newPosition.x = 4 (player went too far right)
// minX = -3, maxX = 3
newPosition.x = Mathf.Clamp(4, -3, 3) = 3; // Stopped at right boundary

// If newPosition.x = -5 (player went too far left)
newPosition.x = Mathf.Clamp(-5, -3, 3) = -3; // Stopped at left boundary

Why: Keeps player within playable area, can't escape boundaries


transform.position = newPosition;

Finally applies the new position to the player

  • We calculated the movement
  • We clamped it to boundaries
  • Now we actually move the player

Why we used a temporary variable:

  • Allows us to modify X coordinate only
  • Keeps Y and Z unchanged
  • If we modified transform.position.x directly, it wouldn't work (Unity doesn't allow it)

3. GROUND LOOP - Detailed Breakdown

public class GroundLoop : MonoBehaviour
{
    public float groundLength = 20f;
    public Transform player;

Variables:

  • groundLength - How long your ground piece is in Unity units
  • Transform player - Reference to player's transform (drag player object here in Inspector)

Why Transform not GameObject?

  • We only need position data
  • Transform is lighter and more direct

void Update()
{
    if (transform.position.z < player.position.z - groundLength)

Breaking down the condition:

  1. transform.position.z - Current Z position of THIS ground piece

  2. player.position.z - Player's Z position (usually stays around 0)

  3. player.position.z - groundLength - Trigger point behind player

Visual explanation:

        Trigger Point          Player
              ↓                  ↓
[-20]=========[-10]=============[0]=========[10]==========
      Ground Piece A         Ground Piece B

When does the condition trigger?

  • Player at Z = 0
  • groundLength = 20
  • Trigger point: 0 - 20 = -20
  • If ground piece A is at Z = -21 (past trigger point), it needs to teleport forward

The Logic: "If this ground piece has passed 20 units behind the player, teleport it ahead"


transform.position += Vector3.forward * groundLength * 2;

Breaking it down:

  1. Vector3.forward - Direction (0, 0, 1) - positive Z

  2. * groundLength * 2 - How far to move forward

    • If groundLength = 20
    • Movement: (0, 0, 1) × 20 × 2 = (0, 0, 40)

Why multiply by 2?

  • You typically have 2-3 ground pieces
  • Moving by groundLength × 2 ensures it goes ahead of other pieces
  • Creates seamless loop

Visual:

BEFORE:
Ground A at Z=-21 (behind player)
Ground B at Z=0
Ground C at Z=20

AFTER (Ground A teleports):
Ground B at Z=0
Ground C at Z=20
Ground A at Z=19 (was -21, moved +40)

4. ADVANCED VERSION - Multiple Ground Pieces

private GameObject[] groundPieces;

GameObject[] - Array (list) of GameObjects

  • Can store multiple ground pieces
  • Access them like: groundPieces[0], groundPieces[1], etc.

void Start()
{
    groundPieces = new GameObject[numberOfGroundPieces];

new GameObject[3] - Creates empty array with 3 slots

  • groundPieces[0] = null
  • groundPieces[1] = null
  • groundPieces[2] = null

for (int i = 0; i < numberOfGroundPieces; i++)
{
    Vector3 spawnPos = Vector3.forward * (groundLength * i);
    groundPieces[i] = Instantiate(groundPrefab, spawnPos, Quaternion.identity, transform);
}

for loop - Repeats code multiple times

Iteration breakdown:

// i = 0:
spawnPos = (0, 0, 1) * (20 * 0) = (0, 0, 0)    // First ground at origin

// i = 1:
spawnPos = (0, 0, 1) * (20 * 1) = (0, 0, 20)   // Second ground 20 units ahead

// i = 2:
spawnPos = (0, 0, 1) * (20 * 2) = (0, 0, 40)   // Third ground 40 units ahead

Instantiate() - Creates a copy of a GameObject

  • groundPrefab - What to copy
  • spawnPos - Where to create it
  • Quaternion.identity - No rotation (0, 0, 0)
  • transform - Parent object (makes it child of this object)

foreach (GameObject ground in groundPieces)

foreach - Loop through each item in array

  • Simpler than for loop when you need to check all items
  • ground is temporary variable representing current ground piece

float maxZ = float.MinValue;
foreach (GameObject g in groundPieces)
{
    if (g.transform.position.z > maxZ)
        maxZ = g.transform.position.z;
}

Purpose: Find the furthest ground piece forward

float.MinValue - Smallest possible float number (like -infinity)

  • Ensures any ground position will be larger
  • Common trick for finding maximum values

Example:

// Ground A at Z=0
// Ground B at Z=20
// Ground C at Z=40

// After loop: maxZ = 40 (furthest forward)

ground.transform.position = new Vector3(0, 0, maxZ + groundLength);

Teleports ground piece ahead of the furthest piece

Example:

  • maxZ = 40 (furthest piece)
  • groundLength = 20
  • New position: (0, 0, 40 + 20) = (0, 0, 60)

Result: Ground that was behind player is now ahead of all other pieces!


KEY CONCEPTS SUMMARY

  1. Time.deltaTime - ALWAYS use for smooth, frame-rate independent movement
  2. += vs = - += adds to current (relative), = sets exact value (absolute)
  3. Vector3 directions - Unity's coordinate system for 3D space
  4. Mathf.Clamp - Restricts values within range
  5. transform.position - Object's location in world space
  6. Arrays - Store multiple objects of same type
  7. Loops - Repeat code multiple times efficiently


canva screen size 1280 by 720

 In Unity, setting your Canvas to a 1280x720 resolution involves configuring the Canvas Scaler component to handle scaling across different screen sizes. 

1. Configure the Canvas Scaler 
To ensure your UI scales correctly to 720p, you must set a "Reference Resolution": 
  • Select your Canvas: Click on the Canvas object in your Hierarchy.
  • UI Scale Mode: In the Inspector, change this to Scale With Screen Size.
  • Reference Resolution: Enter 1280 for X (Width) and 720 for Y (Height).
  • Screen Match Mode: Set this to Match Width or Height.
  • Match Slider: Usually, a value of 0.5 is best to balance scaling between different aspect ratios. 
2. Set the Game View (Editor Preview) 
To see your 1280x720 design while working in the editor:
  • Open the Game tab window.
  • Click the Resolution dropdown in the top left (it often says "Free Aspect").
  • Select 1280x720 (16:9) from the list. If it isn't there, click the + icon to add a custom resolution with those exact pixel values. 
3. Set the Default Build Resolution
To make your final game export default to this size:
  • Go to Edit > Project Settings > Player.
  • Expand Resolution and Presentation.
  • Set Default Screen Width to 1280 and Default Screen Height to 720

ScoreManager.cs

 I've created a complete score management system for Unity! Here's what's included:

ScoreManager.cs - The main system featuring:

  • Current score tracking
  • Automatic high score saving (uses PlayerPrefs)
  • Previous score storage
  • Singleton pattern for easy access
  • Event system for UI updates

ScoreUI.cs - UI display script that:

  • Automatically updates when scores change
  • Works with both TextMeshPro and legacy Unity Text
  • Customizable text prefixes

GameExample.cs - Example implementation showing how to use the system in your game


using UnityEngine;
using System;

public class ScoreManager : MonoBehaviour
{
    // Singleton instance
    public static ScoreManager Instance { get; private set; }

    // Current game session scores
    private int currentScore = 0;
    private int previousScore = 0;

    // High score (persisted)
    private int highScore = 0;

    // Events for UI updates
    public event Action OnScoreChanged;
    public event Action OnHighScoreChanged;

    // PlayerPrefs keys
    private const string HIGH_SCORE_KEY = "HighScore";

    private void Awake()
    {
        // Singleton pattern
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            LoadHighScore();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    /// 
    /// Add points to the current score
    /// 
    public void AddScore(int points)
    {
        currentScore += points;
        OnScoreChanged?.Invoke(currentScore);
        CheckHighScore();
    }

    /// 
    /// Set the score to a specific value
    /// 
    public void SetScore(int score)
    {
        currentScore = score;
        OnScoreChanged?.Invoke(currentScore);
        CheckHighScore();
    }

    /// 
    /// Reset the current score (call this at the start of a new game)
    /// 
    public void ResetScore()
    {
        previousScore = currentScore;
        currentScore = 0;
        OnScoreChanged?.Invoke(currentScore);
    }

    /// 
    /// Check if current score is a new high score and update if needed
    /// 
    private void CheckHighScore()
    {
        if (currentScore > highScore)
        {
            highScore = currentScore;
            SaveHighScore();
            OnHighScoreChanged?.Invoke(highScore);
        }
    }

    /// 
    /// Save high score to PlayerPrefs
    /// 
    private void SaveHighScore()
    {
        PlayerPrefs.SetInt(HIGH_SCORE_KEY, highScore);
        PlayerPrefs.Save();
    }

    /// 
    /// Load high score from PlayerPrefs
    /// 
    private void LoadHighScore()
    {
        highScore = PlayerPrefs.GetInt(HIGH_SCORE_KEY, 0);
    }

    /// 
    /// Manually reset the high score (useful for testing or reset button)
    /// 
    public void ResetHighScore()
    {
        highScore = 0;
        SaveHighScore();
        OnHighScoreChanged?.Invoke(highScore);
    }

    // Getters
    public int GetCurrentScore() => currentScore;
    public int GetHighScore() => highScore;
    public int GetPreviousScore() => previousScore;

    /// 
    /// Check if the current score is a new high score
    /// 
    public bool IsNewHighScore()
    {
        return currentScore >= highScore && currentScore > 0;
    }
}