나아가는 거리와 속도를 매번 새로 뽑도록

"몇 초 동안 나아간다"로 잡았더니 시간 범위가 좁고 속도가 고정이라 매번 비슷한
거리를 갔다. 짜인 것처럼 보였다.

- 시간이 아니라 거리로 이동을 끊는다. 거리는 층 높이의 0.3~6배(1~22칸)에서
  뽑되 pow(randf(), 1.6)으로 짧은 쪽에 치우치게
- 속도 배수도 이동마다 0.6~1.5배로 새로 뽑는다
- 좌우 어느 쪽으로 갈지도 매번 새로. 가던 쪽을 잇게 했더니 한 방향으로 흘렀다
- 위아래로 가는 것은 off_odds로 따로 굴린다 (물고기 15%, 게 4%)
- 벽에서 돌아설 때는 이번 이동을 곧 끝내서 벽을 따라 미끄러지지 않게

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 15:20:38 +09:00
parent 7c52c34874
commit c9b9145615
2 changed files with 120 additions and 88 deletions
+113 -88
View File
@@ -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)