UI بازی و سیستم امتیاز
Canvas، UI elements و state management ساده.
قدم اول: ساخت Canvas و Event System
GameObject > UI > Canvas — Unity خودکار EventSystem و Standalone Input Module میسازد. بدون EventSystem، Button کلیک نمیشود و drag روی UI کار نمیکند. اگر Scene قدیمی دارید و EventSystem حذف شده، از GameObject > UI > Event System یکی اضافه کنید — فقط یک EventSystem در Scene کافی است.
Component Canvas: Render Mode را Screen Space - Overlay بگذارید — UI روی تمام محتوای بازی رسم میشود و برای HUD (امتیاز، جان، minimap) ایدهآل است. Sort Order وقتی چند Canvas دارید مهم است: عدد بالاتر جلوتر رسم میشود. HUD معمولاً 0، Pause Menu 10، Game Over 20.
Canvas Scaler را روی UI Scale Mode = Scale With Screen Size تنظیم کنید. Reference Resolution مثلاً 1920×1080. Match Slider را روی 0.5 بین width و height بگذارید — روی موبایل portrait و desktop landscape نسبتاً متعادل scale میشود. Constant Pixel Size فقط برای بازی pixel-art retro با اندازه ثابت مناسب است.
RectTransform — نه Transform معمولی — anchor، pivot و stretch UI را کنترل میکند. ScoreText را با Alt+Shift+کلیک روی preset بالا-وسط anchor کنید. Pos Y منفی (مثلاً -40) فاصله از لبه بالا را تعیین میکند. با anchor درست، وقتی resolution Game View عوض میشود متن در جای منطقی میماند.
World Space Canvas برای health bar بالای سر دشمن: Render Mode World Space، Canvas را child دشمن کنید، scale کوچک (0.01). اسکریپت Billboard اختیاری است تا UI همیشه به Camera نگاه کند. در این tutorial تمرکز روی Screen Space است؛ World Space را برای پروژه بعدی نگه دارید.
تست اولیه: resolution Game View را بین 16:9 و 9:16 عوض کنید. placeholder متن امتیاز باید خوانا بماند و از گوشهها overflow نکند. اگر متن کوچک شد، Font Size یا Reference Resolution را تنظیم کنید — قبل از اضافه کردن SafeArea در قدم آخر.
قدم دوم: Text امتیاز و TextMeshPro
GameObject > UI > Text - TextMeshPro. اولین بار پنجره TMP Importer باز میشود — Import TMP Essentials و در صورت نیاز Examples. Legacy UI Text منسوخ شده؛ TextMeshPro با SDF در هر resolution و zoom sharp میماند و outline و shadow built-in دارد.
GameObject متن را ScoreText بنامید. Font Asset پیشفرض Liberation Sans OK است؛ برای متن فارسی فونت TTF/OTF با glyph فارسی import کنید و از Font Asset Creator فونت TMP بسازید. Font Size 48، Alignment وسط، Color سفید. از Extra Settings Outline یا Underlay برای contrast روی پسزمینه شلوغ بازی استفاده کنید. عرض RectTransform حداقل 400 unit.
اسکریپت ScoreManager.cs: Singleton ساده با Instance در Awake، فیلد SerializeField برای TextMeshProUGUI و متد public AddScore(int amount) که score را افزایش میدهد و scoreText.text را بهروز میکند. GameObject خالی GameSystems در Scene بسازید و ScoreManager را روی آن قرار دهید — جدا از Player تا با reload Player از بین نرود.
در Inspector، reference scoreText را از Hierarchy بکشید. Play بزنید و از Debug یا test script متد AddScore(10) را صدا بزنید — عدد روی HUD باید فوراً تغییر کند. اگر null reference، field در Inspector وصل نشده. برای allocation کمتر در runtime از scoreText.SetText با format string استفاده کنید.
برای UI فارسی، Alignment راست و فونت با حروف فارسی لازم است. TMP از RTL پشتیبانی میکند. label میتواند «امتیاز: {0}» باشد — در Nova Lab ترکیب فارسی و اعداد لاتین رایج است.
Popup امتیاز +10 اختیاری: Prefab TextMeshProUGUI کوچک که روی Coin spawn میشود، با Coroutine یا Animation به بالا drift و alpha fade میکند سپس Destroy. برای صدها pickup در ثانیه Object Pool بهتر است؛ برای tutorial Instantiate/Destroy کافی است.
قدم سوم: جمعآوری سکه و Trigger
Prefab Coin: Sprite دایرهای، Circle Collider 2D با Is Trigger = true، اسکریپت CoinPickup. در OnTriggerEnter2D اگر other.CompareTag("Player") نبود return؛ سپس ScoreManager.Instance.AddScore(10)، پخش صدا/particle و Destroy(gameObject). مقدار 10 را SerializeField کنید تا در Inspector tune شود.
Player: Tag Player، Rigidbody2D Dynamic، Collider با Is Trigger = false. قانون Unity: برای OnTriggerEnter2D حداقل یک body باید Rigidbody2D داشته باشد — معمولاً Player. Static Collider + Trigger بدون Rigidbody روی Coin OK است وقتی Player حرکت میکند.
چند instance Coin در Scene یا از Prefab در level design. Play — Player روی Coin برود، HUD +10، Coin ناپدید. اگر trigger fire نشد: Is Trigger روی Coin، Tag Player، Rigidbody روی Player، و اینکه Coin layer با Player block نشده را چک کنید.
AudioSource.PlayOneShot روی Player یا Coin هنگام pickup — clip کوتاه 0.1–0.3 ثانیه. Particle System one-shot burst طلایی در موقعیت Coin. scale punch با Coroutine قبل Destroy feedback بصری میدهد.
Respawn بهجای Destroy: SetActive(false)، Invoke بعد 3 ثانیه، position به startPos و SetActive(true). برای arcade loop و level design تکراری بهتر از Destroy/Instantiate مداوم است.
در AddScore بعد از update score، اگر از high score بیشتر شد PlayerPrefs.SetInt("HighScore", score). UI High Score در Game Over panel در قدم بعد نمایش داده میشود.
قدم چهارم: Game Over و Restart UI
Panel GameOver بسازید: GameObject > UI > Panel با Image پسزمینه مشکی نیمهشفاف (alpha حدود 0.7). child TextMeshPro با متن «Game Over» در اندازه بزرگ و Button با label «شروع مجدد» یا Restart. در Start متد gameOverPanel.SetActive(false) تا در gameplay دیده نشود.
GameManager.cs state machine ساده: enum GameState با Playing، Paused، GameOver. متد TriggerGameOver فقط یک بار اجرا شود — State را GameOver کنید، Time.timeScale = 0f و panel را active کنید. timeScale صفر gameplay و physics را متوقف میکند اما UI همچنان render میشود.
PlayerHealth.cs با maxHealth = 3 و currentHealth. متد TakeDamage(int dmg) currentHealth را کم میکند، UI قلبها را UpdateHearts میکند و اگر currentHealth <= 0 بود GameManager.Instance.TriggerGameOver() را صدا میزند. قلبها با Image و sprite swap پر/خالی یا Fill Amount.
Restart: متد public RestartLevel که Time.timeScale = 1f و SceneManager.LoadScene با buildIndex Scene فعلی. Button OnClick در Inspector به این متد وصل شود. فراموش نکنید timeScale را reset کنید — وگرنه Scene جدید هم frozen میماند و بازیکن فکر میکند بازی خراب است.
Pause Menu با Escape: در Update اگر Input.GetKeyDown(KeyCode.Escape) و State != GameOver آنگاه TogglePause. در pause: timeScale=0، pausePanel active، State=Paused. Resume برعکس. در GameOver Escape نباید pause را باز کند — حتماً State check کنید.
Kill zone از tutorial فیزیک: Trigger زیر Ground — OnTriggerEnter2D با Tag Player، TakeDamage(max) یا مستقیم TriggerGameOver. با این setup loop کامل دارید: بازی، امتیاز، آسیب، game over، restart. Scene را Save و Prefab Player/GameSystems را Apply کنید.
قدم پنجم: Button و Navigation
Button component: Target Graphic = Image background. Transition Color Tint — Normal، Highlighted، Pressed، Selected رنگهای distinct. Navigation Automatic — Tab و gamepad بین UI elements.
OnClick(): Inspector Button > Add Listener — GameObject GameManager را بکشید، function GameManager > RestartLevel. چند listener ممکن است. از code: restartButton.onClick.AddListener(RestartLevel); در Start — remember RemoveListener OnDestroy.
Audio click: AudioSource روی Canvas، PlayOneShot در listener قبل Restart. IPointerClickHandler برای custom behavior بدون Button — advanced.
Mobile touch: EventSystem با Input System یا Standalone Input Module touch را به pointer تبدیل میکند. Button min height 44–48 pixel (Apple HIG). Padding کافی بین buttons.
Selected state برای gamepad: EventSystem.current.SetSelectedGameObject(restartButton.gameObject) هنگام Game Over — بازیکن بدون ماوس Restart بزند.
Disable gameplay input در Game Over: PlayerController.enabled = false در TriggerGameOver — Player حرکت نکند زیر panel. در Restart scene reload همه reset.
قدم ششم: ذخیره High Score و polish
PlayerPrefs برای High Score: پس از Game Over اگر score از PlayerPrefs.GetInt("HighScore", 0) بیشتر بود، SetInt و Save. در Start HUD متن «بهترین: X» را با GetInt نشان دهید. PlayerPrefs روی desktop و mobile persist میشود — برای production و leaderboard واقعی به backend/cloud نیاز دارید.
Animator روی GameOverPanel: parameter Show از bool — transition با scale از 0.8 به 1 و ease out. یا CanvasGroup با alpha lerp در اسکریپت. Package DOTween (از Package Manager) animation declarative کوتاهتر میکند — optional برای portfolio.
SafeAreaFitter.cs روی root RectTransform HUD: Rect safeArea = Screen.safeArea; anchorMin و anchorMax را از safeArea نسبت به Screen.width/height محاسبه کنید — UI زیر notch iPhone یا home indicator Android نرود. روی Canvas child خالی SafeAreaRoot اعمال کنید.
Build Settings: Scene در Scenes In Build، Platform مورد نظر (PC/Mac یا Android)، Build And Run. روی device واقعی touch، pause، orientation و readability font را تست کنید — emulator کافی نیست برای safe area و performance.
یادداشت معماری: Singleton برای ScoreManager و GameManager در tutorial و prototype کوچک قابل قبول است. در پروژه تیمی ScriptableObject event channel (مثلاً OnScoreChanged) coupling را کم میکند و unit test را ممکن میکند. Nova Lab بعد از playable اول به این refactor اشاره میکند.
چکلیست نهایی: HUD امتیاز live، جمع سکه +10، سه برخورد = game over، restart کار میکند، pause با Escape، high score بعد بستن بازی persist. تبریک — minimal playable loop با UI کامل دارید و آماده build portfolio در Nova Lab هستید.
سوالات متداول
TextMeshPro یا Legacy Text؟
همیشه TextMeshPro. SDF rendering، outline، rich text و performance بهتر. Unity UI Text legacy deprecated — پروژه جدید TMP از روز اول.
timeScale=0 چه تأثیری دارد؟
Time.deltaTime صفر، physics و Update gameplay متوقف. UI و Animator با Update Mode Unscaled Time یا Time.unscaledDeltaTime حرکت میکنند. pause و game over standard. Restart حتماً timeScale=1.
Singleton مشکلساز نیست؟
برای tutorial و prototype قابل قبول — دسترسی سریع ScoreManager.Instance.AddScore. مشکل: test سخت، hidden dependency، duplicate در multi-scene. scale: event channel یا DI. برای Nova Lab playable اول Singleton آموزش داده میشود.
UI روی موبایل کوچک یا بریده میشود؟
Canvas Scaler Scale With Screen Size + Reference Resolution 1920×1080 + Match 0.5. Anchor به گوشه/لبه نه center با size ثابت pixel. SafeAreaFitter برای notch. Button و font حداقل readable size تست روی device واقعی.