Spielfeld über Art zeichnen
Karte, Figuren und Zielmarke zeichnet jetzt Art. Die Karte zeichnet in Blöcken von 16 mal 16 Kacheln und nach einem Treffer nur die Blöcke um die Kachel neu; jede Kachel kennt ihre Nachbarn. Figuren stehen aufrecht in ihrer Kachel, schreiten und schauen in die Richtung, in die sie gehen. Der eigene Schlag zeigt seine Splitter sofort, auch als Gast, und die Namen über fremden Figuren werden in Bildschirmgröße gezeichnet und bleiben scharf. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+167
-33
@@ -1,48 +1,182 @@
|
||||
class_name MapView
|
||||
extends Node2D
|
||||
## Draws the terrain of the shared world. The colours are placeholders until
|
||||
## a style is chosen from the samples of plan M1d.
|
||||
## Draws the terrain of the shared world in the style of `art`: every tile to
|
||||
## fit its neighbours, the cracks of the hits it took, and a short burst of
|
||||
## chips where a pickaxe lands. The map is drawn in square blocks, so that a
|
||||
## hit draws only the blocks around its tile again.
|
||||
|
||||
const CRACK_COLOR := Color(0.1, 0.08, 0.06, 0.8)
|
||||
## The terrain that rock masses and the edge of the map are made of. Tiles
|
||||
## beside it are drawn to fit it.
|
||||
const SOLID: Array[int] = [GwSession.TERRAIN_SOFT_ROCK, GwSession.TERRAIN_HARD_ROCK, GwSession.TERRAIN_BEDROCK]
|
||||
## Tiles along each side of a block.
|
||||
const BLOCK := 16
|
||||
## Seconds a burst of chips lasts.
|
||||
const HIT_SECONDS := 0.35
|
||||
|
||||
var session: GwSession
|
||||
var art: Art
|
||||
var _width := 0
|
||||
var _height := 0
|
||||
var _terrain := PackedByteArray()
|
||||
var _damage := PackedByteArray()
|
||||
var _solid := PackedByteArray()
|
||||
var _masks := PackedInt32Array()
|
||||
var _variants := PackedInt32Array()
|
||||
var _blocks: Array[MapBlock] = []
|
||||
var _bursts: Array[Dictionary] = []
|
||||
var _burst_layer := Node2D.new()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
## A square part of the map that draws itself.
|
||||
class MapBlock extends Node2D:
|
||||
var view: MapView
|
||||
var tiles: Rect2i
|
||||
|
||||
func _init(owner_view: MapView, block_tiles: Rect2i) -> void:
|
||||
view = owner_view
|
||||
tiles = block_tiles
|
||||
|
||||
func _draw() -> void:
|
||||
view.draw_block(self, tiles)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_burst_layer.draw.connect(_draw_bursts)
|
||||
add_child(_burst_layer)
|
||||
set_process(false)
|
||||
|
||||
|
||||
## The graphic of a terrain code.
|
||||
static func id_of(terrain: int) -> StringName:
|
||||
match terrain:
|
||||
GwSession.TERRAIN_SOFT_ROCK:
|
||||
return Art.SOFT_ROCK
|
||||
GwSession.TERRAIN_HARD_ROCK:
|
||||
return Art.HARD_ROCK
|
||||
GwSession.TERRAIN_BEDROCK:
|
||||
return Art.BEDROCK
|
||||
return Art.GROUND
|
||||
|
||||
|
||||
## Takes the whole map from `session` anew and draws it, or shows nothing if
|
||||
## no world runs. Call it when a world starts or was replaced.
|
||||
func reload() -> void:
|
||||
for block in _blocks:
|
||||
remove_child(block)
|
||||
block.free()
|
||||
_blocks.clear()
|
||||
_bursts.clear()
|
||||
_burst_layer.queue_redraw()
|
||||
if session == null or not session.is_running():
|
||||
_width = 0
|
||||
_height = 0
|
||||
return
|
||||
_width = session.map_width()
|
||||
_height = session.map_height()
|
||||
_terrain = session.terrain_codes()
|
||||
_damage = session.damage_codes()
|
||||
_solid = TileShape.flags(_terrain, SOLID)
|
||||
_masks.resize(_width * _height)
|
||||
_variants.resize(_width * _height)
|
||||
for y in _height:
|
||||
for x in _width:
|
||||
var tile := Vector2i(x, y)
|
||||
_masks[y * _width + x] = TileShape.mask(_solid, _width, _height, tile, true)
|
||||
_variants[y * _width + x] = TileShape.variant(tile)
|
||||
for top in range(0, _height, BLOCK):
|
||||
for left in range(0, _width, BLOCK):
|
||||
var block := MapBlock.new(self, Rect2i(left, top, mini(BLOCK, _width - left), mini(BLOCK, _height - top)))
|
||||
_blocks.append(block)
|
||||
add_child(block)
|
||||
move_child(_burst_layer, -1)
|
||||
|
||||
|
||||
## Draws everything again, for example in another style.
|
||||
func redraw() -> void:
|
||||
for block in _blocks:
|
||||
block.queue_redraw()
|
||||
|
||||
|
||||
## Takes `tile` anew from the session after it changed, and draws it and the
|
||||
## tiles around it again, which may have to fit it differently now.
|
||||
func refresh_tile(tile: Vector2i) -> void:
|
||||
if _blocks.is_empty() or not Rect2i(0, 0, _width, _height).has_point(tile):
|
||||
return
|
||||
_terrain = session.terrain_codes()
|
||||
_damage = session.damage_codes()
|
||||
var index := tile.y * _width + tile.x
|
||||
_solid[index] = 1 if SOLID.has(_terrain[index]) else 0
|
||||
var touched: Array[MapBlock] = []
|
||||
for y in range(tile.y - 1, tile.y + 2):
|
||||
for x in range(tile.x - 1, tile.x + 2):
|
||||
var around := Vector2i(x, y)
|
||||
if not Rect2i(0, 0, _width, _height).has_point(around):
|
||||
continue
|
||||
_masks[y * _width + x] = TileShape.mask(_solid, _width, _height, around, true)
|
||||
var block := _block_of(around)
|
||||
if not touched.has(block):
|
||||
touched.append(block)
|
||||
for block in touched:
|
||||
block.queue_redraw()
|
||||
|
||||
|
||||
## The block that draws `tile`.
|
||||
func _block_of(tile: Vector2i) -> MapBlock:
|
||||
var across := ceili(_width / float(BLOCK))
|
||||
@warning_ignore("integer_division")
|
||||
return _blocks[(tile.y / BLOCK) * across + tile.x / BLOCK]
|
||||
|
||||
|
||||
## Shows a burst of chips on `tile`, where a pickaxe just landed.
|
||||
func hit(tile: Vector2i) -> void:
|
||||
_bursts.append({"tile": tile, "age": 0.0})
|
||||
set_process(true)
|
||||
|
||||
|
||||
## The number of blocks the map is drawn in. Tests read it.
|
||||
func block_count() -> int:
|
||||
return _blocks.size()
|
||||
|
||||
|
||||
## The number of bursts still shown. Tests read it.
|
||||
func burst_count() -> int:
|
||||
return _bursts.size()
|
||||
|
||||
|
||||
## The neighbour mask a tile is drawn with. Tests read it.
|
||||
func mask_at(tile: Vector2i) -> int:
|
||||
return _masks[tile.y * _width + tile.x]
|
||||
|
||||
|
||||
## Draws the tiles `tiles` onto `block`, while the block draws.
|
||||
func draw_block(block: MapBlock, tiles: Rect2i) -> void:
|
||||
if art == null:
|
||||
return
|
||||
var width := session.map_width()
|
||||
var height := session.map_height()
|
||||
var terrain := session.terrain_codes()
|
||||
var damage := session.damage_codes()
|
||||
var size := GridMotion.TILE_SIZE
|
||||
for y in height:
|
||||
for x in width:
|
||||
var index := y * width + x
|
||||
for y in range(tiles.position.y, tiles.end.y):
|
||||
for x in range(tiles.position.x, tiles.end.x):
|
||||
var index := y * _width + x
|
||||
var rect := Rect2(x * size, y * size, size, size)
|
||||
draw_rect(rect, color_of(terrain[index]))
|
||||
if damage[index] > 0:
|
||||
_draw_cracks(rect, damage[index])
|
||||
var variant := _variants[index]
|
||||
art.draw(block, id_of(_terrain[index]), rect, {"solid": _masks[index], "variant": variant})
|
||||
if _damage[index] > 0:
|
||||
art.draw(block, Art.CRACKS, rect, {"hits": _damage[index], "variant": variant})
|
||||
|
||||
|
||||
## The placeholder colour of a terrain code.
|
||||
static func color_of(terrain: int) -> Color:
|
||||
if terrain == GwSession.TERRAIN_GROUND:
|
||||
return Color(0.42, 0.36, 0.28)
|
||||
if terrain == GwSession.TERRAIN_SOFT_ROCK:
|
||||
return Color(0.56, 0.55, 0.52)
|
||||
if terrain == GwSession.TERRAIN_HARD_ROCK:
|
||||
return Color(0.33, 0.33, 0.37)
|
||||
if terrain == GwSession.TERRAIN_BEDROCK:
|
||||
return Color(0.12, 0.12, 0.14)
|
||||
return Color.MAGENTA
|
||||
func _process(delta: float) -> void:
|
||||
for burst in _bursts:
|
||||
burst["age"] += delta / HIT_SECONDS
|
||||
_bursts = _bursts.filter(func(burst: Dictionary) -> bool: return burst["age"] < 1.0)
|
||||
if _bursts.is_empty():
|
||||
set_process(false)
|
||||
_burst_layer.queue_redraw()
|
||||
|
||||
|
||||
## One crack line for every hit the tile has taken.
|
||||
func _draw_cracks(rect: Rect2, hits: int) -> void:
|
||||
var inset := rect.grow(-3.0)
|
||||
for line in hits:
|
||||
var share := (line + 1.0) / (hits + 1.0)
|
||||
var from := Vector2(inset.position.x, inset.position.y + inset.size.y * share)
|
||||
var to := Vector2(inset.end.x, inset.position.y + inset.size.y * (1.0 - share))
|
||||
draw_line(from, to, CRACK_COLOR, 1.0)
|
||||
func _draw_bursts() -> void:
|
||||
if art == null:
|
||||
return
|
||||
var size := GridMotion.TILE_SIZE
|
||||
for burst in _bursts:
|
||||
var tile: Vector2i = burst["tile"]
|
||||
var rect := Rect2(Vector2(tile) * size, Vector2(size, size))
|
||||
art.draw(_burst_layer, Art.HIT, rect, {"age": burst["age"], "variant": TileShape.variant(tile)})
|
||||
|
||||
Reference in New Issue
Block a user