프로젝트명: Kraken Hunter
장르: 탑다운 슈팅 로그라이크
개발 기간: 3일
플랫폼: PC
개발 인원: 4명
역할: 기획 / 프로그래밍
기술 스택: Unity (C#)
프로젝트 개요
입학시험 과제였던 “Cannon for Ship”을 베이스로 발전시킨 로그라이크 슈팅 게임.
배의 이동 조작감을 개선하고, 대포 대신 작살(Harpoon) 을 사용하여
보다 정교하고 리스크 있는 전투를 구현했다.
180초 버티기 → 보스 사냥 구조로 확장,
보스 몬스터 “Kraken”을 처치하거나, 실패 시 재화를 이용해
무기를 강화하며 재도전할 수 있는 순환형 플레이 루프를 구축했다.
핵심 시스템
조작
- WASD: 이동
- 마우스 좌클릭: 작살 발사
- 게임오버 시: 상점으로 이동
전투 구조
- 작살 발사 → 충돌 → 회수
- 투사체는 클릭 지점까지 도달하거나 장애물/적과 충돌 시 회수 시작
- 투척 거리와 회수 시간은 비례, 업그레이드를 통해 단축 가능
- 발사 가능한 작살 개수 제한 존재
미끼 시스템
- 보스 출현 전 체력 개념으로 사용
- 플레이어는 일정 시간 동안 미끼(고래 사체)를 지켜야 함
- 미끼가 모두 파괴되면 보스 등장 실패 및 게임 오버
적 및 보스
- 상어: 플레이어를 추적, 처치 시 재화(Shark Fin) 제공
- 크라켄: 일정 시간 후 등장, 촉수로 근접 공격
- 미끼가 존재하지 않으면 등장하지 않음
환경 요소
- 섬: 무작위로 생성되어 투사체 경로를 방해
- 토네이도: 일정 시간 동안 플레이어를 중심으로 회전시켜 이동 제한
상점 및 강화 시스템
- 재화(Shark Fin) 를 사용해 작살 시스템을 업그레이드
- 상점 UI를 작살로 직접 공격하여 구매하는 독특한 상호작용 방식
- 업그레이드 항목:
- 발사 가능한 작살 개수 증가
- 작살 회수 시간 단축
개발 포인트
- 단 3일 만에 완성된 Cannon for Ship의 진화판
- 간단한 생존형 구조를 보스 레이드 + 성장형 루프로 발전시킴
- 무작위 환경과 업그레이드 요소를 통해 로그라이크 구조 완성
- 작살 투사체 회수, 미끼 보호, 상점 인터랙션 등 다층적 시스템 설계
성과 및 평가
- 짧은 개발 기간 내 완성도 높은 구조와 아트 콘셉트로 팀 내 호평
- “1일 입학 과제를 3일 팀 프로젝트로 확장시킨 사례” 로 높은 평가
- 캐주얼 슈팅에서 로그라이크 구조로 발전시키며 기획력과 시스템 설계 역량 입증
느낀 점
“Kraken Hunter”는 내가 처음으로
기존 게임을 발전시켜 새로운 구조를 설계한 프로젝트였다.
짧은 기간 동안 기획–구현–테스트 전 과정을 반복하면서,
‘기존 시스템을 어떻게 더 깊게 만들 수 있을까’ 를 고민하게 되었다.
특히 팀원들과 역할을 나누어 완성도 있는 결과물을 낸 경험은
협업의 즐거움과 설계의 중요성을 동시에 느끼게 한 프로젝트였다.



public class PlayerMove : MonoBehaviour
{
public float maxSpeed = 5f; // 최대 속도
public float acceleration = 3f; // 가속도
public float turnSpeed = 100f; // 회전 속도 (높을수록 빠르게 회전)
private Rigidbody2D rb;
private float moveInput;
private float turnInput;
private float deceleration = 20f; // 감속도
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
// 입력 받기
moveInput = Input.GetAxis("Vertical"); // W(1) / S(-1)
turnInput = Input.GetAxis("Horizontal"); // A(-1) / D(1)
}
void FixedUpdate()
{
// 현재 속도 가져오기
float currentSpeed = Vector2.Dot(rb.linearVelocity, transform.up);
// 가속 및 감속 처리
if (moveInput != 0)
{
float targetSpeed = moveInput * maxSpeed;
float speedDiff = targetSpeed - currentSpeed;
float accelerationRate = (moveInput > 0) ? acceleration : deceleration;
float movementForce = speedDiff * accelerationRate;
rb.AddForce(transform.up * movementForce, ForceMode2D.Force);
if (Mathf.Abs(currentSpeed) < maxSpeed || Mathf.Sign(targetSpeed) != Mathf.Sign(currentSpeed))
{
rb.AddForce(transform.up * movementForce, ForceMode2D.Force);
}
}
else
{
rb.angularVelocity = 0;
// 감속 시 관성을 줄이기 위한 감속 처리
rb.linearVelocity = Vector2.Lerp(rb.linearVelocity, Vector2.zero, deceleration * Time.fixedDeltaTime);
print(deceleration);
}
// 속도 제한 적용
if (rb.linearVelocity.magnitude > maxSpeed)
{
rb.linearVelocity = rb.linearVelocity.normalized * maxSpeed;
}
//// 회전 처리
//float turnAmount = turnInput * turnSpeed * Time.fixedDeltaTime;
//rb.AddTorque(-turnAmount, ForceMode2D.Force);
// 회전 처리
float turnAmount = turnInput * turnSpeed * Time.deltaTime; // 속도에 따라 회전량 조절
transform.Rotate(Vector3.forward, -turnAmount); // Z축 기준으로 회전
}
}
공격 로직:
작살 쏘는 스크립트
public class PlayerAttack : MonoBehaviour
{
public GameObject bullet;
public CameraController cameraController;
public int attackCount;
private int maxAttackCount = 1;
void Start()
{
cameraController = Camera.main.GetComponent<CameraController>();
maxAttackCount += StateManager.Instance.SpearCount;
attackCount = maxAttackCount;
}
void Update()
{
// 마우스 클릭 시 이동 시작
if (Input.GetMouseButtonDown(0) && attackCount > 0)
{
AttackCountDown();
GameObject bulletObj = Instantiate(bullet, transform.position,Quaternion.Euler(transform.position - Camera.main.ScreenToWorldPoint(Input.mousePosition)));
bulletObj.GetComponent<Spear>().targetPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if(cameraController != null)
StartCoroutine(cameraController.ShakeCamera());
}
}
public void AttackCountUp()
{
attackCount += 1;
}
public void AttackCountDown()
{
if (attackCount != 0)
{
attackCount -= 1;
}
}
}
작살 스크립트
public class Spear : MonoBehaviour
{
public float speed = 6f; // 작살 발사 속도
public float returnSpeed = 6f; // 작살이 돌아올 때 속도
public Vector3 targetPosition; // 목표 위치 (클릭한 위치)
public Vector3 startPosition; // 발사 시작 위치
public GameObject tail; // 작살과 플레이어를 연결하는 줄(LineRenderer)
public float acceleration = 2f; // 가속도 (사용되지 않음)
private float currentSpeed = 0f; // 현재 속도 (가속도용)
private Vector3 originalScale; // 원래 스케일 값
private bool isMoving = false; // 목표로 향하는 중인지 여부
private bool isReturn = false; // 되돌아오는 중인지 여부
private float reloadingTime;
private Bbb10311031_PlayerAttack _playerAttack;
GameObject enemy;
GameObject playerObj;
Transform[] points = new Transform[2];
private void Awake()
{
playerObj = GameObject.FindGameObjectWithTag("Player");
_playerAttack = playerObj.GetComponent<Bbb10311031_PlayerAttack>();
}
void Start()
{
startPosition = transform.position;
targetPosition = new Vector3(targetPosition.x, targetPosition.y, 0f); // 3D 좌표를 2D로 변환
returnSpeed = returnSpeed * StateManager.Instance.ReloadingTime(); // 재장전 속도 보정
SetTail();
isMoving = true; // 발사 시작
}
void SetTail()
{
points[0] = playerObj.transform;
points[1] = gameObject.transform;
tail.GetComponent<LineController>().SetUpLine(points);
}
void Update()
{
if (isMoving)
{
transform.up = (targetPosition - startPosition).normalized; // 작살 진행 방향 조정
// 목표 지점까지 이동
transform.position = Vector3.Lerp(transform.position, targetPosition, speed * Time.deltaTime);
// 목표 지점에 도달 시 처리
if (Vector3.Distance(transform.position, targetPosition) <= 0.1f)
{
if (enemy != null)
{
StateManager.Instance.CoinPlus();
//SoundManager.instance.PlaySFX("Clash"); // 충돌 사운드
}
else
{
//SoundManager.instance.PlaySFX("SmallCanon"); // 빗나감 사운드
}
isReturn = true; // 돌아오는 상태로 전환
isMoving = false;
GetComponent<CapsuleCollider2D>().enabled = false;
}
}
else if (isReturn && playerObj != null)
{
// 되돌아올 때 방향 보정
transform.up = Vector3.Lerp(transform.up, (targetPosition - playerObj.transform.position).normalized, Time.deltaTime);
transform.position = Vector3.MoveTowards(transform.position, playerObj.transform.position, returnSpeed * Time.deltaTime);
SpearRotation();
// 플레이어에게 도착 시
if (Vector3.Distance(transform.position, playerObj.transform.position) < 0.1f)
{
_playerAttack.AttackCountUp();
Destroy(gameObject);
}
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Enemy") && isMoving) // 적과 충돌 시
{
StateManager.Instance.CoinPlus();
Destroy(other.gameObject);
ReturnStart();
// (임시 처리) 특정 적과 충돌 시 상점 이동
if (other.GetComponent<KrakenMove>() != null)
GameManager.Instance.GoShopScene();
}
if (other.CompareTag("Obstacle") && isMoving) // 장애물 충돌 시
{
ReturnStart();
}
if (other.CompareTag("Boss") && isMoving) // 보스와 충돌 시
{
GameManager.Instance.DamagedBossHP(2); // 보스 체력 감소
ReturnStart();
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Enemy") && isMoving) // 적과 충돌 시
{
Destroy(collision.gameObject);
ReturnStart();
}
if (collision.gameObject.CompareTag("Obstacle") && isMoving) // 장애물 충돌 시
{
ReturnStart();
}
}
void SpearRotation()
{
Vector2 direction = transform.position - playerObj.transform.position;
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);
}
// 돌아오는 상태로 전환 (충돌 시에도 호출)
void ReturnStart()
{
isMoving = false;
isReturn = true;
GetComponent<CapsuleCollider2D>().enabled = false;
}
}
작살 공격 시 화면 흔들림 구현
public class CameraController : MonoBehaviour
{
public float shakeDuration = 0.3f; // 흔들리는 지속 시간
public float shakeMagnitude = 0.2f; // 흔들림 강도
Vector3 originalPosition;
[field: SerializeField] public Vector2 ScreenArea { get; private set; } // 화면 크기
void Start()
{
originalPosition = transform.position;
// 카메라의 화면 경계를 월드 좌표로 변환
ScreenArea = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, transform.position.z));
}
// 카메라 화면 흔들기
public IEnumerator ShakeCamera()
{
float elapsedTime = 0f;
while (elapsedTime < shakeDuration)
{
float offsetX = Random.Range(-1f, 1f) * shakeMagnitude;
float offsetY = Random.Range(-1f, 1f) * shakeMagnitude;
Camera.main.transform.position = originalPosition + new Vector3(offsetX, offsetY, 0);
elapsedTime += Time.deltaTime;
yield return null;
}
Camera.main.transform.position = originalPosition; // 원래 위치로 복귀
}
}'프로젝트 > 크래프톤 정글 게임랩 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 |