Ver3.5 Unity 2D 게임 개발을 위한 핵심 소스코드 예제 가이드

Unity 2D 게임 개발을 위한 핵심 소스코드 예제 가이드
실전에서 바로 쓰는 Unity 2D 기본기 총정리 🕹️
솔직히 말하면 Unity 2D는 진입장벽이 낮으면서도 퀄리티 높은 게임을 만들 수 있는 최강 엔진이야 ㅋㅋㅋ
인디게임 시장에서 Unity 2D로 만든 게임이 전체의 약 45% 이상을 차지할 정도로 압도적인 점유율을 자랑하고,
모바일·PC·콘솔 멀티플랫폼 빌드도 한 번에 가능해서 진짜 효율 갑이거든 😎
근데 막상 시작하면 "어디서부터 코드를 짜야 하지?" 하고 멘붕 오는 경우가 많잖아?
이 글에서는 Unity 2D 게임 개발에서 실제로 자주 쓰이는 핵심 소스코드 예제들을 카테고리별로 싹 정리해줄게.
복붙해서 바로 쓸 수 있는 실전 코드 위주로 구성했으니까 북마크 필수임 ㅋㅋ
2D 게임에서 플레이어 이동은 Rigidbody2D를 활용하는 방식이 표준이야.
물리 엔진을 그대로 활용하니까 중력, 충돌 처리가 자동으로 되거든. 개꿀 ㅋㅋ
using UnityEngine;
public class PlayerController : MonoBehaviour
{
// 이동 속도와 점프력 설정
public float moveSpeed = 5f;
public float jumpForce = 10f;
private Rigidbody2D rb;
private bool isGrounded = false;
void Start()
{
// Rigidbody2D 컴포넌트 가져오기
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
// 좌우 입력 받기 (-1 ~ 1 사이 값)
float moveInput = Input.GetAxis("Horizontal");
// 속도 적용 (x축만 변경, y축은 물리 엔진에 맡김)
rb.linearVelocity = new Vector2(moveInput * moveSpeed, rb.linearVelocity.y);
// 점프 처리 (땅에 있을 때만 가능)
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
isGrounded = false;
}
}
// 땅 감지 — 태그가 "Ground"인 오브젝트와 충돌 시
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
}
rb.velocity는 Unity 2022 이후 버전에서 rb.linearVelocity로 변경됐어.버전 확인하고 맞게 써야 함! 안 그러면 경고 뜨는 거 알지? ㅋㅋ
// Update() 안에 추가
void FlipSprite(float moveInput)
{
if (moveInput > 0)
{
// 오른쪽 이동 시 스프라이트 정방향
transform.localScale = new Vector3(1f, 1f, 1f);
}
else if (moveInput < 0)
{
// 왼쪽 이동 시 스프라이트 좌우 반전
transform.localScale = new Vector3(-1f, 1f, 1f);
}
}
이렇게 localScale.x를 -1로 바꾸면 스프라이트가 좌우 반전돼서 캐릭터가 방향을 바라보게 돼.
진짜 간단한데 이거 모르면 캐릭터가 항상 한쪽만 보고 있어서 어색하거든 ㅋㅋㅋ
Unity의 Animator 컴포넌트와 C# 스크립트를 연결하면 상태에 따라 자동으로 애니메이션이 전환돼.
Animator Controller에서 파라미터를 설정하고, 코드에서 그 값을 바꿔주는 방식이야 😊
using UnityEngine;
public class PlayerAnimation : MonoBehaviour
{
private Animator anim;
private Rigidbody2D rb;
void Start()
{
anim = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
// 이동 속도의 절댓값을 "Speed" 파라미터에 전달
// Animator에서 Speed > 0.1 이면 Walk 애니메이션 재생
anim.SetFloat("Speed", Mathf.Abs(rb.linearVelocity.x));
// 점프 중인지 여부를 "IsJumping" 파라미터에 전달
anim.SetBool("IsJumping", rb.linearVelocity.y > 0.1f);
// 낙하 중인지 여부
anim.SetBool("IsFalling", rb.linearVelocity.y < -0.1f);
}
// 공격 애니메이션 트리거 (버튼 누를 때 호출)
public void TriggerAttack()
{
anim.SetTrigger("Attack");
}
}
대소문자도 구분하니까 "speed"랑 "Speed"는 다른 파라미터야! 이거 때문에 삽질하는 사람 진짜 많음 ㅋㅋ
Unity 2D에서 충돌 처리는 크게 두 가지야.
OnCollision2D — 물리적 충돌 (실제로 부딪히는 것)
OnTrigger2D — 트리거 충돌 (통과하면서 이벤트 발생)
using UnityEngine;
public class ItemPickup : MonoBehaviour
{
public int scoreValue = 10;
// 트리거 영역에 들어왔을 때
void OnTriggerEnter2D(Collider2D other)
{
// 플레이어 태그를 가진 오브젝트만 처리
if (other.CompareTag("Player"))
{
// GameManager의 점수 추가 메서드 호출
GameManager.Instance.AddScore(scoreValue);
// 아이템 획득 효과음 재생
AudioManager.Instance.PlaySFX("ItemPickup");
// 아이템 오브젝트 삭제
Destroy(gameObject);
}
}
}
using UnityEngine;
public class EnemyDamage : MonoBehaviour
{
public int damageAmount = 10;
// 물리 충돌 발생 시
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Player"))
{
// 플레이어의 Health 컴포넌트 가져오기
PlayerHealth playerHealth =
collision.gameObject.GetComponent<PlayerHealth>();
if (playerHealth != null)
{
playerHealth.TakeDamage(damageAmount);
}
}
}
}
| 구분 | Collision2D | Trigger2D |
|---|---|---|
| 물리 반응 | 있음 (튕겨남) | 없음 (통과) |
| Is Trigger 설정 | 체크 해제 | 체크 필요 |
| 주요 사용처 | 벽, 바닥, 적 충돌 | 아이템, 포탈, 감지 영역 |
| 콜백 메서드 | OnCollisionEnter2D | OnTriggerEnter2D |
RPG든 플랫포머든 체력 시스템은 거의 모든 게임에 들어가잖아?
싱글톤 패턴이나 컴포넌트 방식으로 구현하는 게 일반적이야 ㅋㅋ
using UnityEngine;
using UnityEngine.Events;
public class PlayerHealth : MonoBehaviour
{
[Header("체력 설정")]
public int maxHealth = 100;
private int currentHealth;
// 체력 변화 시 UI 업데이트를 위한 이벤트
public UnityEvent<int, int> OnHealthChanged;
public UnityEvent OnPlayerDied;
// 무적 시간 관련
private bool isInvincible = false;
public float invincibleDuration = 1.5f;
void Start()
{
currentHealth = maxHealth;
// 시작 시 UI 초기화
OnHealthChanged?.Invoke(currentHealth, maxHealth);
}
// 데미지 받기
public void TakeDamage(int damage)
{
// 무적 상태면 데미지 무시
if (isInvincible) return;
currentHealth -= damage;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
// 체력 변화 이벤트 발생
OnHealthChanged?.Invoke(currentHealth, maxHealth);
if (currentHealth <= 0)
{
Die();
}
else
{
// 무적 시간 시작
StartCoroutine(InvincibleCoroutine());
}
}
// 체력 회복
public void Heal(int amount)
{
currentHealth += amount;
currentHealth = Mathf.Clamp(currentHealth, 0, maxHealth);
OnHealthChanged?.Invoke(currentHealth, maxHealth);
}
// 사망 처리
private void Die()
{
OnPlayerDied?.Invoke();
// 사망 애니메이션, 게임오버 처리 등
Debug.Log("플레이어 사망!");
}
// 무적 시간 코루틴
private System.Collections.IEnumerator InvincibleCoroutine()
{
isInvincible = true;
yield return new WaitForSeconds(invincibleDuration);
isInvincible = false;
}
}
Mathf.Clamp로 체력 값을 0~maxHealth 사이로 제한하지 않으면음수 체력이나 최대치 초과 같은 버그가 생길 수 있어. 꼭 넣어줘야 함!
간단한 2D 게임에서 적 AI는 순찰(Patrol)과 추적(Chase) 두 가지 상태로 구현하는 게 기본이야.
State Machine 패턴을 쓰면 나중에 확장도 쉬워서 강추임 ㅋㅋ
using UnityEngine;
public class EnemyAI : MonoBehaviour
{
// 적 상태 열거형
public enum EnemyState { Patrol, Chase, Attack }
public EnemyState currentState = EnemyState.Patrol;
[Header("이동 설정")]
public float patrolSpeed = 2f;
public float chaseSpeed = 4f;
public float detectionRange = 5f; // 플레이어 감지 범위
public float attackRange = 1.5f; // 공격 범위
[Header("순찰 설정")]
public Transform[] patrolPoints; // 순찰 지점 배열
private int currentPatrolIndex = 0;
private Transform player;
private Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// 씬에서 "Player" 태그 오브젝트 찾기
player = GameObject.FindGameObjectWithTag("Player").transform;
}
void Update()
{
// 플레이어와의 거리 계산
float distToPlayer = Vector2.Distance(transform.position, player.position);
// 상태 전환 로직
if (distToPlayer <= attackRange)
{
currentState = EnemyState.Attack;
}
else if (distToPlayer <= detectionRange)
{
currentState = EnemyState.Chase;
}
else
{
currentState = EnemyState.Patrol;
}
// 상태별 행동 실행
switch (currentState)
{
case EnemyState.Patrol:
Patrol();
break;
case EnemyState.Chase:
ChasePlayer();
break;
case EnemyState.Attack:
AttackPlayer();
break;
}
}
void Patrol()
{
if (patrolPoints.Length == 0) return;
Transform target = patrolPoints[currentPatrolIndex];
Vector2 direction = (target.position - transform.position).normalized;
rb.linearVelocity = new Vector2(direction.x * patrolSpeed, rb.linearVelocity.y);
// 순찰 지점 도달 시 다음 지점으로
if (Vector2.Distance(transform.position, target.position) < 0.3f)
{
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
}
}
void ChasePlayer()
{
Vector2 direction = (player.position - transform.position).normalized;
rb.linearVelocity = new Vector2(direction.x * chaseSpeed, rb.linearVelocity.y);
}
void AttackPlayer()
{
// 공격 중엔 이동 멈춤
rb.linearVelocity = new Vector2(0, rb.linearVelocity.y);
// 공격 로직 (애니메이션 트리거, 데미지 처리 등)
Debug.Log("적 공격!");
}
}
2D 슈팅 게임에서 총알 시스템은 오브젝트 풀링(Object Pooling)을 쓰는 게 성능상 훨씬 유리해.
매번 Instantiate/Destroy 하면 GC(가비지 컬렉터)가 난리 나거든 ㅋㅋㅋ
일단 기본 발사 코드부터 보자!
using UnityEngine;
public class PlayerShooter : MonoBehaviour
{
public GameObject bulletPrefab; // 총알 프리팹
public Transform firePoint; // 발사 위치
public float fireRate = 0.2f; // 발사 간격 (초)
private float nextFireTime = 0f;
void Update()
{
// 마우스 왼쪽 버튼 또는 Z키로 발사
if ((Input.GetButton("Fire1") || Input.GetKey(KeyCode.Z))
&& Time.time >= nextFireTime)
{
Shoot();
nextFireTime = Time.time + fireRate;
}
}
void Shoot()
{
// 총알 생성 (발사 위치와 방향)
GameObject bullet = Instantiate(
bulletPrefab,
firePoint.position,
firePoint.rotation
);
}
}
using UnityEngine;
public class Bullet : MonoBehaviour
{
public float speed = 15f;
public int damage = 10;
public float lifetime = 3f; // 3초 후 자동 삭제
private Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
// 총알 방향으로 속도 적용
rb.linearVelocity = transform.right * speed;
// 일정 시간 후 자동 삭제 (화면 밖으로 나간 경우 대비)
Destroy(gameObject, lifetime);
}
void OnTriggerEnter2D(Collider2D other)
{
// 적에게 맞았을 때
if (other.CompareTag("Enemy"))
{
EnemyHealth enemyHealth = other.GetComponent<EnemyHealth>();
if (enemyHealth != null)
{
enemyHealth.TakeDamage(damage);
}
// 총알 삭제
Destroy(gameObject);
}
// 벽에 맞았을 때
if (other.CompareTag("Wall"))
{
Destroy(gameObject);
}
}
}
GameManager는 게임 전체의 상태(점수, 레벨, 게임오버 등)를 관리하는 중앙 컨트롤러야.
싱글톤 패턴을 써서 어디서든 접근 가능하게 만드는 게 표준이거든 ㅋㅋ
재능넷에서 Unity 관련 재능을 거래하는 분들도 이 패턴은 기본 중의 기본으로 알고 있더라고!
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
// 싱글톤 인스턴스
public static GameManager Instance { get; private set; }
[Header("게임 상태")]
public int currentScore = 0;
public int highScore = 0;
public int currentLevel = 1;
public bool isGameOver = false;
public bool isGamePaused = false;
void Awake()
{
// 싱글톤 패턴: 인스턴스가 이미 있으면 이 오브젝트 삭제
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
// 씬 전환 시에도 파괴되지 않도록 설정
DontDestroyOnLoad(gameObject);
// 저장된 최고 점수 불러오기
highScore = PlayerPrefs.GetInt("HighScore", 0);
}
// 점수 추가
public void AddScore(int amount)
{
if (isGameOver) return;
currentScore += amount;
// 최고 점수 갱신
if (currentScore > highScore)
{
highScore = currentScore;
PlayerPrefs.SetInt("HighScore", highScore);
}
// UI 업데이트 이벤트 (UIManager에서 구독)
UIManager.Instance?.UpdateScoreUI(currentScore);
}
// 게임 오버 처리
public void GameOver()
{
isGameOver = true;
Time.timeScale = 0f; // 게임 일시 정지
UIManager.Instance?.ShowGameOverPanel();
}
// 게임 재시작
public void RestartGame()
{
isGameOver = false;
currentScore = 0;
Time.timeScale = 1f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
// 일시정지 토글
public void TogglePause()
{
isGamePaused = !isGamePaused;
Time.timeScale = isGamePaused ? 0f : 1f;
UIManager.Instance?.ShowPausePanel(isGamePaused);
}
// 다음 레벨 로드
public void LoadNextLevel()
{
currentLevel++;
SceneManager.LoadScene(currentLevel);
}
}
근데 씬을 다시 로드할 때 중복 생성되는 문제가 있어서
Awake()에서 중복 체크가 필수야!
게임 데이터 저장은 크게 두 가지 방법이 있어.
간단한 값은 PlayerPrefs, 복잡한 데이터는 JSON 직렬화를 써!
// 데이터 저장
PlayerPrefs.SetInt("Score", 1000);
PlayerPrefs.SetFloat("Volume", 0.8f);
PlayerPrefs.SetString("PlayerName", "홍길동");
PlayerPrefs.Save(); // 명시적 저장 (선택사항, 앱 종료 시 자동 저장됨)
// 데이터 불러오기 (두 번째 인자: 기본값)
int score = PlayerPrefs.GetInt("Score", 0);
float volume = PlayerPrefs.GetFloat("Volume", 1.0f);
string name = PlayerPrefs.GetString("PlayerName", "플레이어");
// 데이터 존재 여부 확인
if (PlayerPrefs.HasKey("Score"))
{
Debug.Log("저장된 점수 있음!");
}
// 특정 키 삭제
PlayerPrefs.DeleteKey("Score");
// 전체 삭제
PlayerPrefs.DeleteAll();
using UnityEngine;
using System.IO;
// 저장할 데이터 구조체
[System.Serializable]
public class GameSaveData
{
public int level;
public int score;
public float playerX;
public float playerY;
public int[] collectedItems;
public string saveTime;
}
public class SaveSystem : MonoBehaviour
{
private string savePath;
void Awake()
{
// 저장 경로 설정 (플랫폼별 적절한 경로)
savePath = Application.persistentDataPath + "/savedata.json";
}
// 게임 저장
public void SaveGame(GameSaveData data)
{
data.saveTime = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 데이터를 JSON 문자열로 변환
string json = JsonUtility.ToJson(data, true); // true = 보기 좋게 포맷
// 파일에 쓰기
File.WriteAllText(savePath, json);
Debug.Log("게임 저장 완료: " + savePath);
}
// 게임 불러오기
public GameSaveData LoadGame()
{
if (!File.Exists(savePath))
{
Debug.Log("저장 파일 없음, 새 게임 시작");
return null;
}
string json = File.ReadAllText(savePath);
GameSaveData data = JsonUtility.FromJson<GameSaveData>(json);
return data;
}
}
게임에서 사운드는 몰입감의 핵심이잖아?
AudioManager도 싱글톤으로 만들어서 어디서든 호출 가능하게 하는 게 국룰이야 ㅋㅋ
using UnityEngine;
using System.Collections.Generic;
public class AudioManager : MonoBehaviour
{
public static AudioManager Instance { get; private set; }
[Header("오디오 소스")]
public AudioSource bgmSource; // BGM 전용
public AudioSource sfxSource; // 효과음 전용
[Header("사운드 클립")]
public AudioClip[] bgmClips;
public AudioClip[] sfxClips;
// 효과음 이름으로 빠르게 찾기 위한 딕셔너리
private Dictionary<string, AudioClip> sfxDictionary;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
// 딕셔너리 초기화
sfxDictionary = new Dictionary<string, AudioClip>();
foreach (AudioClip clip in sfxClips)
{
if (clip != null)
sfxDictionary[clip.name] = clip;
}
}
// BGM 재생
public void PlayBGM(int index)
{
if (index >= bgmClips.Length) return;
bgmSource.clip = bgmClips[index];
bgmSource.loop = true;
bgmSource.Play();
}
// 효과음 재생 (이름으로 호출)
public void PlaySFX(string clipName)
{
if (sfxDictionary.TryGetValue(clipName, out AudioClip clip))
{
sfxSource.PlayOneShot(clip);
}
else
{
Debug.LogWarning("효과음을 찾을 수 없음: " + clipName);
}
}
// 볼륨 조절
public void SetBGMVolume(float volume)
{
bgmSource.volume = Mathf.Clamp01(volume);
PlayerPrefs.SetFloat("BGMVolume", volume);
}
public void SetSFXVolume(float volume)
{
sfxSource.volume = Mathf.Clamp01(volume);
PlayerPrefs.SetFloat("SFXVolume", volume);
}
}
Unity UI(uGUI)와 코드를 연결하는 건 진짜 자주 쓰이는 패턴이야.
TextMeshPro랑 Slider를 활용한 체력바 예제를 보자!
using UnityEngine;
using UnityEngine.UI;
using TMPro; // TextMeshPro 네임스페이스
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
[Header("체력 UI")]
public Slider healthBar;
public TextMeshProUGUI healthText;
[Header("점수 UI")]
public TextMeshProUGUI scoreText;
public TextMeshProUGUI highScoreText;
[Header("패널")]
public GameObject gameOverPanel;
public GameObject pausePanel;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
// 체력 UI 업데이트
public void UpdateHealthUI(int current, int max)
{
// 슬라이더 값 업데이트 (0~1 사이 값)
healthBar.value = (float)current / max;
// 텍스트 업데이트
healthText.text = $"{current} / {max}";
// 체력에 따라 색상 변경
if (current / (float)max > 0.5f)
healthBar.fillRect.GetComponent<Image>().color = Color.green;
else if (current / (float)max > 0.25f)
healthBar.fillRect.GetComponent<Image>().color = Color.yellow;
else
healthBar.fillRect.GetComponent<Image>().color = Color.red;
}
// 점수 UI 업데이트
public void UpdateScoreUI(int score)
{
scoreText.text = $"점수: {score:N0}"; // 천 단위 구분자 포함
}
// 게임오버 패널 표시
public void ShowGameOverPanel()
{
gameOverPanel.SetActive(true);
highScoreText.text = $"최고 점수: {GameManager.Instance.highScore:N0}";
}
// 일시정지 패널 토글
public void ShowPausePanel(bool show)
{
pausePanel.SetActive(show);
}
}
Unity에서 시간 지연, 반복 처리, 페이드 효과 등을 구현할 때 코루틴(Coroutine)은 진짜 필수야.
async/await보다 Unity 환경에서 더 자연스럽게 동작하거든 ㅋㅋ
using UnityEngine;
using System.Collections;
public class CoroutineExamples : MonoBehaviour
{
// 예시 1: 일정 시간 후 실행
IEnumerator DelayedAction(float delay)
{
yield return new WaitForSeconds(delay);
Debug.Log(delay + "초 후 실행!");
}
// 예시 2: 화면 페이드 인/아웃
public CanvasGroup canvasGroup;
IEnumerator FadeIn(float duration)
{
canvasGroup.alpha = 0f;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
canvasGroup.alpha = Mathf.Clamp01(elapsed / duration);
yield return null; // 다음 프레임까지 대기
}
canvasGroup.alpha = 1f;
}
IEnumerator FadeOut(float duration)
{
canvasGroup.alpha = 1f;
float elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
canvasGroup.alpha = 1f - Mathf.Clamp01(elapsed / duration);
yield return null;
}
canvasGroup.alpha = 0f;
}
// 예시 3: 깜빡임 효과 (무적 시간 표현)
IEnumerator BlinkEffect(SpriteRenderer spriteRenderer, float duration)
{
float elapsed = 0f;
while (elapsed < duration)
{
spriteRenderer.enabled = !spriteRenderer.enabled;
yield return new WaitForSeconds(0.1f);
elapsed += 0.1f;
}
spriteRenderer.enabled = true; // 마지막엔 반드시 보이게
}
// 예시 4: 카메라 흔들기 효과
IEnumerator CameraShake(float duration, float magnitude)
{
Vector3 originalPos = Camera.main.transform.position;
float elapsed = 0f;
while (elapsed < duration)
{
float x = Random.Range(-1f, 1f) * magnitude;
float y = Random.Range(-1f, 1f) * magnitude;
Camera.main.transform.position =
new Vector3(originalPos.x + x, originalPos.y + y, originalPos.z);
elapsed += Time.deltaTime;
yield return null;
}
Camera.main.transform.position = originalPos;
}
void Start()
{
// 코루틴 시작 방법
StartCoroutine(DelayedAction(2f));
StartCoroutine(FadeIn(1.5f));
StartCoroutine(CameraShake(0.5f, 0.1f));
}
}
2D 게임에서 카메라가 플레이어를 부드럽게 따라다니는 건 기본 중의 기본이지!
Cinemachine을 쓰면 더 쉽지만, 직접 구현하는 방법도 알아두면 좋아 ㅋㅋ
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target; // 따라갈 대상 (플레이어)
public float smoothSpeed = 5f; // 부드러움 정도
public Vector3 offset = new Vector3(0, 2f, -10f); // 카메라 오프셋
[Header("카메라 경계 설정")]
public bool useBounds = true;
public float minX, maxX, minY, maxY;
void LateUpdate() // Update 대신 LateUpdate 사용 (플레이어 이동 후 처리)
{
if (target == null) return;
// 목표 위치 계산
Vector3 desiredPosition = target.position + offset;
// 경계 제한 적용
if (useBounds)
{
desiredPosition.x = Mathf.Clamp(desiredPosition.x, minX, maxX);
desiredPosition.y = Mathf.Clamp(desiredPosition.y, minY, maxY);
}
// 부드러운 이동 (Lerp 사용)
Vector3 smoothedPosition = Vector3.Lerp(
transform.position,
desiredPosition,
smoothSpeed * Time.deltaTime
);
transform.position = smoothedPosition;
}
}
Update()에서 하면 플레이어 이동 전에 카메라가 먼저 움직여서 떨림 현상이 생길 수 있거든 ㅋㅋ
| 기능 | 주요 컴포넌트/클래스 | 핵심 메서드 |
|---|---|---|
| 물리 이동 | Rigidbody2D | linearVelocity, AddForce() |
| 충돌 감지 | Collider2D | OnCollisionEnter2D, OnTriggerEnter2D |
| 애니메이션 | Animator | SetFloat, SetBool, SetTrigger |
| 오브젝트 생성 | MonoBehaviour | Instantiate(), Destroy() |
| 씬 관리 | SceneManager | LoadScene(), GetActiveScene() |
| 데이터 저장 | PlayerPrefs | SetInt, GetInt, Save() |
| 시간 처리 | Time | deltaTime, timeScale, time |
| 사운드 | AudioSource | Play(), PlayOneShot(), Stop() |
| UI 텍스트 | TextMeshProUGUI | .text 프로퍼티 |
| 코루틴 | MonoBehaviour | StartCoroutine(), StopCoroutine() |
컴포넌트 참조 전
null 체크 필수!
Update에서
GetComponent 금지!
코루틴에
yield 반드시 포함!
카메라는
LateUpdate 사용!
GetComponent<T>()는 Start() 또는 Awake()에서 한 번만 호출하고 변수에 캐싱해서 써야 해.Update()에서 매 프레임 호출하면 성능이 급격히 떨어지거든 ㅋㅋ 이거 진짜 중요함!
여기까지 Unity 2D 게임 개발의 핵심 소스코드들을 쭉 살펴봤어!
플레이어 이동부터 시작해서 애니메이션, 충돌 감지, 체력 시스템, 적 AI, 발사체, GameManager, 데이터 저장, 사운드, UI, 코루틴, 카메라까지 — 이 정도면 간단한 2D 게임 하나는 충분히 만들 수 있는 기반이 갖춰진 거야 😊
이 코드들을 기반으로 플랫포머 게임, 탑다운 슈팅 게임, 퍼즐 게임 등 다양한 장르에 응용해봐!
처음엔 복붙해서 쓰더라도 점점 코드를 이해하고 수정하다 보면 실력이 쑥쑥 늘거든 ㅋㅋㅋ
Unity 개발 관련해서 더 배우고 싶거나, 직접 만든 게임 소스를 공유하고 싶다면
재능넷(https://www.jaenung.net)에서 Unity 개발 관련 재능을 찾아보는 것도 좋은 방법이야.
다양한 개발자들이 자신의 노하우를 공유하고 있거든 😎
관련 키워드
댓글 0
지식인의 숲 - 지적 재산권 보호 고지
지적 재산권 보호 고지
- 저작권 및 소유권: 본 컨텐츠는 재능넷의 독점 AI 기술로 생성되었으며, 대한민국 저작권법 및 국제 저작권 협약에 의해 보호됩니다.
- AI 생성 컨텐츠의 법적 지위: 본 AI 생성 컨텐츠는 재능넷의 지적 창작물로 인정되며, 관련 법규에 따라 저작권 보호를 받습니다.
- 사용 제한: 재능넷의 명시적 서면 동의 없이 본 컨텐츠를 복제, 수정, 배포, 또는 상업적으로 활용하는 행위는 엄격히 금지됩니다.
- 데이터 수집 금지: 본 컨텐츠에 대한 무단 스크래핑, 크롤링, 및 자동화된 데이터 수집은 법적 제재의 대상이 됩니다.
- AI 학습 제한: 재능넷의 AI 생성 컨텐츠를 타 AI 모델 학습에 무단 사용하는 행위는 금지되며, 이는 지적 재산권 침해로 간주됩니다.

댓글 작성
이 글에 대한 여러분의 생각을 들려주세요
로그인이 필요합니다
댓글을 작성하려면 먼저 로그인해주세요.