update
This commit is contained in:
@@ -16,6 +16,8 @@ const BENCH_OFFSET := 1.95 ## Abstand der Bank hinter der letzten Reihe
|
||||
|
||||
var game: Game = null
|
||||
var camera: Camera3D = null
|
||||
## Brett eines anderen Spielers, das in der Planung oben gezeigt wird.
|
||||
var scout_player: PlayerState = null
|
||||
var selected_slot := {} ## {"kind": "board"/"bench", ...}
|
||||
var hover_slot := {}
|
||||
var drag_active := false
|
||||
@@ -31,6 +33,12 @@ var _bench_tiles := {} ## int -> MeshInstance3D
|
||||
var _bench_mats := {}
|
||||
var _bench_z := 0.0
|
||||
var _units := {} ## key -> Node3D
|
||||
var _dying := {} ## key -> Restzeit der Sterbe-Animation
|
||||
const DEATH_TIME := 0.45
|
||||
var _shots: Array = [] ## fliegende Geschosse {"node", "from", "to", "t", "dur"}
|
||||
var shots_total := 0 ## Zähler aller Geschosse, für Tests
|
||||
var _shot_mesh: SphereMesh
|
||||
var _slash_mesh: BoxMesh
|
||||
var _origin := Vector3.ZERO
|
||||
var _tile_mesh: CylinderMesh
|
||||
var _ring_mesh: TorusMesh
|
||||
@@ -48,6 +56,13 @@ func _ready() -> void:
|
||||
_ring_mesh.outer_radius = 0.66
|
||||
_ring_mesh.ring_segments = 8
|
||||
_ring_mesh.rings = 24
|
||||
_shot_mesh = SphereMesh.new()
|
||||
_shot_mesh.radius = 0.11
|
||||
_shot_mesh.height = 0.22
|
||||
_shot_mesh.radial_segments = 8
|
||||
_shot_mesh.rings = 4
|
||||
_slash_mesh = BoxMesh.new()
|
||||
_slash_mesh.size = Vector3(0.12, 0.12, 0.7)
|
||||
_build_tiles()
|
||||
_build_bench()
|
||||
set_process(true)
|
||||
@@ -212,6 +227,46 @@ func _process(delta: float) -> void:
|
||||
return
|
||||
_update_tiles()
|
||||
_update_units(delta)
|
||||
_update_shots(delta)
|
||||
|
||||
|
||||
## Sichtbarer Angriff: Fernkampf schickt eine Kugel, Nahkampf einen kurzen Hieb.
|
||||
func spawn_attack(from: Vector3, to: Vector3, ranged: bool, col: Color) -> void:
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.mesh = _shot_mesh if ranged else _slash_mesh
|
||||
var m := StandardMaterial3D.new()
|
||||
m.albedo_color = col
|
||||
m.emission_enabled = true
|
||||
m.emission = col
|
||||
m.emission_energy_multiplier = 1.8
|
||||
mi.material_override = m
|
||||
var a := from + Vector3(0.0, 0.7, 0.0)
|
||||
var b := to + Vector3(0.0, 0.6, 0.0)
|
||||
if not ranged:
|
||||
# Der Hieb startet vor dem Angreifer und endet am Ziel.
|
||||
a = a.lerp(b, 0.35)
|
||||
mi.position = a
|
||||
if (b - a).length() > 0.01:
|
||||
mi.look_at_from_position(a, b, Vector3.UP)
|
||||
add_child(mi)
|
||||
shots_total += 1
|
||||
_shots.append({"node": mi, "from": a, "to": b, "t": 0.0, "dur": 0.2 if ranged else 0.09})
|
||||
|
||||
|
||||
func _update_shots(delta: float) -> void:
|
||||
var keep := []
|
||||
for sh in _shots:
|
||||
sh["t"] += delta
|
||||
var f: float = sh["t"] / sh["dur"]
|
||||
var node: MeshInstance3D = sh["node"]
|
||||
if f >= 1.0:
|
||||
node.queue_free()
|
||||
continue
|
||||
var pos: Vector3 = sh["from"].lerp(sh["to"], f)
|
||||
pos.y += sin(f * PI) * 0.35
|
||||
node.position = pos
|
||||
keep.append(sh)
|
||||
_shots = keep
|
||||
|
||||
|
||||
func _update_tiles() -> void:
|
||||
@@ -284,6 +339,10 @@ func _update_units(delta: float) -> void:
|
||||
"side": 0 if u.team == my_team else 1,
|
||||
"cast": u.cast_flash > 0.0,
|
||||
"hit": u.hit_flash > 0.0,
|
||||
"burn": u.burn_timer > 0.0,
|
||||
"slow": u.slow_timer > 0.0 and u.slow > 0.0,
|
||||
"stun": u.stun_timer > 0.0,
|
||||
"shield": clampf(u.shield / maxf(1.0, u.max_hp), 0.0, 1.0),
|
||||
}
|
||||
else:
|
||||
for bh in game.human.board:
|
||||
@@ -292,6 +351,13 @@ func _update_units(delta: float) -> void:
|
||||
"avatar": a, "world": world_pos(from_board(bh)), "hp": 1.0, "mana": 0.0,
|
||||
"side": 0, "cast": false, "hit": false,
|
||||
}
|
||||
if scout_player != null and scout_player.alive:
|
||||
for bh in scout_player.board:
|
||||
var sa: Avatar = scout_player.board[bh]
|
||||
wanted["s%d" % sa.id] = {
|
||||
"avatar": sa, "world": world_pos(Combat._arena_pos(bh, 1)), "hp": 1.0,
|
||||
"mana": 0.0, "side": 1, "cast": false, "hit": false,
|
||||
}
|
||||
# Die Bank ist immer sichtbar, auch während des Kampfes.
|
||||
for i in game.human.bench.size():
|
||||
var ba = game.human.bench[i]
|
||||
@@ -310,9 +376,25 @@ func _update_units(delta: float) -> void:
|
||||
wanted[drag_key]["dragged"] = true
|
||||
|
||||
for key in _units.keys():
|
||||
if not wanted.has(key):
|
||||
_units[key].queue_free()
|
||||
if wanted.has(key):
|
||||
continue
|
||||
var node: Node3D = _units[key]
|
||||
if not _dying.has(key):
|
||||
# Im Kampf schrumpft die Einheit weg, in der Planung verschwindet sie sofort.
|
||||
if game.phase == Game.Phase.COMBAT and key.begins_with("c"):
|
||||
_dying[key] = DEATH_TIME
|
||||
else:
|
||||
node.queue_free()
|
||||
_units.erase(key)
|
||||
continue
|
||||
_dying[key] -= delta
|
||||
var t: float = maxf(0.0, _dying[key] / DEATH_TIME)
|
||||
node.scale = Vector3(t, t * 0.6, t)
|
||||
node.position.y = -0.4 * (1.0 - t)
|
||||
if _dying[key] <= 0.0:
|
||||
node.queue_free()
|
||||
_units.erase(key)
|
||||
_dying.erase(key)
|
||||
|
||||
for key in wanted:
|
||||
var d: Dictionary = wanted[key]
|
||||
@@ -332,10 +414,31 @@ func _update_units(delta: float) -> void:
|
||||
var body: MeshInstance3D = node.get_meta("body")
|
||||
var mat: StandardMaterial3D = body.material_override
|
||||
var base: Color = d["avatar"].color()
|
||||
mat.albedo_color = base.lightened(0.5) if d["hit"] else base
|
||||
var col := base
|
||||
if d.get("slow", false):
|
||||
col = col.lerp(Color(0.55, 0.8, 1.0), 0.45)
|
||||
if d.get("stun", false):
|
||||
col = col.lerp(Color(1.0, 0.95, 0.4), 0.5)
|
||||
if d["hit"]:
|
||||
col = col.lightened(0.5)
|
||||
mat.albedo_color = col
|
||||
var dragged: bool = d.get("dragged", false)
|
||||
mat.emission_energy_multiplier = 1.6 if d["cast"] else (0.45 if dragged else 0.12)
|
||||
node.scale = Vector3.ONE * (1.12 if (d["cast"] or dragged) else 1.0)
|
||||
var emit := 0.12
|
||||
if d["cast"]:
|
||||
emit = 1.6
|
||||
elif d.get("burn", false):
|
||||
emit = 0.9
|
||||
mat.emission = Color(1.0, 0.45, 0.15)
|
||||
elif dragged:
|
||||
emit = 0.45
|
||||
else:
|
||||
mat.emission = base
|
||||
mat.emission_energy_multiplier = emit
|
||||
var sc := 1.12 if (d["cast"] or dragged) else 1.0
|
||||
if d.get("stun", false):
|
||||
node.scale = Vector3(sc * 1.08, sc * 0.8, sc * 1.08)
|
||||
else:
|
||||
node.scale = Vector3.ONE * sc
|
||||
draw_units.append({
|
||||
"pos": node.position, "avatar": d["avatar"], "hp": d["hp"],
|
||||
"mana": d["mana"], "side": d["side"], "cast": d["cast"],
|
||||
@@ -347,12 +450,77 @@ func unit_node(avatar_id: int) -> Node3D:
|
||||
return _units.get("a%d" % avatar_id)
|
||||
|
||||
|
||||
## Für das Overlay: Arena-Feld so spiegeln, wie das Brett gezeichnet wird.
|
||||
func mirror_for_view(p: Vector2i, my_team: int) -> Vector2i:
|
||||
return _mirror(p, my_team)
|
||||
|
||||
|
||||
func _mirror(p: Vector2i, my_team: int) -> Vector2i:
|
||||
if my_team == 0:
|
||||
return p
|
||||
return Vector2i(COLS - 1 - p.x, ROWS - 1 - p.y)
|
||||
|
||||
|
||||
## Kleines Kennzeichen des Hauptelements auf oder neben dem Körper.
|
||||
func _add_ornament(root: Node3D, a: Avatar, body_y: float) -> void:
|
||||
var e := a.primary_element()
|
||||
if e < 0:
|
||||
return
|
||||
var col: Color = Data.ELEMENT_COLORS[e]
|
||||
var m := StandardMaterial3D.new()
|
||||
m.albedo_color = col.lightened(0.15)
|
||||
m.emission_enabled = true
|
||||
m.emission = col
|
||||
m.emission_energy_multiplier = 0.5
|
||||
var top := body_y + 0.5
|
||||
match e:
|
||||
Data.Element.FEUER:
|
||||
# Flamme: kleiner Kegel oben.
|
||||
var c := CylinderMesh.new()
|
||||
c.top_radius = 0.0
|
||||
c.bottom_radius = 0.16
|
||||
c.height = 0.38
|
||||
c.radial_segments = 8
|
||||
_orn(root, c, m, Vector3(0.0, top + 0.18, 0.0))
|
||||
Data.Element.WASSER:
|
||||
# Tropfen: flache Kugel oben.
|
||||
var sp := SphereMesh.new()
|
||||
sp.radius = 0.16
|
||||
sp.height = 0.22
|
||||
_orn(root, sp, m, Vector3(0.0, top + 0.12, 0.0))
|
||||
Data.Element.WIND:
|
||||
# Finne: dünne, hohe Platte.
|
||||
var b := BoxMesh.new()
|
||||
b.size = Vector3(0.05, 0.42, 0.28)
|
||||
_orn(root, b, m, Vector3(0.0, top + 0.2, 0.0))
|
||||
Data.Element.BLITZ:
|
||||
# Zacke: Prisma oben.
|
||||
var pr := PrismMesh.new()
|
||||
pr.size = Vector3(0.28, 0.4, 0.12)
|
||||
_orn(root, pr, m, Vector3(0.0, top + 0.2, 0.0))
|
||||
Data.Element.ERDE:
|
||||
# Schultern: zwei Würfel seitlich.
|
||||
for sx in [-1.0, 1.0]:
|
||||
var bx := BoxMesh.new()
|
||||
bx.size = Vector3(0.22, 0.22, 0.22)
|
||||
_orn(root, bx, m, Vector3(sx * 0.5, body_y + 0.1, 0.0))
|
||||
Data.Element.EIS:
|
||||
# Kristall: umgedrehtes Prisma oben.
|
||||
var cr := PrismMesh.new()
|
||||
cr.size = Vector3(0.22, 0.42, 0.22)
|
||||
var n := _orn(root, cr, m, Vector3(0.0, top + 0.2, 0.0))
|
||||
n.rotation_degrees = Vector3(180.0, 45.0, 0.0)
|
||||
|
||||
|
||||
func _orn(root: Node3D, mesh: Mesh, mat: Material, pos: Vector3) -> MeshInstance3D:
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.mesh = mesh
|
||||
mi.material_override = mat
|
||||
mi.position = pos
|
||||
root.add_child(mi)
|
||||
return mi
|
||||
|
||||
|
||||
func _make_unit(a: Avatar, side: int) -> Node3D:
|
||||
var root := Node3D.new()
|
||||
|
||||
@@ -390,6 +558,8 @@ func _make_unit(a: Avatar, side: int) -> Node3D:
|
||||
root.add_child(body)
|
||||
root.set_meta("body", body)
|
||||
|
||||
_add_ornament(root, a, body.position.y)
|
||||
|
||||
# Bodenring: weiss = eigenes Team, rot = Gegner.
|
||||
var ring := MeshInstance3D.new()
|
||||
ring.mesh = _ring_mesh
|
||||
|
||||
Reference in New Issue
Block a user