입학 전에는.. 단지 게임을 좋아한다는 이유로 게임 개발자가 되고 싶어서 글로벌미디어학부에 입학했다.
입학 후 Unity를 시작해보았는데 뭔가 내가 생각했던 게임 개발자의 모습과 많이 달라서
앱 개발자로 전환했던 에피소드가,,ㄱ-
그 이후로 유니티는 손도 안 댈 줄 알고 수업도 다 피해서 들었는데,
졸업작품으로 유니티를 하게 될 줄은 몰랐따 ...
역시 사람 일은 모른다!!!!!!!
그래두 경험 해보는 게 재밌는 거 같다~
이번 포스팅에서 완성할 것은
- 심박센서에 손가락을 대면
- 5초동안 기다렸다가
- 심박수에 따라 나만의 꽃이 탄생하는 것!
을 해볼 것이다!
[졸업프로젝트] #3 심박센서 값 읽기
전 포스팅에 해둔 것들이 필요하다
심박값에 따라서 제어할 유니티 파일이 필요하다.
일단 3d 모델들을 불러와야한다
피어날 꽃들의 이름들을 3d파일로 만들고 애니메이션까지 넣은 객체들이다
Flowertext(i)은 PulseFlower(i)의 이름!
피어날 꽃들의 3d모델들이다!
심박수에 따라 꽃들을 컨트롤 할 Empty 객체이다!
PulseControl
에 Serial Controller.cs
붙이기에셋스토어에서 Ardity
를 설치하면 Ardity\Scripts\SerialController.cs
파일이 생긴다. 그걸 그대로 빈 오브젝트에 붙인다.
Port Name
에는 아두이노 보드 포트 넘버를 입력하고
Rate
도 본인의 아두이노 Rate와 맞추면 된다
Message Listener
에는 내가 시리얼 통신을 함으로써 움직이게 할 오브젝트를 넣어준다.
내 예시대로 입력하면 COM13로 들어오는 아두이노 값은 PulseControl에 붙어있는 메세지리스너
를 제어한다.
PulseControl에 붙어있는 메세지리스너
는 바로 아래에서 설명하겠다.
PulseMsgListener.cs
작성하기아두이노 값에 따라 작동될 메세지리스너를 작성해보겠다
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PulseMsgListener : MonoBehaviour
{
private bool isCollectingData = false;
private List<int> heartRates = new List<int>();
private List<Animator> pulseFlowerAnimators = new List<Animator>();
private List<Animator> textFlowerAnimators = new List<Animator>();
public GameObject flower1;
public GameObject flower2;
public GameObject flower3;
public GameObject flower4;
public GameObject flower5;
void Start()
{
// "PulseFlower" 오브젝트들의 Animator를 별도로 저장
for (int i = 1; i <= 5; i++)
{
GameObject pulseFlower = GameObject.Find("PulseFlower (" + i + ")");
if (pulseFlower != null)
{
Animator animator = pulseFlower.GetComponent<Animator>();
if (animator != null)
{
pulseFlowerAnimators.Add(animator);
}
}
//FlowerText들 찾아서 애니메이터 저장
GameObject flowerText = GameObject.Find("FlowerText (" + i + ")");
if (flowerText != null)
{
Animator animator = flowerText.GetComponent<Animator>();
if (animator != null)
{
textFlowerAnimators.Add(animator);
}
}
SetObjectAndChildrenVisibility("FlowerText (" + (i) + ")", false);
}
flower1.SetActive(false);
flower2.SetActive(false);
flower3.SetActive(false);
flower4.SetActive(false);
flower5.SetActive(false);
}
public void OnMessageArrived(string msg)
{
try
{
int heartRate = int.Parse(msg);
if (isCollectingData)
{
heartRates.Add(heartRate);
}
else if (heartRate <= 10)
{
// 꽃 애니메이션 처음으로 되돌려놓기
foreach (Animator animator in pulseFlowerAnimators)
{
if (animator != null)
{
animator.SetBool("isPulsed", false);
}
}
// 텍스트 애니메이션 처음으로 되돌려놓기
foreach (Animator animator in textFlowerAnimators)
{
if (animator != null)
{
animator.SetBool("isPulsed", false);
}
}
for (int i = 1; i <= 5; i++)
{
SetObjectAndChildrenVisibility("FlowerText (" + (i) + ")", false);
}
flower1.SetActive(false);
flower2.SetActive(false);
flower3.SetActive(false);
flower4.SetActive(false);
flower5.SetActive(false);
Debug.Log("평균내기 시작");
Debug.Log("Heart Rate: " + heartRate);
isCollectingData = true;
heartRates.Clear();
heartRates.Add(heartRate);
StartCoroutine(CollectDataFor5Seconds());
}
}
catch (System.Exception)
{
throw;
}
}
IEnumerator CollectDataFor5Seconds()
{
yield return new WaitForSeconds(5);
if (heartRates.Count > 0)
{
float average = 0;
foreach (int rate in heartRates)
{
average += rate;
}
average /= heartRates.Count;
Debug.Log("Average Heart Rate: " + average);
// 오브젝트 활성화 조건에 따라 처리
ShowObjectBasedOnAverage(average);
}
isCollectingData = false;
}
void ShowObjectBasedOnAverage(float average)
{
int index = -1;
if (average < 320)
{
index = 0;
flower1.SetActive(true);
}
else if (average >= 320 && average < 340)
{
index = 1;
flower2.SetActive(true);
}
else if (average >= 340 && average < 360)
{
index = 2;
flower3.SetActive(true);
}
else if (average >= 360 && average < 380)
{
index = 3;
flower4.SetActive(true);
}
else if (average >= 380)
{
index = 4;
flower5.SetActive(true);
}
if (index != -1 && index < pulseFlowerAnimators.Count)
{
pulseFlowerAnimators[index].SetBool("isPulsed", true);
textFlowerAnimators[index].SetBool("isPulsed", true);
SetObjectAndChildrenVisibility("PulseFlower (" + (index + 1) + ")", true);
SetObjectAndChildrenVisibility("FlowerText (" + (index + 1) + ")", true);
HideObjectAfterTime("PulseFlower (" + (index + 1) + ")", 10);
}
}
public void SetObjectAndChildrenVisibility(string objectName, bool visible)
{
GameObject obj = GameObject.Find(objectName);
if (obj != null)
{
Debug.Log("Found object: " + objectName);
Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();
foreach (Renderer renderer in renderers)
{
renderer.enabled = visible;
Debug.Log("Setting visibility for: " + renderer.gameObject.name + " to " + visible);
Debug.Log(renderer.gameObject.name + "의 enabled" + renderer.enabled);
}
}
else
{
Debug.Log("Could not find object: " + objectName);
}
}
IEnumerator HideObjectAfterTime(string objectName, float delay)
{
// 지정된 시간만큼 기다림
yield return new WaitForSeconds(delay);
// 지정된 시간이 지난 후 오브젝트를 숨깁니다.
SetObjectAndChildrenVisibility(objectName, false);
// 꽃 애니메이션 처음으로 되돌려놓기
foreach (Animator animator in pulseFlowerAnimators)
{
if (animator != null)
{
animator.SetBool("isPulsed", false);
}
}
}
}
isCollectingData
데이터 수집의 활성화 여부를 나타내는 불리언 값
heartRates
수집된 심박수 데이터를 저장하는 정수형 리스트
pulseFlowerAnimators
와 textFlowerAnimators
각각 꽃과 텍스트 오브젝트의 애니메이터 컴포넌트를 저장하는 리스트
flower1부터 flower5
꽃 오브젝트들을 참조하는 게임 오브젝트 변수
Start 메소드
게임이 시작할 때 실행되는 메소드로, PulseFlower와 FlowerText 오브젝트들을 찾아 그들의 애니메이터 컴포넌트를 저장합. 또한, 모든 꽃 오브젝트를 비활성화.
OnMessageArrived 메소드
외부에서 호출되어 심박수 데이터를 받는 메소드. 심박수가 10 이하일 경우, 데이터 수집을 시작하고, 5초 동안 데이터를 수집하는 CollectDataFor5Seconds 코루틴을 시작.
CollectDataFor5Seconds 코루틴
5초간 대기한 후, 수집된 심박수 데이터의 평균을 계산하고, 그 평균값에 따라 특정 꽃 오브젝트와 애니메이션을 활성화하는 ShowObjectBasedOnAverage 메소드를 호출.
ShowObjectBasedOnAverage 메소드
평균 심박수 값에 따라 특정 꽃 오브젝트를 활성화하고, 해당 꽃과 텍스트의 애니메이션을 활성화.
또한, 지정된 시간 후에 오브젝트를 다시 숨기는 HideObjectAfterTime 코루틴을 시작.
SetObjectAndChildrenVisibility 메소드
지정된 오브젝트와 그 자식 오브젝트들의 가시성을 설정.
오브젝트를 찾아 해당 오브젝트의 렌더러 컴포넌트의 활성화 여부를 변경.
제일 핵심적인 메소드!!!!
HideObjectAfterTime 코루틴
지정된 시간이 지난 후에 오브젝트를 숨기고, 꽃 오브젝트의 애니메이션을 초기 상태로 되돌림.
다음 사람도 체험하려면 초기화해야하기 때문에 만든 코드
PulseControl
에 PulseMsgListener.cs
붙이기
아까 Serial Controller
를 붙인 PulseControl
에 PulseMsgListener.cs
붙이고
Flower1~5 파라미터
에 제어할 PulseFlower들
을 넣어야한다
이렇게 한 이유는 활성화/비활성화 를 제어해야하는데,
객체가 비활성화가 되면
GameObject pulseFlower = GameObject.Find("PulseFlower (" + i + ")");
이 코드로 다시 객체를 찾을 수 없다...
그래서 진짜 방법이 없을까 하면서 찾다가 (챗지피티도 안 알려줌)
파라미터에 넣으면 다시 활성화를 시킬 수 있어서! 그 방법을 사용했다
Animator Controller
에 전환조건 추가하기
아까 메세지리스너 코드에서
if (index != -1 && index < pulseFlowerAnimators.Count)
{
pulseFlowerAnimators[index].SetBool("isPulsed", true);
textFlowerAnimators[index].SetBool("isPulsed", true);
SetObjectAndChildrenVisibility("PulseFlower (" + (index + 1) + ")", true);
SetObjectAndChildrenVisibility("FlowerText (" + (index + 1) + ")", true);
HideObjectAfterTime("PulseFlower (" + (index + 1) + ")", 10);
}
이 부분에서 피어나야 할 꽃의 text
와 pulseFlower
의 isPulsed에 true를 주면
애니메이터 컨트롤러가 동작하는 형식이다!
Flowertext(i)
와 PulseFlower(i)
하나하나 다 돌아가면서 추가해줘야함.. ㅋ
나의 심박수에 맞는 꽃이 피어난 모습!
애니메이션이 진짜 예쁜데 벨로그는 동영상 첨부가 안되더라.. 아쉽
보고 싶다면 저의 졸업전시회에 오세요!