프로젝트명: Kraken Hunter (Rework)
장르: 로그라이크, 핵 앤 슬래시, 탑뷰 액션
개발 기간: 7일
플랫폼: PC
개발 인원: 개인 프로젝트
역할: 기획 / 프로그래밍 / 아트
기술 스택: Unity (C#), Particle System, Trail Renderer, Cinemachine, FSM
프로젝트 개요
이전 주의 Kraken Hunter를 기반으로,
“카타나 제로”의 핵심 재미요소(속도감·타격감·도전성) 를 Unity에서 재현한 개인 프로젝트.
불렛 타임(Bullet Time) 과 패링(Parry), 히트 스탑(Hit Stop) 등
액션 게임의 정수를 직접 구현하여
속도감과 긴장감이 공존하는 핵 앤 슬래시 스타일의 로그라이크 게임으로 발전시켰다.
기획 의도 및 핵심 목표
“단순히 공격하는 게임이 아니라, 리듬과 감각으로 몰입시키는 전투를 만들자.”
- 기존 Kraken Hunter의 슈팅 시스템을 근접 작살 전투 시스템으로 개편
- 빠른 템포 속에서도 컨트롤의 여유를 주는 불렛 타임 시스템 구현
- 보스 패턴을 다단계 FSM 구조(1페이즈 → 2페이즈) 로 발전시켜
단조로운 반복 전투에서 벗어난 “패턴 읽기형 전투” 실현
주요 시스템
플레이어 조작
- W, A, S, D: 이동
- 마우스 좌클릭: 공격 (작살 휘두르기)
- 대시 중 불렛타임 발동
핵심 피처
기능설명
| 불렛 타임 (Bullet Time) | 일정 시간 동안 게임 속도를 느리게 하여, 빠른 패턴을 인지하고 반응할 기회를 제공. 불릿타임 종료 후의 속도 대비로 속도감 극대화. |
| 근접전 (Melee Combat) | 원거리 작살 대신 근거리 휘두르기 방식으로 변경. 제한된 사거리 내에서 공격적 플레이를 유도. |
| 원샷 원킬 (One Shot, One Kill) | 보스를 제외한 모든 적은 한 방에 사망. 플레이어 역시 한 방에 죽음 → 리스크·보상 구조 강화. |
| 히트 스탑 (Hit Stop) | 공격 적중 시 짧은 시간 정지 + 카메라 셰이크 적용 → 타격감 상승. |
| 패링 (Parry) | 타이밍에 맞게 공격을 튕겨내며 역공 가능. 수동적 회피 대신 적극적 대응 유도. |
적 및 보스 설계
- 일반 적: 단발성 공격 / 근접 접근 유도
- 보스:
- Phase 1: 기본 공격 패턴, 시선 유도 공격
- Phase 2: 이동형 패턴 + 연속 공격 + 타이밍 패링 유도
- FSM 기반으로 상태 전이(Idle → Attack → Pattern → Stun → Phase 2) 설계
비주얼 연출
- Trail Renderer: 작살 휘두름 궤적 연출로 액션 감각 강화
- Particle System: 피격, 폭발, 피 튀김 등 타격 효과 표현
- Cinemachine 카메라 셰이킹: 공격 타이밍에 맞춰 강한 반동 효과 제공
- 히트 스탑과 함께 “멈춤 후 폭발”의 타격감을 구현
핵심 재미 요소
- 속도감 → 불렛 타임 전후 대비로 체감 강화
- 타격감 → 히트 스탑 + 카메라 셰이크 + 트레일
- 도전성 → 원샷원킬 구조 + 난이도 상승형 스테이지
- 패턴 공략 → 보스의 다단계 FSM 구조를 읽어 대응
느낀 점
“Kraken Hunter (Rework)”는
내가 ‘게임의 핵심 재미’를 분석하고 직접 재현해본 첫 실험 프로젝트였다.
- 불렛타임·패링·히트스탑 같은 액션 핵심 피처를 실제로 구현하며
게임의 감각적 완성도를 설계하는 법을 배웠고, - 코드로 감정을 전달하는 “리듬 있는 플레이”의 중요성을 깨달았다.
짧은 7일의 작업이었지만,
기획·아트·프로그래밍이 한 사람의 시점에서 완전히 통합된 액션 게임이었다.

=
















---------------------------------------------
불렛타임 및 히트스탑
public class TimeManager : MonoBehaviour
{
static TimeManager _instance;
public static TimeManager Instance { get { return _instance; } private set { } }
public float playTime = 0;
public float bulletTimeGauge = 100;
private bool isbulletTime;
private bool waiting;
private int hitNum = 0;
private bool isClear;
public Image bulletBackground;
void Awake()
{
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
}
else
Destroy(gameObject);
}
private void Update()
{
if (!isClear)
{
playTime += Time.unscaledDeltaTime;
}
BulletTime();
if (hitNum > 0 && !waiting) WaitingHitStop(0.2f);
if (bulletBackground != null)
{
if (isbulletTime)
{
bulletBackground.color = Color.Lerp(bulletBackground.color, new Color32(255, 255, 255, 10), Time.unscaledDeltaTime * 10);
}
else
{
bulletBackground.color = Color.Lerp(bulletBackground.color, new Color32(255, 255, 255, 0), Time.unscaledDeltaTime * 50);
}
}
else
{
bulletBackground = GameObject.FindGameObjectWithTag("BulletTime").GetComponent<Image>();
}
}
public void Dead()
{
bulletTimeGauge = 100;
isbulletTime = false;
Time.timeScale = 1f;
Time.fixedDeltaTime = 0.02f; // 기본값 복구
}
public void BulletTime()
{
if (Input.GetKeyDown(KeyCode.Mouse1))
{
isbulletTime = true;
Time.timeScale = 0.1f;
Time.fixedDeltaTime = 0.02f * Time.timeScale; // 물리 연산을 부드럽게
}
if (Input.GetKeyUp(KeyCode.Mouse1))
{
isbulletTime = false;
Time.timeScale = 1f;
Time.fixedDeltaTime = 0.02f; // 기본값 복구
}
if (isbulletTime && bulletTimeGauge >= 0)
{
bulletTimeGauge -= Time.unscaledDeltaTime * 20;
}
else
{
if (Time.timeScale != 1f && isbulletTime)
{
isbulletTime = false;
Time.timeScale = 1f;
Time.fixedDeltaTime = 0.02f; // 기본값 복구
}
if (bulletTimeGauge <= 100)
{
bulletTimeGauge += Time.unscaledDeltaTime * 10;
}
else
{
bulletTimeGauge = 100;
}
}
}
public void HitStop(float duration)
{
hitNum += 1;
//if (waiting)
// return;
//waiting = true;
//Time.timeScale = 0.0f;
//StartCoroutine(Wait(duration));
}
void WaitingHitStop(float duration)
{
if (waiting)
return;
waiting = true;
Time.timeScale = 0.0f;
StartCoroutine(Wait(duration));
}
IEnumerator Wait(float duration)
{
print("1");
Camera.main.GetComponent<CameraController>().StartShake(0.4f, 0.2f);
yield return new WaitForSecondsRealtime(duration);
Time.timeScale = isbulletTime ? 0.1f : 1.0f;
hitNum -= 1;
waiting = false;
}
public void Clear()
{
isClear = true;
}
}
불렛패링
public class PirateBullet : MonoBehaviour
{
public float speed;
private Vector3 dir;
public void SetDirection(Vector3 target)
{
dir = target.normalized;
}
// Update is called once per frame
void Update()
{
transform.up = dir.normalized;
transform.position += dir * speed * Time.deltaTime;
}
protected virtual void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Spear")
{
Camera.main.GetComponent<CameraController>().StartShake(0.3f, 0.3f);
dir *= -1;
gameObject.tag = "PlayerBullet";
speed = 50;
}
if (other.tag == "Player")
{
print("Player die");
GameManager.Instance.GameOver();
Destroy(gameObject);
}
if (other.tag == "Obstacle")
{
Destroy(gameObject);
}
}
}
'프로젝트 > 크래프톤 정글 게임랩 3기' 카테고리의 다른 글
| [크래프톤 정글 게임랩] 곰팡이 식당 (1) | 2025.08.24 |
|---|---|
| [크래프톤 정글 게임랩] 방패 용사 김소드 (0) | 2025.08.24 |
| [크래프톤 정글 게임랩] 로그라이트 배 작살 게임으로 변신! (0) | 2025.08.24 |
| [크래프톤 정글 게임랩] 배 대포 게임?! (0) | 2025.08.24 |
| Maru Expedition: we can fly (0) | 2025.08.18 |