2023 전공동아리 3분기 팀 프로젝트
깃허브 : https://github.com/DodgeNinga/Sparrow-s_Journey
다운로드 : https://drive.google.com/file/d/1pJ84HjwVijF6XT75D9flm9buGkQgeoxj/view?usp=sharing
영상 : https://youtu.be/lsftP2nCGmU
1. 게임 개요
개발 인원 : 4명
설명 : 2022 전공동아리 탈주닌자 3분기 2팀 프로젝트 입니다 총 4인(2학년 2명 1학년 2명)이 개발하였고 저는 이 프로젝트에서 매인 개발과 팀장을 담당하였고 3분기 1등이라는 성적을 달성하였습니다
장르 : 퍼즐
기획 의도 : 1, 2분기 전공동아리에서 동아리원들에게 너무 가르쳐준것이 없다고 생각하여 같이 배우면서 만들 수 있는 게임은 무었인지 고민을 하다 모뉴먼트 벨리 라는 게임을 발견하여 이 게임을 모작하며 여러가지를 배우자는 취지에서 기획하게 되었습니다
2. 개발 전 구상
단순하고 간단한 느낌의 그래픽 디자인모뉴먼트 벨리와 차별성이 있는 여러가지 기믹 요소
귀여운 참새
착시현상을 최대한 잘 살린 맵 디자인
3. 구현
착시 현상
모뉴먼트 벨리를 모티브로 기획하였기 때문에 모뉴먼트 벨리의 착시현상 시스템을 구현할 필요가 있었다 유니티의 Depth카메라를 사용하여 착시 현상을 구현하였다
플레이어 움직임
모뉴먼트 벨리처럼 클릭한 곳까지 최단거리로 이동하는 기능이 필요하였다 그래서 어떤 알고리즘을 사용할까 고민하다 자료구조 시간에 배운 DFS, BFS가 생각이 났고 BFS를 응용하여 클릭한 곳 까지 최단거리로 이동하는 스크립트를 작성하였다
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[field:SerializeField] public RoadRoot currentRoad { get; private set; }
[field:SerializeField] public LayerMask layerMask { get; private set; }
[field:SerializeField] public GameObject chageObj { get; private set; }
[SerializeField] private GameObject pingPrefab;
private PlayerMove playerMove;
public bool clickAble = true;
private void Awake()
{
playerMove = GetComponent<PlayerMove>();
}
private void Update()
{
InputChack();
}
private void InputChack()
{
if(Input.GetMouseButtonDown(0) && clickAble)
{
GetClickBox();
}
}
private void GetClickBox()
{
var cameraRay = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(cameraRay, out var hit, 1000, LayerMask.GetMask("Road")))
{
if(hit.transform.TryGetComponent<RoadRoot>(out var compo))
{
clickAble = false;
FindRoad(compo);
}
}
}
/// <summary>
/// 최단거리의 길 탐색
/// </summary>
/// <param name="end"></param>
private void FindRoad(RoadRoot end)
{
(RoadRoot road, int idx) endKey = (null, 0);
Dictionary<(RoadRoot road, int idx), List<RoadClass>> roadContainer = new();
Queue<(RoadRoot road, int idx)> visQueue = new(); //방문큐
HashSet<RoadRoot> visited = new() { currentRoad }; //방문했니?
CreateConnectKey(currentRoad, visQueue, roadContainer, visited, (null, 0));
while(visQueue.Count > 0)
{
var current = visQueue.Dequeue();
var last = roadContainer[current].Last();
visited.Add(last.road);
if(last.road == end) //도착이라면?
{
endKey = current;
break;
}
else if (IsCrossRoad(last.road, visited)) //교차로라면?
{
CreateConnectKey(last.road, visQueue, roadContainer, visited, current);
}
else //아니라면?
{
if(GetConnectRoadIndex(last.road, visited) != -1)
{
roadContainer[current].Add(
last.road.connected[GetConnectRoadIndex(last.road, visited)]);
visQueue.Enqueue(current);
}
}
}
if(endKey.road != null)
{
currentRoad = roadContainer[endKey].Last().road;
Instantiate(pingPrefab, currentRoad.GetMovePos(), Quaternion.Euler(-90, 0, 0));
SoundManager.instance.PlayerSFX("Ping");
playerMove.ExecuteMove(roadContainer[endKey].ToList(), () => { clickAble = true; });
}
else
{
clickAble = true;
}
}
private void CreateConnectKey(RoadRoot current,
Queue<(RoadRoot road, int idx)> visQueue,
Dictionary<(RoadRoot road, int idx), List<RoadClass>> roadContainer,
HashSet<RoadRoot> visited,
(RoadRoot road, int idx) beforeKey)
{
for(int i = 0; i < current.connected.Count; i++)
{
if (visited.Contains(current.connected[i].road) ||
!current.connected[i].road.moveAble || roadContainer.ContainsKey((current, i))) continue;
var obj = (current, i);
roadContainer.Add(obj, new());
visQueue.Enqueue(obj);
if(beforeKey.road != null)
{
roadContainer[obj] = roadContainer[beforeKey].ToList(); //값만 복사
}
roadContainer[obj].Add(current.connected[i]);
}
}
private bool IsCrossRoad(RoadRoot road, HashSet<RoadRoot> visited)
{
return road.connected.Where((x) =>
{
return !visited.Contains(x.road) && x.road.moveAble;
}).ToList().Count > 1;
}
private int GetConnectRoadIndex(RoadRoot road, HashSet<RoadRoot> visited)
{
return road.connected.FindIndex(x =>
{
return x.road.moveAble && !visited.Contains(x.road);
});
}
public void ChangeTop() => chageObj.layer = 6;
public void ChangeDef() => chageObj.layer = 0;
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
public class PlayerMove : MonoBehaviour
{
private readonly int WALKHASH = Animator.StringToHash("Walk");
private Animator animator;
private PlayerController playerController;
public Action GetFade;
private void Awake()
{
animator = GetComponentInChildren<Animator>();
playerController = GetComponent<PlayerController>();
}
public void ExecuteMove(List<RoadClass> roads, Action endCallback)
{
Sequence seq = DOTween.Sequence();
animator.SetBool(WALKHASH, true);
transform.SetParent(null);
foreach (var item in roads)
{
seq.AppendCallback(() =>
{
var dir = item.dir switch
{
LookDir.front => item.road.transform.forward,
LookDir.back => -item.road.transform.forward,
LookDir.left => -item.road.transform.right,
LookDir.right => item.road.transform.right,
_ => transform.forward,
};
dir.y = 0;
transform.forward = -dir;
});
seq.Append(transform.DOMove(item.road.GetMovePos(), 0.3f).SetEase(Ease.Linear));
if (item.road.transform.CompareTag("Goal"))
{
seq.OnComplete(() =>
{
GetFade?.Invoke();
});
}
}
seq.AppendCallback(() =>
{
animator.SetBool(WALKHASH, false);
transform.SetParent(playerController.currentRoad.transform);
endCallback();
});
}
}
4. 프로젝트의 문제점
모뉴먼트 벨리의 모작이기도 하고 팀원들과 같이 배우면서 개발을 한 탓에 사전에 기획해 놓았던 차별성 요소를 개발하지 못한점이 아쉬웠다
5. 프로젝트로 느낀점
전공동아리의 마지막 프로젝트였어서 최대한 열심히 개발 하였고 그만큼 좋은 결과가 나와서 뿌듯하고 기분이 좋았다 하지만 팀원과 배우면서 개발을 하여 코드를 많이 작성하지 못한 부분은 조금 아쉬웠다
'포트폴리오 > 팀 프로젝트' 카테고리의 다른 글
[팀 프로젝트] Ancient City : Pottery (0) | 2024.04.29 |
---|---|
[팀 프로젝트] 약한마왕디펜스 (0) | 2024.03.06 |
[팀 프로젝트] Hypertension (0) | 2024.03.05 |