Gomorry Al-Mamlakah: The Development Journey of My Unity 2D Game
- 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.

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.
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:
Ha’il: A desert environment with rocky formations.
Al-Majalis Al-Tis’inat: A nostalgic setting inspired by 1990s-style gatherings.
Buraidah: Traditional architecture and markets.
Old Diriyah: A historic atmosphere with mud-brick buildings.
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.


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
Game Over Animation Repeating: I had to find a way to play the video only once before fading.
Scaling UI Elements: Ensuring the UI adapted to different screen sizes was tricky.
WebGL Issues: The game worked in Unity Editor but displayed a black screen when built.
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
Kenney Shape - system FOR 3D model
AI Retouch: Remove Unwanted Objects from Photos with AI | Photoroom
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