Skip to main content
Glama
game-development-unity-2025.md17.5 kB
# Game Development with Unity 2025 **Updated**: 2025-11-23 | **Engine**: Unity 2023 LTS, C#, Game Design --- ## Unity Basics ```csharp // Player Movement (2D) using UnityEngine; public class PlayerMovement : MonoBehaviour { [SerializeField] private float moveSpeed = 5f; [SerializeField] private float jumpForce = 10f; private Rigidbody2D rb; private bool isGrounded; void Start() { rb = GetComponent<Rigidbody2D>(); } void Update() { // Horizontal movement float moveInput = Input.GetAxis("Horizontal"); rb.velocity = new Vector2(moveInput * moveSpeed, rb.velocity.y); // Jump if (Input.GetButtonDown("Jump") && isGrounded) { rb.velocity = new Vector2(rb.velocity.x, jumpForce); } } void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = true; } } void OnCollisionExit2D(Collision2D collision) { if (collision.gameObject.CompareTag("Ground")) { isGrounded = false; } } } --- // Camera Follow (Smooth) public class CameraFollow : MonoBehaviour { [SerializeField] private Transform target; [SerializeField] private float smoothSpeed = 0.125f; [SerializeField] private Vector3 offset; void LateUpdate() { Vector3 desiredPosition = target.position + offset; Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed); transform.position = smoothedPosition; } } --- // Collectible Items public class Collectible : MonoBehaviour { [SerializeField] private int points = 10; void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Player")) { // Add to score GameManager.Instance.AddScore(points); // Play sound AudioManager.Instance.PlaySound("Coin"); // Destroy coin Destroy(gameObject); } } } --- // Game Manager (Singleton) public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } public int score = 0; public int lives = 3; void Awake() { // Singleton pattern if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } public void AddScore(int points) { score += points; UIManager.Instance.UpdateScore(score); } public void LoseLife() { lives--; UIManager.Instance.UpdateLives(lives); if (lives <= 0) { GameOver(); } } void GameOver() { // Show game over screen UIManager.Instance.ShowGameOver(); // Stop gameplay Time.timeScale = 0; } public void RestartGame() { Time.timeScale = 1; UnityEngine.SceneManagement.SceneManager.LoadScene( UnityEngine.SceneManagement.SceneManager.GetActiveScene().name ); } } ``` --- ## 3D Movement & Combat ```csharp // Third-Person Character Controller using UnityEngine; public class ThirdPersonController : MonoBehaviour { [Header("Movement")] [SerializeField] private float moveSpeed = 5f; [SerializeField] private float sprintSpeed = 8f; [SerializeField] private float rotationSpeed = 10f; [Header("Camera")] [SerializeField] private Transform cameraTransform; private CharacterController controller; private Animator animator; private Vector3 velocity; private bool isGrounded; void Start() { controller = GetComponent<CharacterController>(); animator = GetComponent<Animator>(); } void Update() { // Ground check isGrounded = controller.isGrounded; // Get input float horizontal = Input.GetAxis("Horizontal"); float vertical = Input.GetAxis("Vertical"); Vector3 direction = new Vector3(horizontal, 0f, vertical).normalized; if (direction.magnitude >= 0.1f) { // Calculate movement direction relative to camera float targetAngle = Mathf.Atan2(direction.x, direction.z) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y; float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y, targetAngle, ref rotationSpeed, 0.1f); transform.rotation = Quaternion.Euler(0f, angle, 0f); Vector3 moveDir = Quaternion.Euler(0f, targetAngle, 0f) * Vector3.forward; // Sprint float speed = Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : moveSpeed; // Move controller.Move(moveDir.normalized * speed * Time.deltaTime); // Animate animator.SetFloat("Speed", direction.magnitude * speed); } else { animator.SetFloat("Speed", 0f); } // Gravity if (!isGrounded) { velocity.y += Physics.gravity.y * Time.deltaTime; } else { velocity.y = -2f; } controller.Move(velocity * Time.deltaTime); } } --- // Enemy AI (Simple Chase) public class EnemyAI : MonoBehaviour { [SerializeField] private Transform player; [SerializeField] private float chaseSpeed = 3f; [SerializeField] private float attackRange = 2f; [SerializeField] private int damage = 10; private UnityEngine.AI.NavMeshAgent agent; private Animator animator; private float attackCooldown = 0f; void Start() { agent = GetComponent<UnityEngine.AI.NavMeshAgent>(); animator = GetComponent<Animator>(); } void Update() { float distanceToPlayer = Vector3.Distance(transform.position, player.position); if (distanceToPlayer <= attackRange) { // Stop and attack agent.isStopped = true; AttackPlayer(); } else { // Chase player agent.isStopped = false; agent.SetDestination(player.position); animator.SetBool("IsWalking", true); } attackCooldown -= Time.deltaTime; } void AttackPlayer() { animator.SetBool("IsWalking", false); if (attackCooldown <= 0f) { animator.SetTrigger("Attack"); // Deal damage PlayerHealth playerHealth = player.GetComponent<PlayerHealth>(); if (playerHealth != null) { playerHealth.TakeDamage(damage); } attackCooldown = 2f; // Attack every 2 seconds } } } --- // Health System public class PlayerHealth : MonoBehaviour { [SerializeField] private int maxHealth = 100; private int currentHealth; void Start() { currentHealth = maxHealth; UIManager.Instance.UpdateHealthBar(currentHealth, maxHealth); } public void TakeDamage(int damage) { currentHealth -= damage; currentHealth = Mathf.Max(currentHealth, 0); // Update UI UIManager.Instance.UpdateHealthBar(currentHealth, maxHealth); // Play damage animation GetComponent<Animator>().SetTrigger("Hit"); // Check death if (currentHealth <= 0) { Die(); } } public void Heal(int amount) { currentHealth += amount; currentHealth = Mathf.Min(currentHealth, maxHealth); UIManager.Instance.UpdateHealthBar(currentHealth, maxHealth); } void Die() { GetComponent<Animator>().SetTrigger("Death"); // Disable player control GetComponent<ThirdPersonController>().enabled = false; // Game over GameManager.Instance.GameOver(); } } ``` --- ## UI & Menus ```csharp // UI Manager using UnityEngine; using UnityEngine.UI; using TMPro; public class UIManager : MonoBehaviour { public static UIManager Instance { get; private set; } [Header("HUD")] [SerializeField] private TextMeshProUGUI scoreText; [SerializeField] private TextMeshProUGUI livesText; [SerializeField] private Slider healthBar; [Header("Menus")] [SerializeField] private GameObject pauseMenu; [SerializeField] private GameObject gameOverMenu; void Awake() { Instance = this; } public void UpdateScore(int score) { scoreText.text = "Score: " + score; } public void UpdateLives(int lives) { livesText.text = "Lives: " + lives; } public void UpdateHealthBar(int current, int max) { healthBar.value = (float)current / max; } public void ShowPauseMenu() { pauseMenu.SetActive(true); Time.timeScale = 0; } public void HidePauseMenu() { pauseMenu.SetActive(false); Time.timeScale = 1; } public void ShowGameOver() { gameOverMenu.SetActive(true); } } --- // Main Menu using UnityEngine; using UnityEngine.SceneManagement; public class MainMenu : MonoBehaviour { public void PlayGame() { SceneManager.LoadScene("Level1"); } public void OpenSettings() { // Load settings scene or panel SceneManager.LoadScene("Settings"); } public void QuitGame() { Application.Quit(); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #endif } } ``` --- ## Audio ```csharp // Audio Manager using UnityEngine; using System.Collections.Generic; [System.Serializable] public class Sound { public string name; public AudioClip clip; [Range(0f, 1f)] public float volume = 1f; [Range(0.1f, 3f)] public float pitch = 1f; public bool loop = false; [HideInInspector] public AudioSource source; } public class AudioManager : MonoBehaviour { public static AudioManager Instance { get; private set; } [SerializeField] private Sound[] sounds; void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); return; } foreach (Sound s in sounds) { s.source = gameObject.AddComponent<AudioSource>(); s.source.clip = s.clip; s.source.volume = s.volume; s.source.pitch = s.pitch; s.source.loop = s.loop; } } public void PlaySound(string name) { Sound s = System.Array.Find(sounds, sound => sound.name == name); if (s == null) { Debug.LogWarning("Sound: " + name + " not found!"); return; } s.source.Play(); } public void StopSound(string name) { Sound s = System.Array.Find(sounds, sound => sound.name == name); if (s != null) { s.source.Stop(); } } } // Usage: // AudioManager.Instance.PlaySound("Jump"); // AudioManager.Instance.PlaySound("Explosion"); ``` --- ## Save System ```csharp // Save Data (PlayerPrefs - simple) public class SaveSystem : MonoBehaviour { public static void SavePlayer(PlayerData data) { PlayerPrefs.SetInt("HighScore", data.highScore); PlayerPrefs.SetInt("Level", data.level); PlayerPrefs.SetString("PlayerName", data.playerName); PlayerPrefs.Save(); } public static PlayerData LoadPlayer() { PlayerData data = new PlayerData(); data.highScore = PlayerPrefs.GetInt("HighScore", 0); data.level = PlayerPrefs.GetInt("Level", 1); data.playerName = PlayerPrefs.GetString("PlayerName", "Player"); return data; } } [System.Serializable] public class PlayerData { public int highScore; public int level; public string playerName; } --- // Save Data (JSON - advanced) using System.IO; public class SaveSystemJSON { private static string SavePath => Application.persistentDataPath + "/save.json"; public static void SavePlayer(PlayerData data) { string json = JsonUtility.ToJson(data, true); File.WriteAllText(SavePath, json); } public static PlayerData LoadPlayer() { if (File.Exists(SavePath)) { string json = File.ReadAllText(SavePath); return JsonUtility.FromJson<PlayerData>(json); } else { return new PlayerData(); } } } ``` --- ## Game Design Principles ```markdown CORE LOOP: EXAMPLE (Puzzle game): 1. See puzzle 2. Solve puzzle 3. Get reward (points, unlock next level) 4. Repeat (harder puzzle) EXAMPLE (RPG): 1. Fight enemies 2. Gain XP 3. Level up (stronger) 4. Fight harder enemies ENGAGEMENT: - Loop should be FUN (intrinsically rewarding) - Short loop (5-30 seconds): Immediate feedback - Long loop (hours): Progression, unlocks --- DIFFICULTY CURVE: PROGRESSION: Level 1: Easy (tutorial, learn mechanics) Level 2-3: Medium (practice) Level 4-5: Hard (mastery) Boss: Very Hard (test all skills) FLOW STATE: - Too easy → Boring - Too hard → Frustration - Just right → Flow (engaged, challenged) ADAPTIVE DIFFICULTY: - If player dies 3× → Offer "easy mode" - If player breezes through → Increase challenge --- JUICE (Game Feel): SCREEN SHAKE: - Player hits enemy → Camera shakes slightly - Explosion → Big shake - Makes impact feel powerful PARTICLES: - Coins: Sparkle effect - Hit: Blood/sparks - Power-up: Glow SOUND EFFECTS: - Jump: "Boing!" - Collect: "Ding!" - Hit: "Thud!" ANIMATIONS: - Character squash & stretch (cartoon physics) - Anticipation before jump (crouch) - Follow-through after landing RESULT: Game feels responsive, satisfying --- PLAYER FEEDBACK: VISUAL: - Health bar (how much HP left?) - Damage numbers (how much damage dealt?) - Minimap (where am I?) AUDIO: - Low health: Heartbeat sound - Power-up: Triumphant music - Enemy nearby: Tense music HAPTIC: - Controller vibration (hit, explosion) CLARITY: - Player should always know: * Goal (what am I trying to do?) * Progress (how close am I?) * Feedback (what just happened?) ``` --- ## Optimization ```markdown PERFORMANCE: OBJECT POOLING: - Don't Instantiate/Destroy (slow) - Pre-create pool of objects - Activate/deactivate (fast) Example: Bullets - Create 100 bullets at start (inactive) - When shoot: Activate one, position it - When bullet off-screen: Deactivate, return to pool DRAW CALLS: - Combine meshes (batching) - Use sprite atlases (one texture, many sprites) - Occlusion culling (don't render what's behind walls) PHYSICS: - Use layers (Physics Matrix: which layers collide) - Simplify colliders (box > mesh collider) - Fixed Timestep: 0.02 (50 updates/sec, not 60) MOBILE: - Reduce texture size (2048×2048 → 1024×1024) - Fewer draw calls (<100 for mobile) - Simple shaders (no complex lighting) PROFILER: - Unity Profiler (Window > Analysis > Profiler) - CPU: Where is time spent? - GPU: Rendering bottleneck? - Memory: Leaks, large allocations? ``` --- ## Publishing ```markdown PLATFORMS: PC (Steam): - Build for Windows, Mac, Linux - Steamworks SDK (achievements, cloud saves) - $100 fee per game MOBILE (iOS, Android): - Build settings: Target platform - Optimize for touch controls - In-app purchases (Unity IAP) - Apple: $99/year, Google: $25 one-time CONSOLES: - Apply to developer program (Nintendo, Sony, Xbox) - Requires devkit (special hardware) - Certification process (strict requirements) WEB (WebGL): - Build for WebGL - Host on itch.io, Newgrounds, your site - Smaller file size (compress assets) --- MONETIZATION: PREMIUM (Pay once): - $0.99 - $60 (depending on scope) - No ads, full game - Lower revenue but premium feel FREE-TO-PLAY (F2P): - Ads (Unity Ads, AdMob) - In-app purchases (cosmetics, power-ups) - Higher revenue potential (but can feel exploitative) BATTLE PASS: - Season pass ($10) - Unlock cosmetics by playing - Encourages retention --- MARKETING: TRAILER: - 30-60 seconds - Show gameplay (not just cutscenes) - Hook in first 5 seconds SOCIAL MEDIA: - Twitter: GIFs of gameplay - TikTok: Short clips - Discord: Community PRESS KIT: - Logo, screenshots, trailer - Description, key features - presskit.it (template) DEMO: - Free demo (first 3 levels) - Convert to full game - Steam Next Fest (demo events) ``` --- ## Key Takeaways 1. **Prototype fast** - Test ideas, fail quickly 2. **Juice** - Polish makes games feel good 3. **Playtesting** - Watch players, iterate 4. **Scope small** - Finish small game > abandon huge game 5. **Have fun** - If you're not enjoying it, neither will players --- ## References - Unity Learn (tutorials) - Brackeys (YouTube) - "The Art of Game Design" - Jesse Schell **Related**: `unity-advanced.md`, `game-design-patterns.md`, `mobile-optimization.md`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/seanshin0214/persona-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server