적이 충돌한 게임 오브젝트가 RubyController 스크립트를 가지고 있는지 확인하면 됩니다. 만약 가지고 있다면 메인 캐릭터이므로 데미지를 가할 수 있습니다.
void OnCollisionEnter2D(Collision2D other)
{
RubyController player = other.gameObject.GetComponent<RubyController>();
if (player != null)
{
player.ChangeHealth(-1);
}
}
아, 그니깐, 만약에, 충돌한 적이, 루비가 아니면
루비 스크립트를 가지고 있지않을거고,
결국 null이되니깐, 조건문 통과못하는거네
이렇게되면, 레벨의 갯수만큼, Boolean형 자료형이 늘어나는데,
아니면, 리스트를 만들어서, 젤 처음에는, 모두 false만 담는 방법도 있을것같고
아니면, c#도 switch문이 있나
아니면, 레벨이 늘어나는 정도가 규칙적이라면, 10초 지날때마다, 호출하게 해도될것같기도
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class GameManager : MonoBehaviour
{
private int level;
float timer;
public TextMeshProUGUI levelText;
public int enterLevel2;
public int enterLevel3;
Boolean ifEnteredLv2 = false;
Boolean ifEnteredLv3 = false;
// Start is called before the first frame update
public int getLevel()
{
return level;
}
void Start()
{
timer = 0;
levelText.text = "Lv1";
}
// Update is called once per frame
void Update()
{
timer += Time.deltaTime;
if (timer >= enterLevel2 && !ifEnteredLv2)
{
level += 1;
//Debug.Log("lv2");
levelText.text = "Lv"+level;
}
else if (timer >= enterLevel3 &&ifEnteredLv3)
{
level += 1;
levelText.text = "Lv"+level;
//Debug.Log("lv3");
}
}
}
1. switch 문 사용 (특정 레벨 진입 시점 고정)
C#에도 switch 문이 있습니다. 각 레벨 진입 시점이 고정되어 있다면 switch 문으로 관리할 수 있습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class GameManager : MonoBehaviour
{
private int level = 1; // 시작 레벨은 1로 초기화
private float timer;
public TextMeshProUGUI levelText;
// 각 레벨로 진입하는 시간을 배열로 관리
public float[] levelEnterTimes;
// 현재 레벨 진입 여부를 확인하기 위한 인덱스
private int currentLevelIndex = 0;
public int getLevel()
{
return level;
}
void Start()
{
timer = 0;
levelText.text = "Lv" + level;
}
void Update()
{
timer += Time.deltaTime;
// levelEnterTimes 배열의 범위를 벗어나지 않도록 확인
if (currentLevelIndex < levelEnterTimes.Length)
{
if (timer >= levelEnterTimes[currentLevelIndex])
{
level++;
levelText.text = "Lv" + level;
currentLevelIndex++; // 다음 레벨 진입 조건을 확인하도록 인덱스 증가
Debug.Log("Lv" + level + " Entered!");
}
}
}
}
Inspector 설정 예시:
levelEnterTimes 배열의 크기를 2로 설정하고,
- Element 0: 10 (10초 후 Lv2 진입)
- Element 1: 20 (20초 후 Lv3 진입)
이렇게 설정하면 됩니다.
2. 규칙적인 레벨 증가 (예: 일정 시간마다)
만약 레벨이 10초, 20초, 30초... 와 같이 규칙적으로 증가한다면, levelEnterTimes 배열을 직접 설정하는 대신, levelIncreaseInterval과 같은 변수를 사용할 수 있습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class GameManager : MonoBehaviour
{
private int level = 1; // 시작 레벨은 1로 초기화
private float timer;
public TextMeshProUGUI levelText;
public float levelIncreaseInterval = 10f; // 10초마다 레벨 증가
private float nextLevelUpTime;
public int getLevel()
{
return level;
}
void Start()
{
timer = 0;
levelText.text = "Lv" + level;
nextLevelUpTime = levelIncreaseInterval; // 첫 번째 레벨업 시간 설정
}
void Update()
{
timer += Time.deltaTime;
if (timer >= nextLevelUpTime)
{
level++;
levelText.text = "Lv" + level;
nextLevelUpTime += levelIncreaseInterval; // 다음 레벨업 시간 갱신
Debug.Log("Lv" + level + " Entered!");
}
}
}
1. transform.position = position; (직접적인 위치 변경)
- 물리 엔진 관여 없음: 이 방식은 GameObject의 Transform 컴포넌트에 직접 접근하여 위치를 변경합니다. Rigidbody2D가 GameObject에 있더라도, 이 명령은 물리 엔진의 계산을 무시하고 오브젝트의 위치를 강제로 설정합니다.
- 충돌 처리: 만약 이 방식으로 오브젝트를 움직이다가 다른 콜라이더와 겹치게 되면, 물리 엔진은 겹쳐진 상태를 감지하지 못하거나, 다음 물리 업데이트에서 갑작스럽게 충돌을 해결하려 할 수 있습니다 (텔레포트된 것으로 간주). 이로 인해 예상치 못한 물리 반응(버그, 진동)이 발생할 수 있습니다.
- 사용 시점:
- 물리적인 상호작용이 전혀 필요 없는 오브젝트 (예: UI 요소, 배경 오브젝트, 순수하게 그래픽적인 이동).
- 오브젝트를 순간이동시키거나, 물리 법칙을 완전히 무시한 움직임이 필요할 때.
- Rigidbody2D가 없는 오브젝트를 움직일 때.
- 예시: 질문의 moveUpAndDown 함수는 transform.position = position;을 사용하여 오브젝트를 물리 엔진과 무관하게 상하로 움직입니다. 이 오브젝트가 다른 물리 오브젝트와 충돌해야 한다면 문제가 발생할 수 있습니다.
2. rigidbody2D.MovePosition(position); (물리 엔진을 통한 위치 변경)
- 물리 엔진 관여: 이 방식은 Rigidbody2D 컴포넌트를 통해 오브젝트의 위치를 변경합니다. 즉, 물리 엔진에게 "이 오브젝트를 이 위치로 움직여달라"고 요청하는 것입니다. 물리 엔진은 이 요청을 기반으로 충돌 감지 및 해결을 고려하면서 오브젝트를 움직입니다.
- 충돌 처리: MovePosition은 이동 중에 발생할 수 있는 충돌을 미리 감지하고, 해당 충돌에 맞춰 적절하게 오브젝트의 위치를 조정합니다. 이는 FixedUpdate 함수 내에서 호출하는 것이 일반적이며, 물리 계산 주기와 동기화됩니다.
- 사용 시점:
- Rigidbody2D를 가지고 있고, 다른 물리 오브젝트와 정확하고 부드러운 물리 상호작용이 필요한 오브젝트 (예: 플레이어 캐릭터, 움직이는 플랫폼, 물리 퍼즐 오브젝트).
- 일관된 물리 시뮬레이션이 중요한 경우.
- 예시: 플레이어 캐릭터가 땅을 밟고 움직이거나, 움직이는 플랫폼이 플레이어를 밀어내야 할 때 MovePosition을 사용하면 물리적으로 자연스러운 움직임과 상호작용을 얻을 수 있습니다.
네, 맞습니다! public bool vertical; 와 Boolean goingUp = true; 둘 다 bool (Boolean) 타입의 변수를 선언하는 유효한 방법입니다.
하지만 C#에서는 일반적으로 bool 키워드를 사용하는 것이 관례입니다.
bool vs Boolean
- bool (소문자):
- C# 언어에서 제공하는 키워드입니다.
- System.Boolean 타입의 별칭(alias) 입니다.
- 대부분의 C# 코드에서 부울 값을 선언할 때 사용되는 표준이자 권장 방식입니다.
- 예시: public bool isMoving;, private bool hasCollided = false;
- Boolean (대문자):
- .NET Framework의 System 네임스페이스에 정의된 클래스/구조체(struct) 이름입니다.
- bool 키워드는 컴파일러가 내부적으로 System.Boolean으로 처리합니다.
- 기술적으로 사용할 수는 있지만, C#에서는 기본 타입의 키워드(예: int 대신 Int32, string 대신 String)를 사용하는 것이 더 흔하고 선호됩니다.
왜 둘 다 되는가?
C# 컴파일러가 bool 키워드를 만나면, 내부적으로 System.Boolean 타입으로 변환하여 처리하기 때문입니다. 마치 int 키워드가 System.Int32의 별칭인 것과 같습니다.
int myInteger = 10; // C# 키워드
System.Int32 myOtherInteger = 20; // .NET Framework 타입
// 둘 다 동일한 Int32 타입의 정수형 변수를 선언하는 것입니다.
결론 및 권장 사항
- 둘 다 문법적으로는 유효하며 동일하게 작동합니다.
- 하지만 C# 코드를 작성할 때는 bool (소문자) 키워드를 사용하는 것이 좋습니다. 이는 C#의 관례이며, 코드를 더 읽기 쉽고 표준적으로 만듭니다.
따라서, 질문하신 코드에서는 Boolean goingUp = true; 대신 bool goingUp = true;로 작성하는 것이 더 일반적이고 권장되는 방식입니다.
public class MyScript : MonoBehaviour
{
public bool vertical; // 권장
// public Boolean vertical2; // 작동하지만 덜 일반적
bool goingUp = true; // 권장
// Boolean goingUp2 = true; // 작동하지만 덜 일반적
void Start()
{
if (vertical)
{
Debug.Log("Vertical is true.");
}
if (goingUp)
{
Debug.Log("Going up is true.");
}
}
}
- Collision2D other (사고 현장 보고서):
- OnCollisionEnter2D(Collision2D other)에서 other는 사고 현장에서 경찰이 작성한 '사고 현장 보고서' 같은 겁니다.
- 이 보고서에는 사고에 대한 모든 정보가 담겨 있어요: "어떤 차가 부딪혔는지", "어디가 긁혔는지", "충돌 시 속도는 어땠는지" 등등.
- 보고서 자체는 종이 뭉치일 뿐, 차가 아닙니다.
- 당신이 이 보고서를 보면서 "이 보고서에 바퀴가 달려있나?"라고 묻는다면 이상하겠죠? 보고서에는 바퀴가 없으니까요.
- 마찬가지로, Collision2D other (보고서) 자체에는 GetComponent라는 기능이 없습니다. GetComponent는 "이 게임 오브젝트에 이런 부품(컴포넌트)이 있니?"라고 묻는 기능인데, 보고서는 게임 오브젝트가 아니기 때문이죠.
- other.gameObject (사고 차량):
- 보고서( Collision2D other ) 안에는 "충돌한 상대방의 차량(GameObject) 정보"가 명확하게 적혀 있습니다. 이게 바로 other.gameObject 입니다.
- 이제 당신은 보고서에 적힌 '사고 차량'을 보고 "이 차에 루비 컨트롤러(RubyController)라는 부품이 달려있니?"라고 물어볼 수 있습니다.
- 이렇게 물어보는 것이 other.gameObject.GetComponent<RubyController>() 입니다. 이것은 자연스럽고 올바른 질문이죠.
그래서 핵심 차이는:
- Collision2D other 일 때:
- other.GetComponent<RubyController>() (안 됨): "사고 현장 보고서에 루비 컨트롤러 부품이 달려있니?" 라고 묻는 것과 같습니다. 보고서에는 그런 부품이 없습니다. 그래서 오류가 납니다.
- other.gameObject.GetComponent<RubyController>() (됨): "사고 현장 보고서에 적힌 차량에 루비 컨트롤러 부품이 달려있니?" 라고 묻는 것과 같습니다. 이것은 정확하고 올바른 질문입니다.
1. OnCollisionEnter2D (쾅! 문에 부딪혔을 때)
- 비유: 당신이 문을 향해 걸어가다가 "쾅!" 하고 문에 직접 몸으로 부딪혔을 때입니다.
- 문이 '충돌'을 감지하고 "누가 나한테 부딪혔네!" 하고 반응합니다.
- 특징:
- 물리적 상호작용: 실제로 닿아서 서로 밀거나 튕겨 나갈 수 있습니다. 문에 부딪히면 당신이 멈추거나 뒤로 밀려날 수 있듯이요.
- 사용 예시:
- 플레이어가 벽에 부딪혀 멈출 때.
- 총알이 적에게 맞아 데미지를 줄 때.
- 두 오브젝트가 서로 힘을 주고받아야 할 때.
- 정보: 누가 부딪혔는지에 대한 **'사고 보고서(Collision2D)'**를 줍니다. 이 보고서 안에는 부딪힌 상대방(other.gameObject), 부딪힌 위치, 충격량 등 아주 자세한 정보가 들어있어요.
2. OnTriggerEnter2D (스윽~ 문을 통과했을 때)
- 비유: 당신이 문으로 걸어가다가 문에 닿기 직전, 문 옆에 설치된 '자동문 센서'를 통과했을 때입니다.
- 문은 당신의 존재를 감지하고 "어떤 사람이 여기를 지나가네!" 하고 반응합니다. 하지만 당신은 문에 부딪히지 않고 그냥 통과할 수 있습니다. 문이 열리든 말든, 당신은 계속 직진합니다.
- 특징:
- 비물리적 상호작용 (감지 목적): 실제로 닿는 것(충돌)은 아니지만, 특정 영역 안으로 들어왔음을 감지할 때 사용합니다. 서로 밀거나 튕겨 나가지 않습니다.
- 사용 예시:
- 플레이어가 동전 아이템을 주울 때 (동전을 통과하면 주워짐).
- 플레이어가 특정 구역에 진입했을 때 (퀘스트 시작, 지역 감지).
- 자동문이 플레이어를 감지하고 열릴 때.
- 정보: 누가 통과했는지에 대한 **'상대방의 외형 정보(Collider2D)'**를 줍니다. 여기에는 상대방의 게임 오브젝트(other.gameObject)에 대한 정보가 들어있습니다.
가장 쉬운 차이점 요약:
- OnCollisionEnter2D:
- "쾅!"하고 부딪혀서 서로 영향을 줄 때 (물리적 충돌).
- "부딪혔다!" 라는 자세한 사고 보고서(Collision2D other)를 받습니다.
- OnTriggerEnter2D:
- "스윽~"하고 통과해서 무언가를 감지할 때 (영역 진입 감지).
- "통과했다!" 라는 상대방의 외형 정보(Collider2D other)를 받습니다.
콜라이더 설정:
이 두 가지를 사용하려면, 오브젝트에 Collider2D 컴포넌트가 있어야 합니다. 그리고 인스펙터에서 Is Trigger 체크박스를 어떻게 설정하느냐에 따라 달라집니다.
- Is Trigger 체크 해제: OnCollisionEnter2D가 작동합니다. (실제 부딪힘)
- Is Trigger 체크: OnTriggerEnter2D가 작동합니다. (영역 통과 감지)
왜 안되지 ㅠ
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
public int speed = 1;
float maxX;
float minX;
float maxY;
float minY;
public int damage = 1;
Boolean goingUp = true;
Rigidbody2D rigidbody2D;
float timer;
public bool vertical;
bool goLeft;
public float changeTime = 3.0f;
// Start is called before the first frame update
void Start()
{
//Camera gameCamera = Camera.main;
//Vector3 rightEdge = gameCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, 10));
//Vector3 topEdge = gameCamera.ScreenToWorldPoint(new Vector3(0, Screen.height, 10));
//maxX = rightEdge.x;
//maxY = topEdge.y;
maxY = 2.65f;
minY = -5.3f;
rigidbody2D = GetComponent<Rigidbody2D>();
vertical = false;
maxX = 2.99f;
minX = -2.91f;
goLeft = false;
}
// Update is called once per frame
void Update()
{
calTimeIfVerticalOrHorizontal();
move();
}
void move()
{
if (vertical)
{
moveUpAndDown();
}
else
{
moveRightAndLeft();
}
}
void moveRightAndLeft()
{
Vector2 position = rigidbody2D.position;
if (goLeft)
{
if (position.x <= minX)
{
goLeft = false;
return;
}
position.x -= speed;
}
else
{
if (position.x >= maxX)
{
goLeft = false;
return;
}
position.x += speed;
}
rigidbody2D.MovePosition(position);
}
void moveUpAndDown()
{
Vector2 position = rigidbody2D.position;
//Debug.Log("moveUpAndDown()호출");
if (goingUp)
{
//Debug.Log("if (goingUp) true임");
//Debug.Log("position.y:" + position.y);
//Debug.Log("maxY:" + maxY);
//Debug.Log(position.y < maxY);
if (position.y < maxY)
{
position.y += speed * Time.deltaTime;
//Debug.Log("올라가는중");
}
else
{
goingUp = false;
return;
}
}
else
{
if (position.y > minY)
{
position.y -= speed * Time.deltaTime;
//Debug.Log("내려가는중 ");
}
else
{
goingUp = true;
return;
}
}
rigidbody2D.MovePosition(position);
}
// void OnTriggerEnter2D(Collider2D other)
// {
// //Debug.Log("Object that entered the trigger : " + other);
// RubyController controller = other.GetComponent<RubyController>();
// if (controller != null)
// {
// controller.ChangeHealth(-1 * damage);
// }
// }
void OnCollisionEnter2D(Collision2D other)
{
RubyController player = other.gameObject.GetComponent<RubyController>();
if (player != null)
{
player.ChangeHealth(-1*damage);
}
}
void calTimeIfVerticalOrHorizontal()
{
timer -= Time.deltaTime;
if (timer < 0)
{
timer = changeTime;
if (vertical == false)
{
vertical = true;
}
else
{
vertical = false;
}
}
}
}
문제점 1: moveRightAndLeft() 에서 Time.deltaTime 누락
moveRightAndLeft() 함수를 보면 position.x -= speed; 또는 position.x += speed; 이렇게 Time.deltaTime을 곱하지 않고 있습니다. Update() 함수는 프레임마다 호출되는데, speed만 더하면 컴퓨터 사양에 따라, 또는 프레임 속도에 따라 이동 거리가 달라져서 움직임이 일정하지 않게 됩니다.
수정 제안:
position.x -= speed * Time.deltaTime;
position.x += speed * Time.deltaTime;
문제점 2: moveUpAndDown()의 return; 문
moveUpAndDown() 함수 안에서 maxY나 minY에 도달했을 때 return;을 해버립니다. 이렇게 되면 다음 rigidbody2D.MovePosition(position); 코드가 실행되지 않고 함수가 종료됩니다. 즉, 경계에 닿는 순간 오브젝트의 움직임이 멈춰버립니다.
수정 제안:
return; 문을 제거하고 rigidbody2D.MovePosition(position);이 항상 실행되도록 해야 합니다.
문제점 3: Update()에서 rigidbody2D.MovePosition() 호출
물리 관련 코드는 일반적으로 FixedUpdate()에서 호출하는 것이 권장됩니다. FixedUpdate()는 일정한 시간 간격으로 호출되므로, 물리 계산이 더 정확하고 안정적입니다. Update()는 프레임마다 호출되므로 프레임 속도에 따라 변동성이 있습니다.
'개발 > 유니티' 카테고리의 다른 글
| [습관어플 만들기] #1 (일단, 금연 버튼 누르면, 달력으로 이동되는것까지만) (5) | 2025.08.29 |
|---|---|
| [유니티] 유니티 learn#9 (애니메이션) (3) | 2025.08.28 |
| [유니티] 유니티 learn #7 (0) | 2025.08.25 |
| [유니티] 유니티 learn #6 (5) | 2025.08.25 |
| [유니티] 유니티 learn #5 (6) | 2025.08.24 |