헤엄치는 움직임에 쉬는 틈과 방향 전환을
매 틀 각도를 무작위로 흔들던 것을 나아감/멈춤 두 상태로 바꿨다. 속도가 늘 일정해 쉬는 틈이 없었고, 층이 가로로 길고 세로로 짧아 무작위 방향이 위아래 벽에 계속 부딪히며 튀었다. - 종류마다 기본 축(물고기·게는 좌우, 해마·해파리는 위아래)에서 조금만 벗어나게 - 나아갈 때마다 목표 각도를 정하고 rotate_toward로 부드럽게 돌아서게 - 속도를 서서히 붙이고 죽여서 멈추고 출발하는 것이 기계처럼 보이지 않게 - 벽 앞에서 튕기는 대신 미리 안쪽으로 돌아서게 - 새우의 튀어 나가는 동작은 짧은 CRUISE + 긴 REST로 흡수 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -446,3 +446,14 @@ F11이나 메뉴로 창을 화면 전체로 키운다. 그냥 창만 키우면
|
||||
|
||||
**앞뒤 단서**: 화면 위쪽(뒷줄)일수록 조금 작고 흐리게 그린다(`_apply_depth`).
|
||||
3/4 시점에서 깊이를 읽게 해주는 유일한 신호라 없으면 평면처럼 보인다.
|
||||
|
||||
### 헤엄치는 리듬
|
||||
|
||||
돌아다니는 생물은 두 상태를 오간다 — 한동안 나아가다가(CRUISE) 잠시 멈춘다(REST).
|
||||
방향은 매 틀 흔들지 않고, 나아갈 때마다 목표 각도를 하나 정해 그쪽으로 부드럽게 돌아선다.
|
||||
|
||||
매 틀 각도를 무작위로 흔드는 방식을 먼저 써 봤는데 두 가지가 어색했다. 하나는
|
||||
속도가 늘 일정해서 쉬는 틈이 없었고, 다른 하나는 층이 가로로 아주 길고 세로로
|
||||
짧다 보니 무작위 방향이 위아래 벽에 계속 부딪혀 통통 튀는 것처럼 보였다.
|
||||
그래서 종류마다 기본으로 삼는 축(물고기·게는 좌우, 해마·해파리는 위아래)을 두고
|
||||
거기서 조금만 벗어나게 했고, 벽 앞에서는 튕기는 대신 미리 안쪽으로 돌아서게 했다.
|
||||
|
||||
+141
-67
@@ -30,14 +30,24 @@ var roam_enabled := true
|
||||
var _roaming := false
|
||||
var _roam := Rect2()
|
||||
var _speed := 0.0
|
||||
var _turn := 0.0
|
||||
## 헤엄치는 세로 범위를 층 높이의 비율로. (위, 아래)
|
||||
var _band := Vector2(0.1, 0.95)
|
||||
var _heading := 0.0
|
||||
## 0보다 크면 가끔 튀어 나간다(새우).
|
||||
var _dart := 0.0
|
||||
var _dart_timer := 0.0
|
||||
var _dart_left := 0.0
|
||||
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 _accel := 2.5
|
||||
var _turn_rate := 2.2
|
||||
## 지금 쉬는 중인지, 그리고 다음 전환까지 남은 시간.
|
||||
var _resting := false
|
||||
var _move_timer := 0.0
|
||||
## 0에서 1로 오가는 속도 배수. 서서히 붙고 서서히 죽는다.
|
||||
var _throttle := 0.0
|
||||
|
||||
var _t := 0.0
|
||||
var _phase := 0.0
|
||||
@@ -85,49 +95,85 @@ func set_area(area: Rect2, cell: Vector2, height_tiles: float) -> void:
|
||||
## 층 전체를 자유롭게 돌아다니게 한다.
|
||||
## 집은 정원을 세는 단위일 뿐이고, 헤엄치는 자리는 층 전체다.
|
||||
##
|
||||
## 종류마다 다니는 방식이 다르다. 물고기는 크게 돌고, 게는 바닥을 짚고 걷고,
|
||||
## 해마는 거의 제자리에서 오르내리고, 새우는 튀었다 멈춘다.
|
||||
## 같은 식으로 움직이면 여러 마리가 한 화면에 있을 때 무늬가 되어 버린다.
|
||||
## 움직임은 두 상태를 오간다 — 한동안 나아가다가(CRUISE) 잠시 멈춘다(REST).
|
||||
## 실제 물고기가 그렇고, 무엇보다 쉬는 틈이 있어야 여러 마리가 한 화면에 있을 때
|
||||
## 전부 같은 리듬으로 흐르지 않는다.
|
||||
##
|
||||
## 방향은 매 틀 흔들지 않는다. 나아갈 때마다 목표 각도를 하나 정하고 그쪽으로
|
||||
## 부드럽게 돌아선다. 매 틀 흔들면 술 취한 것처럼 떨리고, 층이 가로로 길고
|
||||
## 세로로 짧아서 위아래 벽에 계속 부딪히며 통통 튀게 된다.
|
||||
func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_roam = area
|
||||
_roaming = true
|
||||
# 종류별 성격. speed는 초당 픽셀, turn은 초당 라디안.
|
||||
var h := _roam.size.y
|
||||
|
||||
# 종류별 성격.
|
||||
# vertical: 0이면 좌우로, 1이면 위아래로 다니는 것을 기본으로 삼는다.
|
||||
# spread : 그 축에서 얼마나 벗어날 수 있는지(라디안).
|
||||
# rest_odds: 한 번 나아간 뒤 쉴 확률.
|
||||
match shape:
|
||||
"crab":
|
||||
# 바닥을 짚고 좌우로만. 위아래로는 거의 안 움직인다.
|
||||
_speed = _roam.size.y * 0.22
|
||||
_turn = 0.5
|
||||
# 바닥을 짚고 좌우로만. 자주 멈춰 선다.
|
||||
_speed = h * 0.22
|
||||
_vertical = 0.0
|
||||
_spread = 0.06
|
||||
_cruise = Vector2(0.8, 2.0)
|
||||
_rest = Vector2(0.6, 2.2)
|
||||
_rest_odds = 0.75
|
||||
_turn_rate = 3.0
|
||||
_accel = 6.0
|
||||
_band = Vector2(0.62, 1.0)
|
||||
_dart = 0.0
|
||||
"seahorse":
|
||||
# 꼬리를 감고 선 채로 조금씩. 오르내림이 주된 움직임이다.
|
||||
_speed = _roam.size.y * 0.10
|
||||
_turn = 1.6
|
||||
_band = Vector2(0.15, 0.9)
|
||||
_dart = 0.0
|
||||
# 꼬리를 감고 선 채로 오르내린다. 오래 멈춰 있다.
|
||||
_speed = h * 0.13
|
||||
_vertical = 1.0
|
||||
_spread = 0.5
|
||||
_cruise = Vector2(1.0, 2.6)
|
||||
_rest = Vector2(1.0, 3.0)
|
||||
_rest_odds = 0.7
|
||||
_turn_rate = 1.6
|
||||
_accel = 2.0
|
||||
_band = Vector2(0.15, 0.92)
|
||||
"shrimp":
|
||||
# 튀었다 멈춘다. 평소 속도는 느리고 가끔 확 나아간다.
|
||||
_speed = _roam.size.y * 0.16
|
||||
_turn = 1.1
|
||||
# 거의 멈춰 있다가 짧게 튄다.
|
||||
_speed = h * 0.6
|
||||
_vertical = 0.0
|
||||
_spread = 0.5
|
||||
_cruise = Vector2(0.15, 0.4)
|
||||
_rest = Vector2(0.9, 2.6)
|
||||
_rest_odds = 0.9
|
||||
_turn_rate = 5.0
|
||||
_accel = 12.0
|
||||
_band = Vector2(0.5, 1.0)
|
||||
_dart = 1.0
|
||||
"glow":
|
||||
# 해파리는 물살에 밀리듯 아주 천천히.
|
||||
_speed = _roam.size.y * 0.13
|
||||
_turn = 0.35
|
||||
_band = Vector2(0.05, 0.75)
|
||||
_dart = 0.0
|
||||
# 해파리는 물살에 밀리듯 길게 흐르고 가끔 멎는다.
|
||||
_speed = h * 0.14
|
||||
_vertical = 1.0
|
||||
_spread = 0.7
|
||||
_cruise = Vector2(2.5, 5.0)
|
||||
_rest = Vector2(0.8, 2.0)
|
||||
_rest_odds = 0.4
|
||||
_turn_rate = 0.8
|
||||
_accel = 1.2
|
||||
_band = Vector2(0.05, 0.78)
|
||||
_:
|
||||
_speed = _roam.size.y * 0.38
|
||||
_turn = 0.8
|
||||
_speed = h * 0.4
|
||||
_vertical = 0.0
|
||||
_spread = 0.35
|
||||
_cruise = Vector2(1.6, 3.8)
|
||||
_rest = Vector2(0.25, 1.1)
|
||||
_rest_odds = 0.55
|
||||
_turn_rate = 2.2
|
||||
_accel = 2.5
|
||||
_band = Vector2(0.1, 0.95)
|
||||
_dart = 0.0
|
||||
|
||||
# 돌아다니는 것이 큰 움직임을 맡으므로 제자리 흔들림은 줄인다. 안 그러면 서로 싸운다.
|
||||
# 돌아다니는 것이 큰 움직임을 맡으므로 제자리 흔들림은 줄인다.
|
||||
_swim_range *= 0.3
|
||||
_speed *= rng.randf_range(0.8, 1.25)
|
||||
_heading = rng.randf() * TAU
|
||||
_dart_timer = rng.randf_range(0.5, 3.0)
|
||||
_speed *= rng.randf_range(0.82, 1.2)
|
||||
_heading = _axis_angle() + (PI if rng.randf() < 0.5 else 0.0)
|
||||
_desired = _heading
|
||||
# 시작 시각을 흩어 놓는다. 안 그러면 모두 같은 박자로 멈추고 나아간다.
|
||||
_move_timer = rng.randf_range(0.0, _cruise.y)
|
||||
_apply_depth()
|
||||
|
||||
|
||||
@@ -135,6 +181,11 @@ 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(
|
||||
@@ -142,43 +193,66 @@ 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
|
||||
return axis + (0.0 if same_way else PI) + randf_range(-_spread, _spread)
|
||||
|
||||
|
||||
func _roam_step(delta: float) -> void:
|
||||
# 방향을 조금씩 튼다. 목표를 따로 두지 않고 각도만 흔들어도
|
||||
# 충분히 어슬렁거리는 것처럼 보인다.
|
||||
_heading += (randf() - 0.5) * _turn * delta * 6.0
|
||||
_move_timer -= delta
|
||||
if _move_timer <= 0.0:
|
||||
if _resting:
|
||||
_resting = false
|
||||
_move_timer = randf_range(_cruise.x, _cruise.y)
|
||||
_desired = _new_heading()
|
||||
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()
|
||||
|
||||
# 튀는 종류는 가끔 확 나아간다.
|
||||
var boost := 1.0
|
||||
if _dart > 0.0:
|
||||
_dart_timer -= delta
|
||||
if _dart_timer <= 0.0:
|
||||
_dart_timer = randf_range(1.2, 3.5)
|
||||
_dart_left = 0.35
|
||||
if _dart_left > 0.0:
|
||||
_dart_left -= delta
|
||||
boost = 3.2
|
||||
|
||||
var dir := Vector2.RIGHT.rotated(_heading)
|
||||
# 게는 좌우로만 걷는다. 위아래로 미끄러지면 헤엄치는 것처럼 보인다.
|
||||
if shape == "crab":
|
||||
dir.y *= 0.12
|
||||
elif shape == "seahorse":
|
||||
dir.x *= 0.35
|
||||
var next := position + dir * _speed * boost * delta
|
||||
|
||||
# 테두리에 닿기 전에 미리 돌아선다. 벽에 붙어 비비는 것보다 자연스럽다.
|
||||
var margin := _roam.size.y * 0.12
|
||||
# 벽이 가까우면 안쪽으로 돌아선다. 부딪혀 튕기면 위아래로 통통 튀는 것처럼 보인다.
|
||||
var band := _band_range()
|
||||
if next.x < _roam.position.x + margin or next.x > _roam.end.x - margin:
|
||||
_heading = PI - _heading
|
||||
next.x = clampf(next.x, _roam.position.x + margin, _roam.end.x - margin)
|
||||
if next.y < band.x or next.y > band.y:
|
||||
_heading = -_heading
|
||||
next.y = clampf(next.y, band.x, band.y)
|
||||
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:
|
||||
_desired = away.angle()
|
||||
_resting = false
|
||||
|
||||
# 목표 각도로 부드럽게 돌아선다.
|
||||
_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
|
||||
position += step
|
||||
# 제 범위를 벗어나지 않게 잡아 둔다. 위 steering이 대부분 막아 주고,
|
||||
# 이건 크기가 갑자기 바뀌었을 때를 위한 마지막 그물이다.
|
||||
position.x = clampf(position.x, _roam.position.x, _roam.end.x)
|
||||
position.y = clampf(position.y, band.x, band.y)
|
||||
if absf(step.x) > 0.02:
|
||||
_facing = signf(step.x)
|
||||
|
||||
if absf(next.x - position.x) > 0.02:
|
||||
_facing = signf(next.x - position.x)
|
||||
position = next
|
||||
_apply_depth()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user