diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a7ae914..af9461e 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -462,3 +462,10 @@ F11이나 메뉴로 창을 화면 전체로 키운다. 그냥 창만 키우면 빠른 움직임은 몇 분만 지나도 산만해진다. 물고기가 벗어나는 각도도 6도 남짓으로 좁혀 두었다 -- 실제로 물고기가 수직으로 오르내리는 일은 드물다. 대신 열 번에 한 번쯤은 크게 틀어 수심을 바꾸게 해서, 모두가 제 높이에 줄지어 있지 않도록 했다. + +한동안은 "몇 초 동안 나아간다"로 잡았는데, 시간이 좁은 범위 안에서만 흔들리고 +속도가 고정이라 결국 매번 비슷한 거리를 갔다. 짜인 것처럼 보였다. 지금은 나아갈 +때마다 **거리와 속도를 각각 새로 뽑는다**. 거리는 층 높이의 0.3~6배(대략 1~22칸) +사이에서 뽑되 짧은 쪽으로 치우치게 해서(`pow(randf(), 1.6)`), 몇 칸만 가는 일이 +잦고 화면을 가로지르는 일은 가끔 있게 했다. 방향도 좌우 어느 쪽인지 매번 새로 +던진다 -- 가던 쪽을 이어 가게 했더니 오히려 한 방향으로 흐르는 것처럼 보였다. diff --git a/scenes/aquarium/tile_item.gd b/scenes/aquarium/tile_item.gd index c870e82..3834bd2 100644 --- a/scenes/aquarium/tile_item.gd +++ b/scenes/aquarium/tile_item.gd @@ -37,15 +37,22 @@ var _desired := 0.0 ## 기본으로 삼는 축. 0이면 좌우, 1이면 위아래. var _vertical := 0.0 var _spread := 0.35 -## 나아가는/쉬는 시간의 (최소, 최대). -var _cruise := Vector2(1.6, 3.8) -var _rest := Vector2(0.25, 1.1) -var _rest_odds := 0.5 +## 기본 축을 벗어난 쪽(물고기라면 위아래)으로 갈 확률. +var _off_odds := 0.15 +## 한 번에 나아가는 거리의 (최소, 최대). 층 높이의 배수. +var _leg := Vector2(0.3, 6.0) +## 쉬는 시간의 (최소, 최대). +var _rest := Vector2(0.8, 6.0) +var _rest_odds := 0.6 var _accel := 2.5 var _turn_rate := 2.2 -## 지금 쉬는 중인지, 그리고 다음 전환까지 남은 시간. +## 지금 쉬는 중인지, 그리고 얼마나 더 쉬는지. var _resting := false -var _move_timer := 0.0 +var _rest_left := 0.0 +## 이번에 갈 거리, 지금까지 온 거리, 이번만의 속도 배수. +var _leg_dist := 0.0 +var _travelled := 0.0 +var _leg_speed := 1.0 ## 0에서 1로 오가는 속도 배수. 서서히 붙고 서서히 죽는다. var _throttle := 0.0 @@ -108,17 +115,19 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void: var h := _roam.size.y # 종류별 성격. - # vertical: 0이면 좌우로, 1이면 위아래로 다니는 것을 기본으로 삼는다. - # spread : 그 축에서 얼마나 벗어날 수 있는지(라디안). - # rest_odds: 한 번 나아간 뒤 쉴 확률. + # vertical : 0이면 좌우, 1이면 위아래를 기본 축으로 삼는다. + # off_odds : 그 축을 벗어난 쪽으로 갈 확률. + # leg : 한 번에 나아가는 거리의 (최소, 최대). 층 높이의 배수다. + # rest : 쉬는 시간의 (최소, 최대). match shape: "crab": - # 바닥을 짚고 좌우로만. 자주, 오래 멈춰 선다. + # 바닥을 짚고 좌우로만. 몇 칸 종종거리다 오래 멈춰 선다. _speed = h * 0.10 _vertical = 0.0 - _spread = 0.04 - _cruise = Vector2(1.2, 3.0) - _rest = Vector2(1.5, 4.5) + _off_odds = 0.04 + _spread = 0.05 + _leg = Vector2(0.2, 2.5) + _rest = Vector2(1.0, 6.0) _rest_odds = 0.8 _turn_rate = 1.6 _accel = 3.0 @@ -127,9 +136,10 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void: # 꼬리를 감고 선 채로 아주 천천히 오르내린다. _speed = h * 0.07 _vertical = 1.0 - _spread = 0.35 - _cruise = Vector2(1.5, 3.5) - _rest = Vector2(2.5, 6.0) + _off_odds = 0.3 + _spread = 0.3 + _leg = Vector2(0.15, 1.5) + _rest = Vector2(1.5, 7.0) _rest_odds = 0.75 _turn_rate = 1.0 _accel = 1.5 @@ -138,9 +148,10 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void: # 거의 멈춰 있다가 짧게 튄다. 튀는 순간만 빠르다. _speed = h * 0.35 _vertical = 0.0 + _off_odds = 0.15 _spread = 0.15 - _cruise = Vector2(0.2, 0.5) - _rest = Vector2(2.0, 5.0) + _leg = Vector2(0.2, 1.2) + _rest = Vector2(1.5, 6.0) _rest_odds = 0.9 _turn_rate = 4.0 _accel = 10.0 @@ -149,22 +160,24 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void: # 해파리는 물살에 밀리듯 길게 흐르고 가끔 멎는다. _speed = h * 0.07 _vertical = 1.0 + _off_odds = 0.35 _spread = 0.5 - _cruise = Vector2(3.5, 7.0) - _rest = Vector2(2.0, 5.0) + _leg = Vector2(0.5, 4.0) + _rest = Vector2(1.0, 5.0) _rest_odds = 0.45 _turn_rate = 0.6 _accel = 0.9 _band = Vector2(0.05, 0.78) _: # 물고기는 거의 좌우로만 다닌다. spread가 0.1이면 6도쯤 -- - # 헤엄치는 동안 수심이 눈에 띄게 바뀌지 않을 만큼이다. - _speed = h * 0.15 + # 한 번 나아가는 동안 수심이 눈에 띄게 바뀌지 않을 만큼이다. + _speed = h * 0.16 _vertical = 0.0 + _off_odds = 0.15 _spread = 0.1 - _cruise = Vector2(2.5, 6.0) - _rest = Vector2(1.5, 4.5) - _rest_odds = 0.65 + _leg = Vector2(0.3, 6.0) + _rest = Vector2(0.8, 6.0) + _rest_odds = 0.6 _turn_rate = 1.2 _accel = 1.4 _band = Vector2(0.1, 0.95) @@ -172,10 +185,16 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void: # 돌아다니는 것이 큰 움직임을 맡으므로 제자리 흔들림은 줄인다. _swim_range *= 0.22 _speed *= rng.randf_range(0.82, 1.2) - _heading = _axis_angle() + (PI if rng.randf() < 0.5 else 0.0) + _heading = _new_heading() _desired = _heading - # 시작 시각을 흩어 놓는다. 안 그러면 모두 같은 박자로 멈추고 나아간다. - _move_timer = rng.randf_range(0.0, _cruise.y) + + # 시작을 흩어 놓는다. 안 그러면 모두 같은 박자로 멈추고 나아간다. + if rng.randf() < 0.5: + _start_rest() + _rest_left *= rng.randf() + else: + _start_leg() + _travelled = _leg_dist * rng.randf() _apply_depth() @@ -183,11 +202,6 @@ func is_roaming() -> bool: return _roaming and roam_enabled and _roam.size.x > 1.0 -## 이 종류가 기본으로 삼는 축. -func _axis_angle() -> float: - return PI * 0.5 if _vertical > 0.5 else 0.0 - - ## 헤엄칠 수 있는 세로 범위. 종류마다 물속에서 노는 높이가 다르다. func _band_range() -> Vector2: return Vector2( @@ -195,79 +209,90 @@ func _band_range() -> Vector2: _roam.position.y + _roam.size.y * _band.y) -## 다음에 나아갈 방향. 가던 쪽을 웬만하면 유지한다 -- -## 매번 새로 뽑으면 제자리에서 좌우로 왔다 갔다 하는 것처럼 보인다. +## 다음에 나아갈 방향. 좌우든 위아래든 매번 새로 던진다. +## 가던 쪽을 이어 가게 해 봤더니 오히려 한 방향으로 흐르는 것처럼 보였다. func _new_heading() -> float: - var axis := _axis_angle() - var forward: float = cos(_heading) if _vertical < 0.5 else sin(_heading) - var same_way := forward >= 0.0 - if randf() < 0.3: - same_way = not same_way + var vertical := _vertical > 0.5 + if randf() < _off_odds: + vertical = not vertical + var axis: float = PI * 0.5 if vertical else 0.0 + var back: float = PI if randf() < 0.5 else 0.0 + return axis + back + randf_range(-_spread, _spread) - # 가끔은 크게 틀어 수심을 바꾼다. 늘 제 높이만 오가면 줄지어 다니는 것처럼 - # 보이므로, 열 번에 한 번쯤은 비스듬히 올라가거나 내려가게 둔다. - var spread := _spread * (5.0 if randf() < 0.12 else 1.0) - return axis + (0.0 if same_way else PI) + randf_range(-spread, spread) + +## 새로 나아가기 시작한다. 거리도 속도도 매번 새로 뽑는다 -- +## 한 번은 화면을 가로지르고 다음엔 몇 칸만 가야 짜인 것처럼 보이지 않는다. +func _start_leg() -> void: + _resting = false + _travelled = 0.0 + # 짧은 이동이 더 잦고 긴 이동은 가끔. 고르게 뽑으면 늘 멀리까지만 간다. + var t := pow(randf(), 1.6) + _leg_dist = _roam.size.y * lerpf(_leg.x, _leg.y, t) + _leg_speed = randf_range(0.6, 1.5) + _desired = _new_heading() + + +func _start_rest() -> void: + _resting = true + _rest_left = randf_range(_rest.x, _rest.y) func _roam_step(delta: float) -> void: - _move_timer -= delta - if _move_timer <= 0.0: - if _resting: - _resting = false - _move_timer = randf_range(_cruise.x, _cruise.y) - _desired = _new_heading() + if _resting: + _rest_left -= delta + if _rest_left <= 0.0: + _start_leg() + elif _travelled >= _leg_dist: + if randf() < _rest_odds: + _start_rest() else: - _resting = randf() < _rest_odds - if _resting: - _move_timer = randf_range(_rest.x, _rest.y) - else: - # 쉬지 않고 이어 갈 때도 방향은 새로 잡는다. - _move_timer = randf_range(_cruise.x, _cruise.y) - _desired = _new_heading() + _start_leg() - # 벽이 가까우면 안쪽으로 돌아선다. 부딪혀 튕기면 위아래로 통통 튀는 것처럼 보인다. var band := _band_range() - var mx := _roam.size.y * 0.35 - var my := (band.y - band.x) * 0.18 - var away := Vector2.ZERO - if position.x < _roam.position.x + mx: - away.x += 1.0 - elif position.x > _roam.end.x - mx: - away.x -= 1.0 - if position.y < band.x + my: - away.y += 1.0 - elif position.y > band.y - my: - away.y -= 1.0 - if away != Vector2.ZERO: - # 좌우로 다니는 것은 위아래 벽에서도 곧장 꺾지 않는다. 가던 쪽으로 - # 나아가면서 비스듬히 벗어난다 -- 수직으로 솟구치면 물고기 같지 않다. - if _vertical < 0.5 and is_zero_approx(away.x): - away.x = signf(cos(_heading)) - if is_zero_approx(away.x): - away.x = 1.0 - away.y *= 0.45 - elif _vertical > 0.5 and is_zero_approx(away.y): - away.y = signf(sin(_heading)) - if is_zero_approx(away.y): - away.y = 1.0 - away.x *= 0.45 - _desired = away.angle() - _resting = false - # 목표 각도로 부드럽게 돌아선다. + # 벽이 가까우면 안쪽으로 돌아선다. 부딪혀 튕기면 통통 튀는 것처럼 보인다. + # 쉬는 중이라면 어차피 벗어날 일이 없으니 그냥 둔다. + if not _resting: + var mx := _roam.size.y * 0.35 + var my := (band.y - band.x) * 0.18 + var away := Vector2.ZERO + if position.x < _roam.position.x + mx: + away.x += 1.0 + elif position.x > _roam.end.x - mx: + away.x -= 1.0 + if position.y < band.x + my: + away.y += 1.0 + elif position.y > band.y - my: + away.y -= 1.0 + if away != Vector2.ZERO: + # 좌우로 다니는 것은 위아래 벽에서도 곧장 꺾지 않는다. 가던 쪽으로 + # 나아가면서 비스듬히 벗어난다 -- 수직으로 솟구치면 물고기 같지 않다. + if _vertical < 0.5 and is_zero_approx(away.x): + away.x = signf(cos(_heading)) + if is_zero_approx(away.x): + away.x = 1.0 + away.y *= 0.45 + elif _vertical > 0.5 and is_zero_approx(away.y): + away.y = signf(sin(_heading)) + if is_zero_approx(away.y): + away.y = 1.0 + away.x *= 0.45 + _desired = away.angle() + # 벽을 따라 하염없이 미끄러지지 않도록 이번 이동을 곧 끝낸다. + _leg_dist = minf(_leg_dist, _travelled + _roam.size.y * 0.8) + + # 목표 각도로 부드럽게 돌아서고, 속도는 서서히 붙고 서서히 죽는다. _heading = rotate_toward(_heading, _desired, _turn_rate * delta) - - # 속도는 서서히 붙고 서서히 죽는다. 딱 멈추고 딱 출발하면 기계처럼 보인다. _throttle = move_toward(_throttle, 0.0 if _resting else 1.0, _accel * delta) if _throttle > 0.001: - var step := Vector2.RIGHT.rotated(_heading) * _speed * _throttle * delta + var step := Vector2.RIGHT.rotated(_heading) * _speed * _leg_speed * _throttle * delta position += step # 제 범위를 벗어나지 않게 잡아 둔다. 위 steering이 대부분 막아 주고, # 이건 크기가 갑자기 바뀌었을 때를 위한 마지막 그물이다. position.x = clampf(position.x, _roam.position.x, _roam.end.x) position.y = clampf(position.y, band.x, band.y) + _travelled += step.length() if absf(step.x) > 0.02: _facing = signf(step.x)