헤엄치는 것을 바닥에서 띄워 물속에 있게
바닥은 부감, 물고기는 측면이라 물고기가 모래를 기어다니는 것처럼 보였다. 물고기를 설치물과 똑같이 바닥에 붙여 놨으니 당연한 결과였다. - position은 "모래 위 어디", hover는 "거기서 물기둥 얼마나 위"로 분리. 그림자는 바닥에 남고 몸만 뜬다. 이 간격이 물속으로 읽히게 한다 - 물고기 1~2.9칸, 해파리 1.8~3.6칸, 해마 0.5~1.7칸, 게·새우는 거의 0 - 이동을 시작할 때마다 높이를 새로 뽑아 천천히 오르내린다 - 뜰 자리를 위해 헤엄치는 것의 바닥 범위를 앞쪽으로. 뒷줄일수록 덜 뜬다 - 뜬 높이만큼 그림자를 넓고 옅게 draw_set_transform()이 쌓이지 않고 갈아치우는 탓에, 형태 함수가 좌우를 뒤집으며 Vector2.ZERO로 다시 부를 때 hover가 통째로 날아가고 있었다. 오프셋을 _body_offset에 담고 _xform()을 거치도록 고쳤다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,11 @@ class_name TileItem
|
||||
## 바닥은 위에서 내려다보지만 물건은 "서 있는" 모습으로 그린다.
|
||||
## 노드의 원점은 발이 바닥에 닿는 지점(발자국 아래 가운데)이고,
|
||||
## 그림은 거기서 위(-Y)로 자란다. 그래서 키가 큰 것은 뒷줄을 가린다.
|
||||
##
|
||||
## 헤엄치는 것은 여기에 hover가 하나 더 붙는다. 원점은 여전히 "모래 위 어디"를
|
||||
## 가리키고 그림자도 거기 남지만, 몸은 hover만큼 위에 그린다. 이 둘을 나누지
|
||||
## 않으면 -- 즉 물고기를 설치물과 똑같이 바닥에 붙여 놓으면 -- 모래 위를 기어
|
||||
## 다니는 것처럼 보인다. 뜬 높이만큼 몸과 그림자가 벌어져야 물속으로 읽힌다.
|
||||
## 아직 아트 에셋이 없어서 형태별로 직접 그린다.
|
||||
## assets/art/ 에 스프라이트가 생기면 Sprite2D + offset 기반으로 바꾸면 된다.
|
||||
|
||||
@@ -23,7 +28,16 @@ var span := Vector2(34, 32)
|
||||
## 서 있는 높이(픽셀).
|
||||
var body_height := 32.0
|
||||
## 바닥에서 얼마나 떠 있는지(픽셀). 헤엄치는 것만 0보다 크다.
|
||||
## 바닥 위치(position)와는 따로 논다 -- position은 "모래 위 어디"이고
|
||||
## hover는 "그 자리에서 물기둥 얼마나 위". 그림자는 바닥에, 몸은 hover만큼 위에
|
||||
## 그려지므로, 둘 사이가 벌어질수록 물속에 떠 있는 것으로 읽힌다.
|
||||
var hover := 0.0
|
||||
var _cell_h := 26.0
|
||||
## 떠오를 높이의 (최소, 최대). 칸 높이의 배수. 헤엄치는 것만 0이 아니다.
|
||||
var _hover_range := Vector2.ZERO
|
||||
var _hover_goal := 0.0
|
||||
## 이번 틀에 몸을 그릴 기준 오프셋. _xform()이 여기에 얹는다.
|
||||
var _body_offset := Vector2.ZERO
|
||||
|
||||
## 헤엄치는 것만 돌아다닌다. 끌고 있는 동안에는 멈춘다.
|
||||
var roam_enabled := true
|
||||
@@ -83,6 +97,7 @@ func setup(item_data: Dictionary, kind: String, index: int, area: Rect2, cell: V
|
||||
func set_area(area: Rect2, cell: Vector2, height_tiles: float) -> void:
|
||||
span = area.size
|
||||
var cell_h := maxf(cell.y, 1.0)
|
||||
_cell_h = cell_h
|
||||
body_height = cell_h * height_tiles
|
||||
position = Vector2(area.position.x + area.size.x * 0.5, area.end.y)
|
||||
|
||||
@@ -132,6 +147,7 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_turn_rate = 1.6
|
||||
_accel = 3.0
|
||||
_band = Vector2(0.62, 1.0)
|
||||
_hover_range = Vector2(0.0, 0.05)
|
||||
"seahorse":
|
||||
# 꼬리를 감고 선 채로 아주 천천히 오르내린다.
|
||||
_speed = h * 0.07
|
||||
@@ -143,7 +159,8 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_rest_odds = 0.75
|
||||
_turn_rate = 1.0
|
||||
_accel = 1.5
|
||||
_band = Vector2(0.15, 0.92)
|
||||
_band = Vector2(0.55, 0.96)
|
||||
_hover_range = Vector2(0.5, 1.7)
|
||||
"shrimp":
|
||||
# 거의 멈춰 있다가 짧게 튄다. 튀는 순간만 빠르다.
|
||||
_speed = h * 0.35
|
||||
@@ -155,7 +172,8 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_rest_odds = 0.9
|
||||
_turn_rate = 4.0
|
||||
_accel = 10.0
|
||||
_band = Vector2(0.5, 1.0)
|
||||
_band = Vector2(0.55, 1.0)
|
||||
_hover_range = Vector2(0.0, 0.35)
|
||||
"glow":
|
||||
# 해파리는 물살에 밀리듯 길게 흐르고 가끔 멎는다.
|
||||
_speed = h * 0.07
|
||||
@@ -167,7 +185,8 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_rest_odds = 0.45
|
||||
_turn_rate = 0.6
|
||||
_accel = 0.9
|
||||
_band = Vector2(0.05, 0.78)
|
||||
_band = Vector2(0.42, 0.86)
|
||||
_hover_range = Vector2(1.8, 3.6)
|
||||
_:
|
||||
# 물고기는 거의 좌우로만 다닌다. spread가 0.1이면 6도쯤 --
|
||||
# 한 번 나아가는 동안 수심이 눈에 띄게 바뀌지 않을 만큼이다.
|
||||
@@ -180,7 +199,8 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
_rest_odds = 0.6
|
||||
_turn_rate = 1.2
|
||||
_accel = 1.4
|
||||
_band = Vector2(0.1, 0.95)
|
||||
_band = Vector2(0.45, 0.98)
|
||||
_hover_range = Vector2(1.0, 2.9)
|
||||
|
||||
# 돌아다니는 것이 큰 움직임을 맡으므로 제자리 흔들림은 줄인다.
|
||||
_swim_range *= 0.22
|
||||
@@ -195,6 +215,12 @@ func set_roam(area: Rect2, rng: RandomNumberGenerator) -> void:
|
||||
else:
|
||||
_start_leg()
|
||||
_travelled = _leg_dist * rng.randf()
|
||||
|
||||
# 집 앞에서 시작하므로 제 바닥 범위 밖일 수 있다.
|
||||
var band := _band_range()
|
||||
position.y = clampf(position.y, band.x, band.y)
|
||||
_pick_hover()
|
||||
hover = _hover_goal
|
||||
_apply_depth()
|
||||
|
||||
|
||||
@@ -230,6 +256,21 @@ func _start_leg() -> void:
|
||||
_leg_dist = _roam.size.y * lerpf(_leg.x, _leg.y, t)
|
||||
_leg_speed = randf_range(0.6, 1.5)
|
||||
_desired = _new_heading()
|
||||
_pick_hover()
|
||||
|
||||
|
||||
## 이 자리에서 띄울 수 있는 한계. 층 위로 솟구쳐 나가면 안 된다.
|
||||
## 뒷줄에 있을수록 화면 위 여유가 적으니 덜 뜬다 -- 멀리 있는 물고기가
|
||||
## 낮게 떠 있는 것은 원근으로도 맞다.
|
||||
func _hover_ceiling() -> float:
|
||||
return maxf(position.y - _roam.position.y - _cell_h * 0.35, 0.0)
|
||||
|
||||
|
||||
func _pick_hover() -> void:
|
||||
if _hover_range == Vector2.ZERO:
|
||||
return
|
||||
_hover_goal = minf(_cell_h * randf_range(_hover_range.x, _hover_range.y),
|
||||
_hover_ceiling())
|
||||
|
||||
|
||||
func _start_rest() -> void:
|
||||
@@ -281,6 +322,12 @@ func _roam_step(delta: float) -> void:
|
||||
# 벽을 따라 하염없이 미끄러지지 않도록 이번 이동을 곧 끝낸다.
|
||||
_leg_dist = minf(_leg_dist, _travelled + _roam.size.y * 0.8)
|
||||
|
||||
# 물기둥에서 오르내리는 것은 바닥 위치와 따로 간다. 아주 천천히 움직여야
|
||||
# 헤엄쳐 올라가는 것처럼 보인다.
|
||||
if _hover_range != Vector2.ZERO:
|
||||
_hover_goal = minf(_hover_goal, _hover_ceiling())
|
||||
hover = move_toward(hover, _hover_goal, _cell_h * 0.55 * delta)
|
||||
|
||||
# 목표 각도로 부드럽게 돌아서고, 속도는 서서히 붙고 서서히 죽는다.
|
||||
_heading = rotate_toward(_heading, _desired, _turn_rate * delta)
|
||||
_throttle = move_toward(_throttle, 0.0 if _resting else 1.0, _accel * delta)
|
||||
@@ -330,17 +377,28 @@ func _process(delta: float) -> void:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
# 바닥 그림자. 3/4 시점에서 물건이 땅에 붙어 있다고 알려주는 신호다.
|
||||
# 떠 있을수록 넓고 옅어진다.
|
||||
var lift := clampf(hover / maxf(span.y, 1.0), 0.0, 1.0)
|
||||
draw_set_transform(Vector2(0, -span.y * 0.12), 0.0, Vector2(1.0, 0.34))
|
||||
draw_circle(Vector2.ZERO, span.x * (0.30 + lift * 0.10),
|
||||
Color(0, 0, 0, 0.20 - lift * 0.09))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
## 몸을 그릴 기준 변환. draw_set_transform은 쌓이지 않고 통째로 갈아치우므로,
|
||||
## 형태 함수 안에서 좌우를 뒤집거나 윗면을 눕힐 때도 반드시 _body_offset을 다시
|
||||
## 실어야 한다. 빠뜨리면 hover가 날아가 헤엄치는 것이 모래 위에 붙어 버린다.
|
||||
func _xform(offset := Vector2.ZERO, scale := Vector2.ONE) -> void:
|
||||
draw_set_transform(_body_offset + offset, 0.0, scale)
|
||||
|
||||
# 헤엄치는 것은 제 칸 위를 오간다.
|
||||
draw_set_transform(Vector2(_swim.x, -hover + _swim.y), 0.0, Vector2.ONE)
|
||||
|
||||
func _draw() -> void:
|
||||
# 몸을 얼마나 띄워 그릴지. 형태 함수들이 좌우를 뒤집을 때도 이 값을 다시
|
||||
# 실어야 하므로 멤버에 담아 둔다.
|
||||
_body_offset = Vector2(_swim.x, -hover + _swim.y)
|
||||
|
||||
# 바닥 그림자. 3/4 시점에서 이것이 바닥 어디에 있는지 알려주는 유일한 신호다.
|
||||
# 헤엄치는 것은 그림자만 모래에 남고 몸은 hover만큼 위로 떠오른다.
|
||||
# 이 간격이 "바닥을 기어간다"와 "물속에 떠 있다"를 가른다.
|
||||
# 높이 뜰수록 그림자는 넓고 옅게 번진다.
|
||||
var lift := clampf(hover / maxf(_cell_h * 3.0, 1.0), 0.0, 1.0)
|
||||
draw_set_transform(Vector2(0, -span.y * 0.12), 0.0, Vector2(1.0, 0.34))
|
||||
draw_circle(Vector2.ZERO, span.x * (0.30 + lift * 0.34),
|
||||
Color(0, 0, 0, 0.22 - lift * 0.13))
|
||||
|
||||
_xform()
|
||||
match shape:
|
||||
"plant": _draw_plant()
|
||||
"fish": _draw_fish()
|
||||
@@ -387,7 +445,7 @@ func _draw_plant() -> void:
|
||||
func _draw_fish() -> void:
|
||||
var l := span.x * 0.34
|
||||
var wag := sin(_t * 4.0 + _phase) * l * 0.30
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing, 1.0))
|
||||
_xform(Vector2.ZERO, Vector2(_facing, 1.0))
|
||||
|
||||
# 꼬리지느러미.
|
||||
_fill(PackedVector2Array([
|
||||
@@ -421,7 +479,7 @@ func _draw_fish() -> void:
|
||||
]), color.lightened(0.28))
|
||||
|
||||
draw_circle(Vector2(l * 0.60, -l * 0.14), maxf(l * 0.11, 1.2), Color(0.06, 0.07, 0.09))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 스스로 빛나며 떠 있는 것. 후광과 아래로 늘어진 촉수.
|
||||
@@ -487,9 +545,9 @@ func _draw_rock() -> void:
|
||||
|
||||
# 꼭대기 면. 부감이라 윗면이 살짝 보이지만, 몸통보다 넓으면 안 된다.
|
||||
var top_w := w * _rock_profile(1.0, 0.0)
|
||||
draw_set_transform(Vector2(0, -h), 0.0, Vector2(1.0, 0.34))
|
||||
_xform(Vector2(0, -h), Vector2(1.0, 0.34))
|
||||
draw_circle(Vector2.ZERO, top_w * 0.92, color.lightened(0.16))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 바위의 높이 f(0=밑동, 1=꼭대기)에서 옆으로 벌어지는 정도.
|
||||
@@ -543,9 +601,9 @@ func _draw_vessel() -> void:
|
||||
|
||||
# 입구. 물고기가 드나드는 곳이라 확실히 어둡게 파 둔다.
|
||||
var mouth := w * _vessel_profile(1.0)
|
||||
draw_set_transform(Vector2(0, -h), 0.0, Vector2(1.0, 0.38))
|
||||
_xform(Vector2(0, -h), Vector2(1.0, 0.38))
|
||||
draw_circle(Vector2.ZERO, mouth * 1.05, color.darkened(0.72))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 밑동은 좁고, 중간이 부르고, 목에서 다시 좁아진다.
|
||||
@@ -619,9 +677,9 @@ func _draw_pillar() -> void:
|
||||
draw_line(Vector2(-half, y), Vector2(half, y), seam, maxf(span.x * 0.028, 1.0))
|
||||
|
||||
# 꼭대기 면. 부감이라 윗면이 살짝 보인다.
|
||||
draw_set_transform(Vector2(0, -h), 0.0, Vector2(1.0, 0.34))
|
||||
_xform(Vector2(0, -h), Vector2(1.0, 0.34))
|
||||
draw_circle(Vector2.ZERO, w * 1.02, color.lightened(0.18))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 바닥을 걷는 게. 옆으로 벌어진 몸에 다리가 뻗고 집게가 앞을 짚는다.
|
||||
@@ -629,7 +687,7 @@ func _draw_pillar() -> void:
|
||||
func _draw_crab() -> void:
|
||||
var r := span.x * 0.26
|
||||
var step := sin(_t * 3.0 + _phase)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing, 1.0))
|
||||
_xform(Vector2.ZERO, Vector2(_facing, 1.0))
|
||||
|
||||
# 다리. 좌우 세 쌍이 번갈아 짚는다.
|
||||
for side: float in [-1.0, 1.0]:
|
||||
@@ -655,14 +713,14 @@ func _draw_crab() -> void:
|
||||
]), color.lightened(0.1))
|
||||
|
||||
# 등딱지. 가로로 넓은 타원.
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing * 1.35, 0.92))
|
||||
_xform(Vector2.ZERO, Vector2(_facing * 1.35, 0.92))
|
||||
draw_circle(Vector2.ZERO, r, color)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing, 1.0))
|
||||
_xform(Vector2.ZERO, Vector2(_facing, 1.0))
|
||||
draw_circle(Vector2(-r * 0.1, -r * 0.22), r * 0.55, color.lightened(0.16))
|
||||
for side: float in [-1.0, 1.0]:
|
||||
draw_circle(Vector2(r * 0.5, side * r * 0.34), maxf(r * 0.12, 1.2),
|
||||
Color(0.06, 0.07, 0.09))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 선 채로 흔들리는 해마. 세로로 긴 S자가 곧 실루엣이다.
|
||||
@@ -670,7 +728,7 @@ func _draw_seahorse() -> void:
|
||||
var h := body_height * 0.9
|
||||
var w := span.x * 0.16
|
||||
var sway := sin(_t * 1.3 + _phase) * 0.22
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing, 1.0))
|
||||
_xform(Vector2.ZERO, Vector2(_facing, 1.0))
|
||||
|
||||
# 몸통. 아래로 갈수록 가늘어지며 꼬리가 안으로 말린다.
|
||||
var left := PackedVector2Array()
|
||||
@@ -703,14 +761,14 @@ func _draw_seahorse() -> void:
|
||||
Vector2(bend - w * 2.0, -h * (f + 0.06)),
|
||||
Vector2(bend - w * 0.8, -h * (f + 0.12)),
|
||||
]), color.darkened(0.3))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
|
||||
## 새우. 등이 굽고 더듬이가 길다. 꼬리를 튕겨 조금씩 물러난다.
|
||||
func _draw_shrimp() -> void:
|
||||
var l := span.x * 0.3
|
||||
var flick := sin(_t * 4.5 + _phase)
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2(_facing, 1.0))
|
||||
_xform(Vector2.ZERO, Vector2(_facing, 1.0))
|
||||
|
||||
# 더듬이. 실루엣의 절반을 차지한다.
|
||||
for side: float in [-0.35, 0.15]:
|
||||
@@ -742,4 +800,4 @@ func _draw_shrimp() -> void:
|
||||
tail + Vector2(-l * 0.35, l * 0.35),
|
||||
]), color.darkened(0.25))
|
||||
draw_circle(Vector2(l * 0.68, -l * 0.12), maxf(l * 0.1, 1.2), Color(0.06, 0.07, 0.09))
|
||||
draw_set_transform(Vector2.ZERO, 0.0, Vector2.ONE)
|
||||
_xform()
|
||||
|
||||
Reference in New Issue
Block a user