기존에 리펙토링과, 비주얼 개선작업을 진행한 drag system의 결과는 다음과 같았다.
https://nunsori.tistory.com/19
[Unity] Merge-Is-Mine 핵심기능 리팩토링, 비주얼 개선
1. 기존 drag system의 문제점@merge effect글자 리소스 변경 제외@우선 기획자가 작성한 프로토타입을 새로운시스템으로 개발을 해야할 필요성이 있었다. - 문제점1. 현재 드래그하여 물체를 이동하는
nunsori.tistory.com
물체를 이동시키고, glow를 넣고, 화살표 가이드를 추가하고, 물체 이동 조건을 더 명확히하여 개선하였지만,
몇가지 문제사항이 더 남아있었다.
물체가 0.n 초의 딜레이 후 이동이 아닌, 즉발형으로 이동되어 유저가 받는 피드백, 액션성 부족
문제상황을 하나로 정리하면 위와같은데, 이를 해결하기 위해, 붉은색 영역이 초록색 영역으로 이동하는 듯한 애니메이션과, 부가 효과를 넣어 해결을 하려 한다.

기존에 작성하였던 핵심 기능만 정리된 다아이어그램을 확인해보면,
zonepool 함수 내에 DestroyZoneCor 함수를 확인할 수 있다.
해당 함수를 확인하면 아래와 같은데,

주석이 포함된 설명을 확인하니 이 글과같은 유지보수, 신규기능제작과 같은 상황에 다이어그램의 정리가 매우 유용하였다.
해당 함수를 수정함으로서 부가 연출을 진행해줄 수 있을것이며,
기존에는 영역 자체를 줄이며 바로 사라지게 하는 코루틴이 수행되고 있었음을 확인할 수 있다.
해당 부분에 새로운 연출 코루틴을 호출해주도록 만들어주어 수정을 하면 될것이다.
그럼 연출은 어떤것을 추가하는 것이 좋을까?
여러 래퍼런스 예시를 생각해 보았을때,
1. 영역주변에 일그러지는 듯한 약간의 왜곡효과 -> 영역의 강조
2. 영역이 이동하는것과 같은 잔상효과 -> 플랫포머 게임 대부분에서 나타나는 대쉬, 공격시 남는 약간의 잔상
3. 합쳐졌을때, 나오는 폭발과 같은 파티클 -> 영역이 합쳐졌다는 것을 나타내는 강조 효과
4. 바로 합쳐지는 것이 아닌 약간의 딜레이, 색변환 -> 플랫포머에서 플레이어가 몬스터를 피격할 시 약간의 색 변환이나, 경직등은 타격감에 큰 도움을 주었다.
따라서 다음과같은 새로운 코루틴을 호출해주도록 구성하였다
/// <summary>
/// Drag 잔상 이동관련 연출추가
/// </summary>
/// <param name="zoneType">현재 zone의 type지정</param>
/// <param name="MainZonePos">이동해야할 main zone의 위치</param>
/// <param name="MainZoneSize">main zone의 크기</param>
/// <param name="timeCheck">시간 체크진행 여부 (true 시 해당 코루틴 진행중 드래그 불가)</param>
/// <returns></returns>
private IEnumerator TranslateToMainZone(ZoneType zoneType, Vector3 MainZonePos, Vector2 MainZoneSize, bool timeCheck = false)
그렇다면 필요한 것은 다음과 같다.
1. 왜곡 쉐이더
2. 잔상 기능
3. 파티클 기능
4. dotween을 이용한 약간의 딜레이, 화면떨림등의 효과
그러면 이것을 구현목표로 잡되, 잔상기능, 왜곡 기능을 우선 기능해보도록 하자
잔상 기능
잔상기능의 예시는 다음과 같이 찾아볼 수 있었다.
https://www.youtube.com/watch?v=7vvycc2iX6E&t=479s
첫번째로 간단히 AfterImage라는 이름의 class를 구성하여, 투명도를 구성하도록 작성해주었다.
using UnityEngine;
namespace MergeIsMine.Util
{
/// <summary>
/// 잔상스크립트 구성진행, spriterenderer를 가진 오브젝트 내에 부착
/// </summary>
public class AfterImage : MonoBehaviour
{
#region Variables
private float interval; // 잔상 간격
private Transform target; // 부착된 gameobject의 transform
private SpriteRenderer renderer; //부착된 gameobject의 spriterenderer
private bool starting = false; // after image가 진행중인지 여부
private float timef = 0f; // after image 시작후 경과된 시간
#endregion
#region temp values
private Color color;
private float OpacityRatio = 1f;
#endregion
#region Methods
/// <summary>
/// 현재 AfterImage 진행중인지 반환
/// </summary>
/// <returns>true : 연출진행중 / false : 연출 비활성화상태</returns>
public bool Available()
{
return !starting;
}
/// <summary>
/// 초기화 밑 연출시작
/// </summary>
/// <param name="interval">잔상지속시간</param>
/// <param name="targetpos">타겟 transform</param>
/// <param name="renderer">타겟 renderer</param>
/// <param name="targetSize">타겟의 sprite size</param>
public void Init(float interval, Transform targetpos, SpriteRenderer renderer, Vector2 targetSize)
{
target = targetpos;
gameObject.transform.position = target.position;
this.interval = interval;
this.renderer = renderer;
this.renderer.size = targetSize;
renderer.color = Color.white;
color = Color.white;
gameObject.SetActive(true);
timef = 0;
starting = true;
}
/// <summary>
/// 초기화 연출시작
/// </summary>
/// <param name="interval">잔상지속시간</param>
/// <param name="pos">잔상위치</param>
/// <param name="renderer">타겟 랜더러</param>
/// <param name="targetSize">스프라이트 사이즈</param>
/// <param name="OpacityRatio">잔상 투명도 배율</param>
public void Init(float interval, Vector3 pos, SpriteRenderer renderer, Vector2 targetSize, float OpacityRatio)
{
//target = targetpos;
gameObject.transform.position = pos;//target.position;
this.interval = interval;
this.renderer = renderer;
this.renderer.size = targetSize;
renderer.color = Color.white;
color = Color.white;
gameObject.SetActive(true);
timef = 0;
starting = true;
this.OpacityRatio = OpacityRatio;
}
#endregion
#region LifeCycles
/// <summary>
/// 잔상 시작 시 지속시간 동안 잔상의 투명도를 낮추고 종료
/// </summary>
void Update()
{
if (starting)
{
timef += Time.deltaTime;
if (timef >= interval)
{
timef = 0f;
color = Color.white;
starting = false;
gameObject.SetActive(false);
return;
}
color.a = (1f - (timef / interval)) / OpacityRatio;
renderer.color = color;
}
}
#endregion
}
}
그리고 이를 AfterImage Controller를 통해서 시작 시 미리 풀링되어있는 오브젝트 풀에서 각각 활성화를 시켜줄 수 있도록 진행을 해주었다.
public void StartEffect()
{
AfterImageContainer.SetActive(true);
for (int i = 0; i < MaxAfterImage; i++)
{
renderers[i].size = tragetSpriteRenderer.size;
afterImages[i].Init(duration, gameObject.transform, renderers[i], tragetSpriteRenderer.size);
}
}
void Update()
{
if (AfterImageContainer.activeSelf)
{
timef += Time.deltaTime;
if (timef >= AfterImageInterval)
{
timef = 0;
for (int i = 0; i < MaxAfterImage; i++)
{
if (afterImages[i].Available())
{
afterImages[i].Init(duration, gameObject.transform, renderers[i], tragetSpriteRenderer.size);
break;
}
}
}
}
}
위와같이 구현이 진행되어 영역이 합쳐질 때, 잔상은 나타나나 몇가지 문제가 남아있다.
1. 잔상의 간격이 일정하지 않다.
2. 잔상이 단지 일정간격마다 껐다가 켜지는 현상으로 진행하여 일정 easing함수를 통해 잔상의 간격이나 속도를 조절하고싶을 때, 커스텀이 되지 않아 어려운 점이 있다.
그렇다면 이 현상을 해결하기 위해 다음 해결책을 제시하였다.
- 현재 나타나는 문제는 잔상에서 계산하는 시간과, 따로 연출을 진행하는 코루틴(위에서 따로 진행하는 코루틴)에서 시간계산을 각각 진행하기 때문에
시간을 연출 코루틴 하나에서만 담당하도록 진행하고, 해당 코루틴이 계산하는 특정 시간대에 잔상하나를 만들도록 함수를 구성해주었다.
따라서 AfterImageController에서는 다음과 같은 함수를 추가해주었다.
/// <summary>
/// 단일 잔상 생성함수
/// 잔상 풀 내에서 비활성화 되어있는 잔상을 하나 선택하여 나타나도록 한다.
/// </summary>
/// <param name="durtiona">단일잔상 지속시간</param>
/// <param name="pos">잔상 위치</param>
/// <param name="OpacityRatio">투명도 계수</param>
public void ShowGhost(float durtiona, Vector3 pos, float OpacityRatio)
{
AfterImageContainer.SetActive(true);
for (int i = 0; i < MaxAfterImage; i++)
{
if (afterImages[i].Available())
{
renderers[i].size = tragetSpriteRenderer.size;
afterImages[i].Init(durtiona, pos, renderers[i], tragetSpriteRenderer.size, OpacityRatio);
break;
}
}
}
따라서 제작한 AfterImage와 AfterImageController를 이용해서 위에서 새로 만든 연출 코루틴을 구성하면 다음과 같다. 이후작성진행중
while (t < zoneTranslateTime){
gameObject.transform.position = Vector3.Lerp(orginTransform, MainZonePos, t / zoneTranslateTime);
_spriteRenderer.size = Vector2.Lerp(originRenderer, MainZoneSize, t / zoneTranslateTime);
if (interval >= AfterImageInterval){
afterImageController.ShowGhost(AfterImageLength, _spriteRenderer.transform.position, AfterImageOpacity);
}
}
위와같은 방식으로 진행하면, 설정한 interval마다 잔상을 생성할 수 있다.
다만, 이때, time을 나타내는 t의 갱신은 각 프레임간 시간인 Time.deltatime을 기반으로 업데이트하고있다.
이를 자세히 생각해보면, frame별로 잔상을 생성할지, 말지에 대한 여부가 결정되는데, 이렇게되면 frame rate에따라 잔상의 모습이 일정하지 않다는 점이 나오게 된다.

이를 그림으로 나타내면 위와같다.
파란색 interval t는 frame rate보다 간격이 커서 잔상 생성에 크게 문제가 되지않는다,
즉 손실이 일어나더라도 일정 간격의 손실이지, 생성되어야할 잔상의 누락이 되거나 하지 않는다,
초록색 interval t는 frame rate보다 간격이 좁아서 생성되어야할 잔상이 생기지 않게된다,
그림에서 확인해보면 각 frame간격사이에 interval이 2개 있으므로, 원래 의도한 잔상의 1/2만큼만 잔상이 보이게 되는것이다.
즉, 정리하면 다음과 같다.
파란색 t -> 잔상위치손실O / 잔상양의손실X
초록색 t -> 잔상위치손실O / 잔상양의손실O
이것을 영상으로 확인해보면 다음과같다.
이를 해결하기 위해서는 다음 2가지 해결법을 도입할 수 있다.
1. interval이 누락이 되었을때를 판별, 그리고 누락이 누적된 만큼 잔상을 추가 생성 (fixed update와 작동원리가 같음)
2. interval이 지난 시점때의 영역위치에 잔상생성을 하지 않고(frame에 영향을 받기때문), Lerp함수를 이용하여 미리 정해진 위치에 잔상을 생성하도록 변경
afterImageController.ShowGhost(AfterImageLength, _spriteRenderer.transform.position, AfterImageOpacity);
이 함수를
afterImageController.ShowGhost(0.1f, Vector3.Lerp(orginTransform, MainZonePos, (0.01f * count) / 0.2f));
이것과 같이 위치정보를 Lerp로 이용하여 변경하기
//이후작성 진행중
'개발 > Unity' 카테고리의 다른 글
| [Unity] Merge-Is-Mine 범용적인 팝업시스템 구성 (0) | 2025.10.07 |
|---|---|
| [Unity] Merge-Is-Mine 핵심기능 리팩토링, 비주얼 개선 (0) | 2025.09.02 |
| [Unity] 프로젝트 소스코드 관련 간단 검토자료 (0) | 2025.08.13 |
| [Unity] 현재 프로젝트문제점, 개선고려점 정리 (1) | 2025.08.13 |
| [Unity] Merge-Is-Mine 맵 생성 시스템 (0) | 2025.08.03 |