일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- MAC
- polymorphism
- STCF
- AINCAA
- DSP
- information hiding
- MLFQ
- OSI 7계층
- DP
- 배경 그림
- FIFO
- 게임 개발
- stride
- 유니티
- 게임개발
- OWASP
- SJF
- Unity #Indie Game
- frequency-domain spectrum analysis
- Waterfall
- 메카님
- Security
- link layer
- unity
- 운영체제
- 컴퓨터 네트워크
- Trap
- protection
- 유스케이스
- SDLC
- Today
- Total
다양한 기록
Parrying Sword #29 : [기획][프로그래밍] 물 구현 및 최적화 본문
// 물을 구현하게 된 사유 및 일기
최근 몬스터 헌터 3G를 하였다.
그 게임에 수중전이라는 개념이 나오는데, 물 속에서 전투를 하는 컨셉이다.
많은 비판을 받는 요소이긴 하지만, 그래도 나름 감성이 있어 마음에 들었기에 물을 구현을 해보려고 했다.
그리고 마땅히 마음에 드는 방법이 안 떠올라서 진행이 막혔다.
사실 화산 지대도 만들다 말았기에 먼저 화산 지대를 하면 됐지만,
물 구현이 자꾸 마음에 걸려 코딩 테스트 연습 문제를 풀거나 하면서 다른 걸 했다. 결국 거의 한 달을 진행을 안했다.
그러던 중, 고등학교 때의 친구에게 전화가 왔다. 뭘 하고 있느냐 물었는데, 하필 게임 중이라 게임 중이라고 답을 했더니
만든다던 게임은 관둔 거냐고 물어봐서 슬슬 다시 할 때가 되어, 다시 진행하였다.
/////////
처음에는 지형을 폭탄으로 터트리면 물이 떨어지는 등, 물 덩어리를 여러개 만들고 겹쳐놔서
다른 지형 지물과 상호 작용이 가능하도록 만들고 싶었습니다.
가능은 할 것 같았는데, 아무리 생각해도 퀄리티가 높아지면 높아질수록
그만큼 많은 물 덩어리를 작게, 그리고 많이 만들어야 하니 리소스를 너무 크게 잡아 먹을 것이 분명하였습니다.
여기서 결국 타협하고, Sprite Shape Controller를 사용하여 물을 구현하기로 했습니다.
Sprite Shape Controller로 물 사이에 많은 점들을 만들고, 물결이 흔들리는 것을 각 지점을 이용해서 구현하는 계획입니다.
물 안에서의 움직임은 컬라이더 내부에 있으면 속도와 점프력을 낮추면 됩니다.
첫번째 결과입니다. 물결을 Sin 함수로 계산했습니다.
일단 물체를 만들고, 그걸 쭉 길게 만들었습니다. 문제는 프레임이 너무 요동칩니다.
for 루프에서 분기예측 하다가 문제가 생긴 건지 아니면 물 움직임을 스프라이트로 표현하다가 문제가 생긴 건지는 모르지만,
해당 부분을 고쳐야 함은 분명합니다.
두번째 결과입니다. 아직 최적화가 적용된 버전은 아니고,
가만히 있을 때 잔물결이 있으면 더 예쁠까 싶어 적용시켜본 것입니다.
별로 예쁘지도 않고, 프레임이 요동치는 수준이 아니라 그냥 심하게 떨어집니다.
위 영상에는 그래도 프레임이 괜찮아 보일지 몰라도, 좀 더 길어지면 10, 20프레임까지 떨어져서 도저히 안됩니다.
세번째입니다. 최적화를 했습니다.
물이 가로로 길어지면 하나의 오브젝트를 길게 늘려 for 문이 탐색해야 할 지점들의 숫자가 너무 많아지는 것이 문제라고 파악했습니다.
그래서, 물 오브젝트를 여러 개 만들고 옆에다 가져다 놔서 하나인 것처럼 눈속임을 하기로 했습니다.
위의 두 영상에 나온 것보다 더 길게 물을 만들었는데도 더 높은 프레임을 보여줍니다.
실제로는 10개를 이어 붙인 모습입니다.
이전 방식 대로는 하나의 for 문에서 100번의 루프를 하니 분기예측이 중간에 어긋나면 크게 문제가 생길 것이지만,
이렇게 10개로 나눠버리면 하나의 물 오브젝트 당 10개의 부담만 하면 됩니다.
세로로는 그냥 길게 만들어도 문제 없습니다.
플레이어가 빠진 곳을 기준으로 근처만 조금 물이 흔들리도록 코드를 짰지만,
그럼에도 하나의 오브젝트로 처리하면 흔들리지 않는 곳이 많아도 영향을 받을 수 밖에 없습니다.
이렇게 오브젝트를 나눠버려 for 문의 최대 크기를 줄이면 나머지 오브젝트는 멀쩡히 분기예측이 진행이 될 것이라 파악됩니다.
물론 분기예측 문제가 아닐 수도 있으나,
해당 부분이 문제일 것이라 파악하고 이를 위한 해결 방안을 적용하여 크게 개선되었습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
public class LiquidController : MonoBehaviour
{
private SpriteShapeController spriteShapeController;
private Spline spline;
private GameObject player;
private PlayerController playerCtrl;
private Rigidbody2D playerRB;
private BoxCollider2D col;
private float orgMovingWeight;
private float orgGravity;
private float width, height;
private int waterLineCount;
private float[] wave;
private int length;
private float waveConstant = 50.0f;
private int interval = 1;
public bool onWaterLine = false;
public float waveWegiht = 1.5f;
public float density = 1.65f;
public GameObject heightObj, widthObj;
private void Awake()
{
col = GetComponent<BoxCollider2D>();
player = GameObject.Find("Player");
playerCtrl = player.GetComponent<PlayerController>();
playerRB = player.GetComponent<Rigidbody2D>();
spriteShapeController = GetComponent<SpriteShapeController>();
spline = spriteShapeController.spline;
orgMovingWeight = playerCtrl.movingWeight;
orgGravity = playerRB.gravityScale;
width = widthObj.transform.position.x - transform.position.x;
height = heightObj.transform.position.y - transform.position.y;
waterLineCount = 0;
spline.Clear();
Vector3 pos = new Vector3(0, height, 0);
spline.InsertPointAt(0, pos);
// 1번 ~ waterLineCount 까지 중간 지점
for( int i = 1; i * interval < width; i++ )
{
waterLineCount++;
pos = new Vector3(i * interval, height, 0);
spline.InsertPointAt(i, pos);
}
pos = new Vector3(width, height, 0);
spline.InsertPointAt(waterLineCount + 1, pos);
pos = new Vector3(width, 0, 0);
spline.InsertPointAt(waterLineCount + 2, pos);
pos = new Vector3(0, 0, 0);
spline.InsertPointAt(waterLineCount + 3, pos);
length = waterLineCount + 1;
col.offset = new Vector2(width / 2.0f, height / 2.0f);
col.size = new Vector2(width, height);
wave = new float[waterLineCount + 2];
length = waterLineCount + 2;
for( int i = 0; i < length; i++ )
{
wave[i] = 0.0f;
}
spline.SetHeight(0, 0.1f);
spline.SetHeight(waterLineCount + 2, 0.1f);
}
private void Update()
{
if (!onWaterLine) return;
for( int i = 0; i < length - 1; i++ )
{
Vector3 pos = new Vector3(i * interval, height + wave[i] * Mathf.Sin(i * waveConstant / 150), 0.0f);
spline.SetPosition(i, pos);
wave[i] = wave[i] / 1.03f;
if( wave[i] < 0.15f )
{
//wave[i] = 0.149f;
wave[i] = 0.0f;
}
}
if( waveConstant < 360.0f || waveConstant > 0.0f)
{
waveConstant = (waveConstant + 1.4f * interval);
}
else
{
waveConstant = (waveConstant - 1.4f * interval);
}
}
private void waveUpdate(int index, float weight, int direction) // direction = 0 > 시작 -1 왼쪽 1 오른쪽
{
if (index < 0 || index >= length) return;
if (weight < 0.1f) return;
wave[index] = weight;
if ( direction != 1 )
{
waveUpdate(index - 1, weight / ( 2.0f * interval ), -1);
}
if( direction != -1 )
{
waveUpdate(index + 1, weight / ( 2.0f * interval ) , 1);
}
}
private void makeWave()
{
int index = (int)(player.transform.position.x - transform.position.x) / interval;
if (index < 0 || index >= length) return;
waveUpdate(index, waveWegiht, 0);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag != "PlayerBody") return;
if( onWaterLine & !playerCtrl.grounded )
{
makeWave();
}
}
private void OnTriggerStay2D(Collider2D collision)
{
if (collision.tag != "PlayerBody") return;
playerRB.velocity = new Vector2(playerRB.velocity.x, Mathf.Clamp(playerRB.velocity.y, -20.0f, 40.0f));
playerCtrl.movingWeight = orgMovingWeight / density;
playerRB.gravityScale = orgGravity / 5.0f;
playerCtrl.weakJump(true);
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag != "PlayerBody") return;
playerCtrl.movingWeight = orgMovingWeight;
playerRB.gravityScale = orgGravity;
playerCtrl.weakJump(false);
if (onWaterLine && player.transform.position.y > transform.position.y + height * transform.localScale.y)
{
makeWave();
}
}
}
코드가 좀 지저분하여 나중에 수정할 가능성이 크지만, 일단 올립니다.
'유니티 엔진 > Parrying Sowrd' 카테고리의 다른 글
Parrying Sword #31 : [아트] 보스몬스터 Scaloot, 걷기 애니메이션 (0) | 2023.11.08 |
---|---|
Parrying Sword #30 : [아트] 화산 지대 적용 완료 및 이미지 자르기 (0) | 2023.06.28 |
Parrying Sword #28 : [아트] 화산 지대 (0) | 2023.05.25 |
Parrying Sword #27 : [기획] 기획 변경 (0) | 2023.05.19 |
Parrying Sword #26 : [기획][프로그래밍] 화면 가장자리에 나침반 화살표 만들기 (0) | 2023.05.18 |