반응형
[23일차 수업내용] 250617
1.캡슐화
2.인터페이스 - 아이템 줍기 / 사용 / 버리기
3.상속, 인터페이스 - 몬스터 상속과 아이템 줍기 실습
1. 캡슐화
데이터(변수)와 기능(메서드)을 하나로 묶고, 외부에서 직접 접근하지 못하게 숨기는 것
1.1 Getter Setter -> 과거에 쓰던 방식
- Getter: 값을 읽는 메서드
- Setter: 값을 설정하는 메서드
private int hp; // 숨긴 변수 (필드)
public int Hp // 외부에 노출하는 '프로퍼티'
{
get { return hp; } // 읽기
set { hp = value; } // 쓰기 (value는 입력값)
}
class Player
{
private int hp;
public int GetHp() { return hp; }
public void SetHp(int value) { hp = value; }
}
1.2. Property 프로퍼티 = 캡슐화한 데이터
- get : 값을 읽을 때 사용
- set : 값을 설정할 때 사용
- Get / Set -> 누가 어디서 썼는지 알 수 있다는 장점
- 보안성 높이고 선택지를 늘린다.
[예시]
private int hp; // 필드 (데이터 저장)
public int Hp // 프로퍼티 (접근 통로)
{
get { return hp; } // 읽기
set { if (value >= 0) hp = value; } // 쓰기 (0 이상일 때만 허용)
}
- 수업 코드 예시
public class StudyProperty : MonoBehaviour
{
private int number1 = 10; // 내부에서 관리하는 변수 (외부 접근 불가)
// 프로퍼티 → 외부에서는 읽기 가능, 쓰기는 내부(private)에서만 가능
public int Number1
{
get { return number1; } // 읽기
private set { number1 = value; } // 쓰기(private)
}
// 읽기 전용 프로퍼티 (초기값 20, 변경 불가)
public int Number2 { get; } = 20;
// 외부에서 읽기 가능, 쓰기는 내부(private)에서만 가능 (초기값 30)
public int Number3 { get; private set; } = 30;
private float hp = 100f; // 내부 값
// 프로퍼티 → 외부에서 읽기 가능, set은 존재하나 현재 내용 없음
public float Hp
{
get { return hp; }
set
{
// 일부러 비워둠 → 외부에서 값을 바꿔도 실제 hp 값 변화 없음
}
}
private SoundManager sound; // 사운드 매니저 객체 저장용 (초기엔 null)
// Sound 프로퍼티 → 필요할 때 찾아서 캐싱(Lazy Loading) = 최적화
public SoundManager Sound
{
get
{
// 아직 할당되지 않았다면 → 씬 내에서 첫 번째 SoundManager 찾기
if (sound == null)
sound = FindFirstObjectByType<SoundManager>();
return sound; // 반환 (이미 찾았으면 저장된 값 반환)
}
}
}
1.2.1 private set
public int Hp { get; private set; }
외부에서는 읽을 수만 있고, 바꿀 수 없음. (읽기 전용)
내부(클래스 안)에서는 값 변경 가능
1.3 접근제한자 정리
| public | 어디서든 접근 가능 |
| private | 클래스 내부에서만 접근 가능 |
| protected | 클래스 내부 + 자식 클래스 접근 가능 |
| [SerializeField] | private 변수 Unity 인스펙터에 노출 |
| [field: SerializeField] | 프로퍼티 인스펙터에 노출할 때 사용 |
| property | get/set 형태의 깔끔한 접근 방식 제공 |
public int hp; // 어디서나 접근 가능
private int mana; // 클래스 안에서만 접근 가능
protected int gold; // 상속받은 클래스에서 접근 가능
[SerializeField]
- Unity 전용
- private인데 인스펙터 창에 보이게 하고 싶을 때 사용 → 주로 프로퍼티랑 같이 씀
[field: SerializeField]
public int Hp { get; private set; }
2. 인터페이스 - 아이템 줍기 / 사용 / 버리기
인터페이스 (interface)
- “이걸 구현하는 클래스는 반드시 이 기능을 구현해야 한다” 라고 강제함
- 클래스는 여러 개의 인터페이스를 “상속”(구현)할 수 있음 (다중 상속 가능)
- 보통 ‘I’로 시작 (ex. IAttack, IMovable)
[선언]
interface IAttack
{
void Attack(); // 선언만 있음, 구현 ❌
}
[구현]
interface IAttack
{
void Attack();
}
class Orc : IAttack
{
public void Attack() // 반드시 구현해야 함!
{
Console.WriteLine("오크가 공격한다!");
}
}
[실습내용]
Character 생성
+ Gun Flashlight -> IDropItem 안에 Grab(), Use(), Drop() 공통 함수를 가지게 됨
public interface IDropItem
{
void Grab(); // 줍기
void Use(); // 사용
void Drop(); // 버리기
}
[실습 코드]
public class CharacterControl : MonoBehaviour
{
[SerializeField] private float moveSpeed = 3f; // 이동 속도 (유니티 에디터에서 조절 가능)
private IDropItem currentItem; // 현재 들고 있는 아이템 (인터페이스 타입)
void Update()
{
Move();
Interaction(); // 매 프레임마다 입력 처리 호출
}
private void Move()
{
// 키보드 입력 받기 (좌우, 앞뒤)
float h = Input.GetAxis("Horizontal"); // 좌우 방향키 (A,D 또는 ←,→)
float v = Input.GetAxis("Vertical"); // 앞뒤 방향키 (W,S 또는 ↑,↓)
// 방향 벡터 생성 (Y축은 0으로 고정)
Vector3 dir = new Vector3(h, 0, v).normalized;
// 이동량 계산 후 위치에 더하기 (프레임 독립적 움직임)
transform.position += dir * moveSpeed * Time.deltaTime;
}
private void Interaction()
{
// 마우스 왼쪽 클릭 시
if (Input.GetMouseButtonDown(0))
{
if (currentItem != null) // 아이템이 있을 때만
currentItem.Use(); // 아이템 사용
}
// 스페이스바 눌렀을 때
if (Input.GetKeyDown(KeyCode.Space))
{
if (currentItem != null) // 아이템이 있을 때만 버리기 가능
{
currentItem.Drop();
currentItem = null; // 버린 뒤에는 아이템 없음 처리
}
}
}
// 트리거 충돌 시 호출되는 함수
private void OnTriggerEnter(Collider other)
{
var item = other.GetComponent<IDropItem>(); // 충돌한 오브젝트가 IDropItem인지 확인
if (item != null) // 맞으면
{
currentItem = item;
currentItem.Grab();
}
}
}
*리마인드
- var =C#에서 지역 변수(local variable) 선언 시 타입을 명시하지 않고 컴파일러가 자동으로 변수 타입을 추론하
- OnTriggerEnter() 작동 조건 = Collider + 충돌하는 두 오브젝트 중 하나는 반드시 Rigidbody 포함해야 이벤트 발생함
- 충돌 대상(GameObject)에 → Collider + Rigidbody + IDropItem 스크립트 필수
3. 몬스터 상속
몬스터 클래스 상속과 abstract / override를 활용한 필수 구현
public abstract class Monster : MonoBehaviour
{
private SpriteRenderer sRenderer; // 몬스터의 스프라이트 렌더러 (이미지 뒤집기용)
[SerializeField] protected float hp = 3f; // 체력 (자식 클래스에서 접근 가능 + 인스펙터에 표시됨)
[SerializeField] protected float moveSpeed = 3f; // 이동 속도 (자식 클래스에서 접근 가능 + 인스펙터에 표시됨)
private int dir = 1; //이동 방향 (1: 오른쪽, -1: 왼쪽)
// 추상 메서드: 반드시 하위 클래스가 구현해야 함 (오버라이딩)
public abstract void Init();
void Start()
{
sRenderer = GetComponent<SpriteRenderer>(); //스프라이트 렌더러 컴포넌트 가져오기
Init(); // 하위 클래스에서 정의
}
void OnMouseDown()
{
Hit(1); // 마우스로 클릭하면 1 데미지 주기
}
void Update()
{
Move(); // 매 프레임마다 이동 실행
}
void Move()
{
transform.position += Vector3.right * dir * moveSpeed * Time.deltaTime;
// 화면 오른쪽 끝에 도달하면 방향 전환 + 이미지 뒤집기
if (transform.position.x > 8f)
{
dir = -1;
sRenderer.flipX = true; // 왼쪽 방향으로 이미지 반전
}
// 화면 왼쪽 끝에 도달하면 방향 전환 + 이미지 원상복귀
else if (transform.position.x < -8f)
{
dir = 1;
sRenderer.flipX = false;
}
}
// 몬스터 피격 처리
void Hit(float damage)
{
hp -= damage;
if (hp <= 0)
{
Debug.Log("몬스터 죽음");
Destroy(gameObject); // 몬스터 오브젝트 삭제
}
}
}
* 리마인드
- 여기서 float damage는 매개변수 = 값은 그 함수 내부에서만 유효
- 유연성 ↑ → 같은 함수인데도 상황에 따라 다른 값 사용 가능 / 함수 끝나면 사라짐
[각 몬스터 별로 구현 ex-goblin]
public class Goblin : Monster
{
public override void Init()
{
hp = 3f;
moveSpeed = 3f;
}
}
4. 기타 주의사항
- GetComponent<SpriteRenderer>(); -→ 컴포넌트가 반드시 붙어 있어야 함 (없으면 null, 실행 시 에러)
- OnMouseDown() → Collider 필수 → Collider가 없으면 클릭 이벤트 안 발생함
- Orthographic 카메라란? 원근(Perspective) 효과가 없는 카메라 = 2D게임
게임을 보다 체계적이고 효율적으로 구현하는 방법들을 배워나가는 것 같아서 재미있었다. 갈수록 속도가 더 빨라지는 것 같아 열심히 복습해야겠다.
반응형
'프로그래밍 > 유니티 부트캠프' 카테고리의 다른 글
| 유니티(몬스터 공격 실습/아이템획득/게임수학/터렛회전) _ 멋쟁이사자처럼 유니티 부트캠프 후기 25회차 (2) | 2025.06.23 |
|---|---|
| 유니티(상속/인터페이스 연습/layer) _ 멋쟁이사자처럼 유니티 부트캠프 후기 24회차 (0) | 2025.06.18 |
| 유니티(오버로딩,상속,가상화/추상화,인터페이스 실습) _ 멋쟁이사자처럼 유니티 부트캠프 후기 22회차 (0) | 2025.06.17 |
| 유니티(형변환,객체지향프로그래밍) _ 멋쟁이사자처럼 유니티 부트캠프 후기 21회차 (0) | 2025.06.13 |
| 유니티(게임 다시하기, 빌드하기, RenderTexture 이용해 CCTV, 미니맵 만들기, Lotto 구현) _ 멋쟁이사자처럼 유니티 부트캠프 후기 20회차 (0) | 2025.06.13 |