top of page

Gomorry Al-Mamlakah: The Development Journey of My Unity 2D Game

  • Writer: Yasser M.
    Yasser M.
  • Feb 4
  • 14 min read

Updated: Feb 13


Table of Contents:

Introduction

Concept and Inspiration

Development Process

Challenges Faced

Future Plans

Supportive Websites

Extra Project Details

Conclusion




Introduction:


Developing a game is an exciting yet challenging endeavor, and my journey in creating Gomorry Al-Mamlakah has been no exception. This blog post will walk you through the entire development process of my mobile game, from its initial concept to its technical execution and future plans.


Title: Gomorry Al-Mamlakah / قمري المملكة

Genre: Start as 2D Endless Runner - then it's become a 6 Stages Game with an End.

Platform: mobile Android (plans for macOS)

Engine: Unity engine

Programming Language: C#


Concept and Inspiration:


The idea for Gomorry Al-Mamlakah emerged from a combination of nostalgia, cultural references, and a desire to create a simple yet engaging mobile game.


Inspired by classic games like Flappy Bird, I wanted to integrate elements of Saudi Arabian heritage, particularly from the 1980s and 1990s. The game would feature:


  • A bird character navigating between obstacles resembling stone structures.

  • A retro Saudi soundtrack, including traditional proverbs and messages resembling old Nokia SMS texts.

  • Six unique levels based on different Saudi regions: Ha'il, Al-Majalis Al-Tis’inat, Buraidah, Old Diriyah, 90s Nights and Modern Diriyah.


Levels: The game features five levels inspired by regions of Saudi Arabia
Levels: The game features five levels inspired by regions of Saudi Arabia

  • A gradual increase in difficulty, with the bird’s speed increasing as the player progresses.

  • No power-ups or rewards, making it a pure skill-based challenge.


Game Trailer using ChatGPT - Sore

Monetization


  • Ads and In-App Purchases: Future consideration to monetize through optional ad removal or skins.



Development Process:


1. Choosing the Game Engine


I selected Unity as the development engine due to its robust features for 2D game creation, cross-platform support, and the ability to handle physics-based mechanics seamlessly. Since I was developing for Android (with future plans for iOS), Unity was the best choice.


2. Prototyping the Core Mechanics


The first step was to create the core gameplay:


  • The bird character moves forward automatically.

  • The player taps the screen to make the bird flap and ascend, with gravity pulling it down.

  • Obstacles resembling two stones appear in different positions, requiring precise timing to navigate through them.

  • The player earns a point for every successful pass, with the game continuing endlessly.


Scenes: 13 Scenes for 6 stages and 6 Maps menus plus main menu.


3. Designing the Game World


Each level was designed to reflect cultural elements of Saudi Arabia:

  1. Ha’il: A desert environment with rocky formations.

  2. Al-Majalis Al-Tis’inat: A nostalgic setting inspired by 1990s-style gatherings.

  3. Buraidah: Traditional architecture and markets.

  4. Old Diriyah: A historic atmosphere with mud-brick buildings.

  5. Modern Diriyah: A futuristic take on Saudi heritage.





4. Implementing the User Interface (UI)


The main menu was inspired by old Nokia phones, featuring a greenish color scheme. The game starts with:


  • An intro mimicking TV static, followed by the game’s logo.

  • Menu options: Start Game, Adjust Sound, Change Language, and Credits.

  • A nostalgic font and text design reminiscent of older mobile devices.


Cost me 5$ from Unity Store
Cost me 5$ from Unity Store
110 RS Saudi from NAIF-PIXEL Store
110 RS Saudi from NAIF-PIXEL Store

Total Purchases: is 230 RS Saudi if we add a Google Lineses as Devolder to publish games.


5. Audio and Soundtrack


I wanted the game to have a unique audio identity, so I incorporated:

  • Nostalgic music from the 1980s and 1990s, playing randomly in different levels.

  • Traditional Saudi proverbs and messages, appearing in text form to add an extra challenge.

  • Sound effects for the bird’s movements, game over sequence, and button clicks.


6. Implementing Game Mechanics


Some of the key mechanics I developed include:

  • Increasing speed: As the player earns more points, the bird’s speed increases, making the game more challenging.

  • Health points: The player starts with two health points; losing both results in restarting the game.

  • Game over animation: Upon losing, a short video plays, fading into the background to enhance immersion.



Game Font: Handjet file for (Arabic Language) with system called (Arabic Writer)



7. Optimizing for Different Devices


Since I was targeting Android (with future plans for iOS), I had to ensure:

  • The background scales properly across different screen sizes.

  • Performance optimization for smooth gameplay on lower-end devices.

  • Testing WebGL builds, although I encountered black screen issues that needed debugging.



8. Monetization Strategy


I want to follow Google Play’s policies and plan to implement:

  • A free version with ads.

  • A premium version without ads.

  • Possible in-game purchases for customization (without affecting gameplay balance).



9. Publishing the Game


After testing and refining, I planned the release strategy:

  • First release as a test APK for feedback.

  • Upload to Google Play Console with compliance checks.

  • Post-launch updates for bug fixes and new features.



10. scripts Game


  • MainMenu

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine.Video;
using System.Collections;

public class MainMenu : MonoBehaviour
{
    public VideoPlayer videoPlayer;    // Reference to the Video Player
    public Button startButton;         // Reference to the Start Button
    public GameObject screen;          // Reference to the Screen GameObject
    public AudioSource backgroundMusic; // Reference to the AudioSource for music
    public RawImage videoDisplay;      // Reference to the RawImage showing the video
    public float fadeDuration = 2f;    // Duration for the fade effect
    public Button muteButton;          // Button to mute all audio
    public Button unmuteButton;        // Button to unmute all audio
    public string sceneToLoad;

    void Start()
    {
        // Video Player setup
        if (videoPlayer == null)
        {
            Debug.LogError("VideoPlayer is not assigned in the Inspector.");
        }
        else
        {
            videoPlayer.loopPointReached += OnVideoEnd;
        }

        // Start Button setup
        if (startButton == null)
        {
            Debug.LogError("StartButton is not assigned in the Inspector.");
        }
        else
        {
            startButton.gameObject.SetActive(false); // Hide the button initially
            startButton.onClick.AddListener(() => StartGame(sceneToLoad)); // Add listener to button
        }

        // Screen setup
        if (screen == null)
        {
            Debug.LogError("Screen GameObject is not assigned in the Inspector.");
        }

        // Background Music setup
        if (backgroundMusic == null)
        {
            Debug.LogError("AudioSource (backgroundMusic) is not assigned in the Inspector.");
        }
        else
        {
            backgroundMusic.Stop(); // Ensure music is off initially
        }

        // Video Display setup
        if (videoDisplay == null)
        {
            Debug.LogError("VideoDisplay (RawImage) is not assigned in the Inspector.");
        }

        // Mute Button setup
        if (muteButton == null)
        {
            Debug.LogError("MuteButton is not assigned in the Inspector.");
        }
        else
        {
            muteButton.onClick.AddListener(MuteAllAudio);
            muteButton.gameObject.SetActive(true); // Show the button initially
        }

        // Unmute Button setup
        if (unmuteButton == null)
        {
            Debug.LogError("UnmuteButton is not assigned in the Inspector.");
        }
        else
        {
            unmuteButton.onClick.AddListener(UnmuteAllAudio);
            unmuteButton.gameObject.SetActive(false); // Hide initially
        }
    }

    void OnVideoEnd(VideoPlayer vp)
    {
        StartCoroutine(FadeOutVideo());

        if (backgroundMusic != null)
        {
            backgroundMusic.Play();
        }
    }

    IEnumerator FadeOutVideo()
    {
        float elapsedTime = 0f;
        Color startColor = videoDisplay.color;
        startColor.a = 1f; // Fully opaque
        Color endColor = videoDisplay.color;
        endColor.a = 0f; // Fully transparent

        while (elapsedTime < fadeDuration)
        {
            elapsedTime += Time.deltaTime;
            videoDisplay.color = Color.Lerp(startColor, endColor, elapsedTime / fadeDuration);
            yield return null;
        }

        // After fade-out, deactivate the video display and show the start button
        videoDisplay.gameObject.SetActive(false);
        startButton.gameObject.SetActive(true);
    }

    public void StartGame(string sceneName)
    {
        if (string.IsNullOrEmpty(sceneName))
        {
            Debug.LogError("Scene name is empty or null!");
            return;
        }

        if (SceneManager.GetSceneByName(sceneName) != null)
        {
            SceneManager.LoadScene(sceneName); // Load the specified scene
        }
        else
        {
            Debug.LogError("Scene '" + sceneName + "' not found! Ensure the scene is added to Build Settings.");
        }
    }
    public void MuteAllAudio()
    {
        AudioListener.volume = 0; // Mute all audio
        muteButton.gameObject.SetActive(false);
        unmuteButton.gameObject.SetActive(true);
        Debug.Log("All audio muted.");
    }

    public void UnmuteAllAudio()
    {
        AudioListener.volume = 1; // Restore audio
        muteButton.gameObject.SetActive(true);
        unmuteButton.gameObject.SetActive(false);
        Debug.Log("All audio unmuted.");
    }
}
  • OpenMultipleURLs

using UnityEngine;

public class OpenMultipleURLs : MonoBehaviour
{
    // URLs for the two buttons
    public string url1 = "https://www.instagram.com/naifpxl/";
    public string url2 = "https://www.threads.net/@yms.1995/";

    // Method for Button 1
    public void OpenURL1()
    {
        if (!string.IsNullOrEmpty(url1))
        {
            Application.OpenURL(url1);
            Debug.Log($"Opening URL 1: {url1}");
        }
        else
        {
            Debug.LogWarning("URL 1 is empty or not set.");
        }
    }

    // Method for Button 2
    public void OpenURL2()
    {
        if (!string.IsNullOrEmpty(url2))
        {
            Application.OpenURL(url2);
            Debug.Log($"Opening URL 2: {url2}");
        }
        else
        {
            Debug.LogWarning("URL 2 is empty or not set.");
        }
    }
}
  • Playermove

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

public class Playermove : MonoBehaviour
{
    public float jumpForce = 10f;
    public Rigidbody2D rb;
    public int currentScore = 0;
    public int lives = 2; 
    public Text CurrentScoreText;
    public Text gameOverText;

    // >>> Add a public reference to a game over Image <<<
    public Image gameOverImage;

    public List<GameObject> lifeUIElements;
    public AudioSource jumpSound;
    public AudioSource collisionSound;
    public AudioSource outOfBoundsSound;
    public AudioSource scoreSound;
    public AudioSource gameOverSound; 
    public Animator playerAnimator; 

    // Flag to track if Game Over sound has played
    private bool hasGameOverSoundPlayed = false; 

    // -----------------------------------------------------------
    //  Random MUSIC SETUP
    // -----------------------------------------------------------
    [Header("Music Randomization")]
    [Tooltip("Drop multiple AudioSource objects here (each with different music clips)")]
    public List<AudioSource> musicTracks;
    // Keep track of which AudioSource is currently playing (optional)
    private AudioSource currentMusicTrack;

    // -----------------------------------------------------------
    //  HIGH SCORE
    // -----------------------------------------------------------
    public int highScore = 0;
    [Tooltip("Optional: Assign a UI Text to display the high score on screen")]
    public Text highScoreText;

    // Target score and next scene
    public int targetScore;  // Score threshold for the next scene
    public string nextSceneName; // Name of the scene to load

    private void Start()
    {
        // Safety checks
        if (jumpSound == null) Debug.LogWarning("Jump sound is not assigned in the Inspector!");
        if (collisionSound == null) Debug.LogWarning("Collision sound is not assigned in the Inspector!");
        if (outOfBoundsSound == null) Debug.LogWarning("Out of bounds sound is not assigned in the Inspector!");
        if (scoreSound == null) Debug.LogWarning("Score sound is not assigned in the Inspector!");
        if (gameOverSound == null) Debug.LogWarning("Game Over sound is not assigned in the Inspector!");
        if (playerAnimator == null) Debug.LogWarning("Player Animator is not assigned in the Inspector!");
        if (lifeUIElements.Count == 0) Debug.LogWarning("Life UI elements are not assigned in the Inspector!");

        // If we have a Game Over text, hide it at the start
        if (gameOverText != null) gameOverText.gameObject.SetActive(false);

        // Hide Game Over Image at the start
        if (gameOverImage != null) gameOverImage.gameObject.SetActive(false);

        // -------------------------------------
        //  Load High Score from PlayerPrefs
        // -------------------------------------
        if (PlayerPrefs.HasKey("HighScore"))
        {
            highScore = PlayerPrefs.GetInt("HighScore");
        }
        else
        {
            PlayerPrefs.SetInt("HighScore", 0);
            highScore = 0;
        }

        // Update High Score UI text if assigned
        if (highScoreText != null)
        {
            highScoreText.text = "High Score: " + highScore;
        }

        // Initialize current score text
        if (CurrentScoreText != null)
        {
            CurrentScoreText.text = currentScore.ToString();
        }

        // Play random music
        PlayRandomMusic();
    }

    private void Update()
    {
        // Check if the player goes out of bounds vertically
        if (transform.position.y >= 6.5f || transform.position.y <= -5f)
        {
            LoseLife();
        }

        // Handle jumping with space key
        if (Input.GetKey(KeyCode.Space))
        {
            Jump();
        }

        // Handle jumping with touch input
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                Jump();
            }
        }

        // Update score text in real-time (optional)
        if (CurrentScoreText != null)
        {
            CurrentScoreText.text = currentScore.ToString();
        }
    }

    private void Jump()
    {
        Vector2 velocity = rb.velocity;
        velocity.y = jumpForce;
        rb.velocity = velocity;

        if (jumpSound != null)
        {
            jumpSound.Play();
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        // Increment score if the player triggers a "Score" object
        if (other.gameObject.tag == "Score")
        {
            currentScore += 1;

            // Play score sound
            if (scoreSound != null)
            {
                scoreSound.Play();
            }

            // -------------------------------------
            //  Check for new high score
            // -------------------------------------
            if (currentScore > highScore)
            {
                highScore = currentScore;
                PlayerPrefs.SetInt("HighScore", highScore);
                PlayerPrefs.Save();  // Good practice to save immediately

                // Update High Score UI
                if (highScoreText != null)
                {
                    highScoreText.text = "High Score:" + highScore;
                }
            }

            // Load the next scene if the target score is reached
            if (currentScore >= targetScore)
            {
                SceneManager.LoadScene(nextSceneName); // Replace with the actual scene name
            }
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        // Lose a life if the player collides with "Ground" or "Pipe"
        if (collision.collider.tag == "Ground" || collision.collider.tag == "Pipe")
        {
            LoseLife();
        }
    }

    private void LoseLife()
    {
        // Decide which sound to play (out of bounds vs. collision)
        if (outOfBoundsSound != null && (transform.position.y >= 6.5f || transform.position.y <= -5f))
        {
            outOfBoundsSound.Play();
        }
        else if (collisionSound != null)
        {
            collisionSound.Play();
        }

        if (lives > 0)
        {
            lives--;

            // Hide a life UI element
            if (lifeUIElements.Count > lives)
            {
                lifeUIElements[lives].SetActive(false);
            }

            // Reset player position
            transform.position = new Vector3(-0.94f, 0.27f, 0f);
        }

        // If lives are 0 or below, trigger Game Over
        if (lives <= 0)
        {
            if (playerAnimator != null)
            {
                playerAnimator.SetTrigger("GameOverTrigger");
            }

            // Start the Game Over routine which will reload the scene
            StartCoroutine(GameOverRoutine());
        }
    }

    private IEnumerator GameOverRoutine()
    {
        // Play Game Over sound once
        if (!hasGameOverSoundPlayed && gameOverSound != null)
        {
            gameOverSound.Play();
            hasGameOverSoundPlayed = true;
        }

        // Show Game Over text and image
        if (gameOverText != null)
        {
            gameOverText.text = $"GAME OVER\nScore: {currentScore}\nHigh Score: {highScore}";
            gameOverText.gameObject.SetActive(true);
        }
        if (gameOverImage != null)
        {
            gameOverImage.gameObject.SetActive(true);
        }

        // Wait for the sound/animation to finish
        yield return new WaitForSeconds(1f);

        // Deactivate the Player
        gameObject.SetActive(false);

        // Notify the GameManager to reload the scene
        GameManager.Instance.ReloadSceneAfterDelay(0); // Set delay as needed
    }

    private void PlayRandomMusic()
    {
        if (musicTracks == null || musicTracks.Count == 0)
        {
            Debug.LogWarning("No music tracks are assigned in the musicTracks list!");
            return;
        }

        // Stop any previously playing track (optional)
        if (currentMusicTrack != null)
        {
            currentMusicTrack.Stop();
        }

        int randomIndex = Random.Range(0, musicTracks.Count);

        // Pick a random track
        currentMusicTrack = musicTracks[randomIndex];
        currentMusicTrack.Play();
    }
}
  • GameManager

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    private static GameManager instance;

    public static GameManager Instance
    {
        get
        {
            if (instance == null)
            {
                instance = FindObjectOfType<GameManager>();
                if (instance == null)
                {
                    GameObject gm = new GameObject("GameManager");
                    instance = gm.AddComponent<GameManager>();
                }
            }
            return instance;
        }
    }

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

    public void ReloadSceneAfterDelay(float delay)
    {
        StartCoroutine(ReloadSceneCoroutine(delay));
    }

    private IEnumerator ReloadSceneCoroutine(float delay)
    {
        yield return new WaitForSeconds(delay);
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}
  • MoveRightUI

using UnityEngine;
using UnityEngine.UI;
using System.Linq; // Add this line to include LINQ

public class MoveRightUI : MonoBehaviour
{
    // Array of Text objects
    public Text[] textObjects;

    // Speed of movement
    public float speed = 5f;

    // Spawn (appear) point
    public Vector2 spawnPosition = new Vector2(-500f, 0f);

    // Disappear point
    public Vector2 disappearPosition = new Vector2(600f, 0f);

    // Array to keep track of active Text objects
    private bool[] isActive;

    private void Start()
    {
        // Ensure we have at least one Text object
        if (textObjects.Length == 0)
        {
            Debug.LogError("No Text objects assigned!");
            return;
        }

        // Initialize the active tracking array
        isActive = new bool[textObjects.Length];
        for (int i = 0; i < textObjects.Length; i++)
        {
            // Initially, set all Text objects to inactive
            isActive[i] = false;
            // Optionally, deactivate all Text objects at the start
            textObjects[i].gameObject.SetActive(false);
        }

        // Start by activating a random Text
        ActivateRandomText();
    }

    private void Update()
    {
        for (int i = 0; i < textObjects.Length; i++)
        {
            if (isActive[i])
            {
                RectTransform rectTransform = textObjects[i].GetComponent<RectTransform>();
                if (rectTransform != null)
                {
                    // Move the Text object to the right
                    rectTransform.anchoredPosition += Vector2.right * speed * Time.deltaTime;

                    // Debug log for movement
                    Debug.Log($"Text {i} Current Position: {rectTransform.anchoredPosition}");

                    // Check if the Text has passed the disappear position
                    if (rectTransform.anchoredPosition.x > disappearPosition.x)
                    {
                        // Deactivate the Text
                        DeactivateText(i);

                        // Optionally, activate another random Text
                        ActivateRandomText();
                    }
                }
                else
                {
                    Debug.LogError($"Text object at index {i} does not have a RectTransform!");
                }
            }
        }
    }

    // Activates a random inactive Text object and positions it at the spawn point
    private void ActivateRandomText()
    {
        // Find all inactive Text objects
        int[] inactiveIndices = System.Linq.Enumerable.Range(0, isActive.Length)
                                                   .Where(index => !isActive[index])
                                                   .ToArray();

        if (inactiveIndices.Length == 0)
        {
            Debug.LogWarning("All Text objects are currently active!");
            return;
        }

        // Randomly select one inactive Text object
        int randomIndex = inactiveIndices[Random.Range(0, inactiveIndices.Length)];
        Text randomText = textObjects[randomIndex];
        RectTransform rectTransform = randomText.GetComponent<RectTransform>();

        if (rectTransform == null)
        {
            Debug.LogError($"Selected Text object at index {randomIndex} does not have a RectTransform!");
            return;
        }

        // Move the Text to the spawn position
        rectTransform.anchoredPosition = spawnPosition;

        // Activate the Text (ensure it's enabled)
        randomText.gameObject.SetActive(true);

        // Mark it as active
        isActive[randomIndex] = true;
    }

    // Deactivates the Text object at the specified index
    private void DeactivateText(int index)
    {
        Text textToDeactivate = textObjects[index];
        if (textToDeactivate != null)
        {
            // Optionally, disable the GameObject
            textToDeactivate.gameObject.SetActive(false);

            // Mark it as inactive
            isActive[index] = false;

            Debug.Log($"Text {index} has been deactivated.");
        }
        else
        {
            Debug.LogError($"Text object at index {index} is null!");
        }
    }
}
  • Ground

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

public class Ground : MonoBehaviour
{
    public float speed = 4f;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(new Vector3(-1f, 0, 0) * Time.deltaTime * speed);

        if (transform.position.x <= -10.5f)
        {
            transform.position = new Vector3(1.5f, transform.position.y, transform.position.z);
        }
    }
}
  • Spawner

using System.Numerics;
using System;
using System.Threading;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour
{
    public GameObject pipePrefab;
    public Transform[] spawnPositions;
    public float startTime = 2.5f;
    private float timeBetweenSpawn;

    // Start is called before the first frame update
    void Start()
    {
        timeBetweenSpawn = startTime;
    }

    // Update is called once per frame
    void Update()
    {
        if (timeBetweenSpawn <= 0f)
        {
            SpawnPipe();
            timeBetweenSpawn = startTime;
        }
        else 
        {
            timeBetweenSpawn -= Time.deltaTime;
        }
    }

    void SpawnPipe()
    {
        int randomPoint = UnityEngine.Random.Range(0, spawnPositions.Length);
        Instantiate(pipePrefab, spawnPositions[randomPoint].position, UnityEngine.Quaternion.identity);
    }
}
  • Pipe

using System.Threading; // Remove if not needed
using UnityEngine;

public class Pipe : MonoBehaviour
{
    public float speed = 3f;

    // Update is called once per frame
    void Update()
    {
        // Move the pipe to the left
        transform.Translate(Vector2.left * speed * Time.deltaTime);

        // Destroy the pipe if it moves out of bounds
        if (transform.position.x <= -20f)
        {
            Destroy(this.gameObject);
        }
    }
}
  • VideoBackgroundController

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

public class VideoBackgroundController : MonoBehaviour
{
    [Header("UI References")]
    public RawImage rawImage;
    public Text legacyText; // UI Text element for displaying the legacy text.

    [Header("Video Setup")]
    public RenderTexture videoRenderTexture;

    [Header("Multiple Video Players")]
    public List<VideoPlayer> videoPlayers; // Assign multiple VideoPlayers in the Inspector.

    [Header("Multiple Video Clips")]
    public List<VideoClip> videoClips; // Assign your video clips in the Inspector.

    [Header("Legacy Text")]
    public List<string> videoTexts; // Add corresponding text for each video.

    private int activePlayerIndex = -1;

    void Start()
    {
        if (rawImage == null || videoRenderTexture == null || videoPlayers == null || videoPlayers.Count == 0 || videoClips == null || videoClips.Count == 0 || videoTexts == null || videoTexts.Count == 0)
        {
            Debug.LogWarning("VideoBackgroundController: Missing references in the Inspector!");
            return;
        }

        if (videoClips.Count != videoTexts.Count)
        {
            Debug.LogWarning("VideoBackgroundController: Mismatch between video clips and texts!");
            return;
        }

        PlayRandomVideo();
    }

    void PlayRandomVideo()
    {
        // Pick a random VideoClip from the list
        int randomIndex = Random.Range(0, videoClips.Count);
        VideoClip chosenClip = videoClips[randomIndex];

        // Disable all VideoPlayers first
        foreach (var player in videoPlayers)
        {
            player.enabled = false;
        }

        // Enable the selected VideoPlayer
        activePlayerIndex = randomIndex % videoPlayers.Count; // Ensures we don't exceed the available players
        var activePlayer = videoPlayers[activePlayerIndex];
        activePlayer.enabled = true;

        // Assign the chosen clip to the active VideoPlayer
        activePlayer.clip = chosenClip;

        // Set the RenderTexture for the active VideoPlayer
        activePlayer.targetTexture = videoRenderTexture;

        // Set the same RenderTexture on the RawImage
        rawImage.texture = videoRenderTexture;

        // Display the corresponding legacy text
        legacyText.text = videoTexts[randomIndex];

        // Start playback
        activePlayer.Play();
    }

    public void SwitchToNextVideo()
    {
        if (videoPlayers == null || videoPlayers.Count == 0)
            return;

        // Stop the current video
        if (activePlayerIndex >= 0 && activePlayerIndex < videoPlayers.Count)
        {
            videoPlayers[activePlayerIndex].Stop();
        }

        // Play the next video
        PlayRandomVideo();
    }
}
  • LoadSceneOnScore

using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using System.Collections; // Required for coroutines

public class LoadSceneOnScore : MonoBehaviour
{
    public int targetScore = 50;  // Target score to trigger the scene load
    public string sceneToLoad = "NextScene";  // Name of the scene to load
    public Text scoreText;  // Reference to the UI Text component displaying the score
    public Image transitionImage; // Reference to the UI Image for transition
    public AudioClip transitionSound; // Sound effect before loading scene
    private AudioSource audioSource;
    private int currentScore = 0;  // Current score value

    void Start()
    {
        if (transitionImage != null)
        {
            transitionImage.gameObject.SetActive(false); // Ensure image is hidden initially
        }
        audioSource = gameObject.AddComponent<AudioSource>();
    }

    void Update()
    {
        // Update the score display
        if (scoreText != null)
        {
            scoreText.text = "Score: " + currentScore.ToString();
        }

        // Check if the player's score has reached the target score
        if (currentScore >= targetScore)
        {
            StartCoroutine(ShowTransitionEffect());
        }
    }

    // Method to add score
    public void AddScore(int points)
    {
        currentScore += points;
    }

    // Coroutine to show image and play sound before scene transition
    IEnumerator ShowTransitionEffect()
    {
        if (transitionImage != null)
        {
            transitionImage.gameObject.SetActive(true);
            transitionImage.canvasRenderer.SetAlpha(0f);
            transitionImage.CrossFadeAlpha(1f, 1f, false); // Fade in effect
        }
        
        if (audioSource != null && transitionSound != null)
        {
            audioSource.PlayOneShot(transitionSound);
        }
        
        yield return new WaitForSeconds(2f); // Wait before scene loads
        LoadNextScene();
    }

    // Method to load the next scene
    void LoadNextScene()
    {
        SceneManager.LoadScene(sceneToLoad);
    }
}
  • ImageDisplay

using UnityEngine;
using UnityEngine.UI;

public class ImageDisplay : MonoBehaviour
{
    public Image image; // Assign your UI Image in the inspector
    private float timer = 8f;
    private bool isVisible = true;

    void Start()
    {
        if (image != null)
        {
            image.gameObject.SetActive(true); // Show the image at the start
        }
    }

    void Update()
    {
        if (isVisible)
        {
            timer -= Time.deltaTime;
            if (timer <= 0)
            {
                HideImage();
            }
        }
    }

    public void HideImage()
    {
        if (image != null)
        {
            image.gameObject.SetActive(false);
            isVisible = false;
        }
    }

    public void OnImageClick()
    {
        HideImage();
    }
}

Challenges Faced


  1. Game Over Animation Repeating: I had to find a way to play the video only once before fading.

  2. Scaling UI Elements: Ensuring the UI adapted to different screen sizes was tricky.

  3. WebGL Issues: The game worked in Unity Editor but displayed a black screen when built.

  4. Keystore for App Signing: Setting up the keystore for Google Play took extra troubleshooting.



Future Plans


  • Apple Macintosh and iOS Release: Expanding to more platforms.

  • More Nostalgic Content: Adding new soundtracks and text-based challenges.

  • Achievements and Leaderboards: Implementing a ranking system.

  • Cultural Expansions: Introducing new regions and themes reflecting Saudi history.



Supportive Websites


  1. Remove Background from Image for Free – remove.bg

  2. Sora

  3. Voice Maker - Create a Voice - Online & Free

  4. Modify image - ResizePixel

  5. Uncrop Image - Pixelcut

  6. Mobile UI Pixel Icons Pack | 2D | Unity Asset Store

  7. Kenney Shape - system FOR 3D model

  8. AI Retouch: Remove Unwanted Objects from Photos with AI | Photoroom

  9. Untitled - FlexClip

  10. Pixel It - Create pixel art from an image

  11. Fotor GoArt: Turn Your Photos into Stunning Artworks with Hundreds of Photo Effects



    Extra Project Details


    Solo Developer: Yasser Mohammed AlSharif

    Artist Pixel: Naif Al-lehadan


    Test Game Date: 2025/1/1

    Version Number: 2.0 v

    Project plan: it takes me 2 Weeks for the first version 1.0

    Editor Version: 2022.3.28f1


    Not published yet (Play Store) finish project Date: 2025/2




Conclusion


Developing Gomorry Al-Mamlakah has been a rewarding experience. From designing nostalgic elements to refining the gameplay mechanics, each step has brought me closer to creating a game that merges entertainment with cultural appreciation. The journey is far from over, and I look forward to launching and improving the game based on player feedback.


Comments


bottom of page