当前位置: 首页 > news >正文

Godot4.2实战:用AstarGrid2D给你的战棋游戏做个“行动力范围”高亮(含四种对角线模式详解)

Godot4.2实战:用AstarGrid2D实现战棋游戏行动力范围高亮

战棋游戏的核心乐趣之一在于策略性移动,而行动力范围的可视化则是提升玩家决策体验的关键。本文将深入探讨如何利用Godot4.2的AstarGrid2D系统,为战棋角色实现精确的行动力范围计算与高亮显示,并详细解析四种对角线模式在不同游戏规则中的应用差异。

1. AstarGrid2D基础配置与行动力原理

在开始构建行动力系统前,我们需要建立基础的网格导航环境。与传统的Astar2D相比,AstarGrid2D通过预定义网格结构大幅简化了路径计算的初始化工作。

var astar_grid = AStarGrid2D.new() var grid_size = Vector2i(20, 20) # 20x20的网格 var cell_size = Vector2i(64, 64) # 每格64像素 func _ready(): astar_grid.size = grid_size astar_grid.cell_size = cell_size astar_grid.default_compute_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN astar_grid.update()

行动力(Movement Points)系统的核心在于:

  • 每个角色拥有固定的移动点数
  • 每移动一格消耗1点(正交移动)或1.5点(对角线移动)
  • 行动力范围是所有移动消耗不超过总行动力的可到达格子

注意:实际项目中建议将行动力数值乘以2处理,避免浮点数运算带来的精度问题。例如将3点行动力记为6,对角线移动消耗3。

2. 四种对角线模式深度解析

AstarGrid2D提供了四种对角线处理模式,直接影响行动力范围的计算结果:

模式枚举值适用场景移动消耗计算
DIAGONAL_MODE_NEVER1国际象棋车、中国象棋车仅正交移动,对角线完全禁止
DIAGONAL_MODE_ALWAYS0国际象棋王、皇后对角线与正交移动消耗相同
DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE2火焰纹章系列相邻两格至少有一格可通行才允许对角线
DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES3高级战争系列相邻两格都必须可通行才允许对角线

典型应用场景对比

  • 中国象棋"士"的移动:
astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_ALWAYS # 同时需要自定义is_in_bounds()检查九宫格限制
  • 战棋类游戏标准移动:
astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE # 配合行动力消耗系数1.5倍

3. 高效计算行动力范围的三种方法

3.1 矩形范围筛选法

最直观的实现方式,适合小范围移动或性能要求不高的场景:

func get_movement_range(start_pos: Vector2i, move_points: int) -> Array: var reachable = [] var rect = Rect2i( start_pos - Vector2i(move_points, move_points), Vector2i(2 * move_points + 1, 2 * move_points + 1) ) for x in range(rect.position.x, rect.end.x): for y in range(rect.position.y, rect.end.y): var target = Vector2i(x, y) if !astar_grid.is_in_bounds(target): continue var path = astar_grid.get_point_path(start_pos, target) if path.is_empty(): continue var cost = calculate_path_cost(path) if cost <= move_points: reachable.append(target) return reachable

3.2 广度优先搜索优化版

更高效的算法实现,特别适合大范围移动计算:

func bfs_movement_range(start: Vector2i, max_cost: int) -> Dictionary: var frontier = [start] var cost_so_far = {start: 0} var came_from = {} while not frontier.is_empty(): var current = frontier.pop_front() for neighbor in get_neighbors(current): var new_cost = cost_so_far[current] + get_move_cost(current, neighbor) if new_cost > max_cost: continue if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: cost_so_far[neighbor] = new_cost frontier.append(neighbor) came_from[neighbor] = current return { "reachable": cost_so_far.keys(), "paths": came_from }

3.3 预计算与缓存策略

对于固定地图的战棋游戏,可以采用预处理技术:

  1. 预先计算所有格子到周围格子的移动消耗
  2. 将结果存储在二维数组或位图中
  3. 运行时直接查询预处理数据
var movement_cache = [] func precompute_movement_costs(): movement_cache.resize(grid_size.x) for x in grid_size.x: movement_cache[x] = [] movement_cache[x].resize(grid_size.y) for y in grid_size.y: movement_cache[x][y] = compute_cell_movement(Vector2i(x,y)) func compute_cell_movement(pos: Vector2i) -> Dictionary: # 实现与bfs_movement_range类似,但只计算单格信息 ...

4. 行动力范围的可视化实现

计算得到可移动格子后,我们需要将其直观地展示给玩家。以下是几种常见的可视化方案:

4.1 基础高亮方案

func draw_movement_range(reachable_cells: Array): var highlight = Color(0, 1, 0, 0.3) # 半透明绿色 for cell in reachable_cells: var rect = Rect2(cell * cell_size, cell_size) draw_rect(rect, highlight, true)

4.2 梯度颜色方案

根据移动消耗显示不同颜色深度:

func draw_gradient_range(cost_map: Dictionary, max_cost: int): for cell in cost_map: var ratio = float(cost_map[cell]) / max_cost var color = Color(1 - ratio, ratio, 0, 0.5) # 红到绿渐变 draw_rect(Rect2(cell * cell_size, cell_size), color, true)

4.3 高级Shader效果

使用材质着色器实现更炫酷的效果:

shader_type canvas_item; uniform vec4 highlight_color : source_color = vec4(0,1,0,0.5); uniform float grid_size; uniform float pulse_speed = 1.0; uniform float time; void fragment() { vec2 grid_pos = fract(UV * grid_size); float dist = distance(grid_pos, vec2(0.5)); float alpha = smoothstep(0.4, 0.5, dist); float pulse = sin(time * pulse_speed) * 0.1 + 0.9; COLOR = highlight_color * vec4(pulse) * (1.0 - alpha); }

5. 性能优化技巧

当处理大型地图或多角色时,性能优化至关重要:

  1. 分层计算

    • 先计算粗略范围(如矩形区域)
    • 再对边缘格子进行精确计算
  2. 增量更新

    var cached_movement = {} func get_movement_range_optimized(unit): if unit.position in cached_movement: return cached_movement[unit.position] # 否则执行完整计算并缓存 var result = calculate_full_range(unit) cached_movement[unit.position] = result return result
  3. 多线程处理

    func calculate_in_thread(start_pos: Vector2i, move_points: int): var thread = Thread.new() thread.start(_thread_calculate.bind(start_pos, move_points)) func _thread_calculate(start_pos, move_points): var result = bfs_movement_range(start_pos, move_points) call_deferred("_on_calculation_done", result)
  4. 可视区域优化

    func get_visible_range(camera_rect: Rect2): var visible_cells = [] var grid_rect = Rect2i( floor(camera_rect.position / cell_size), ceil(camera_rect.size / cell_size) ) # 只计算视野内的格子 ...

6. 特殊地形与行动力消耗

真实战棋游戏通常包含多种地形类型,需要扩展基础系统:

enum TERRAIN { PLAIN, # 平原 消耗1 FOREST, # 森林 消耗2 MOUNTAIN, # 山地 消耗3 WATER # 水域 不可通行 } var terrain_map = [] func get_move_cost(from: Vector2i, to: Vector2i) -> float: var base_cost = 1.0 if is_diagonal_move(from, to): base_cost = 1.5 var terrain_cost = get_terrain_cost(to) return base_cost * terrain_cost func get_terrain_cost(pos: Vector2i) -> int: return TERRAIN.values()[terrain_map[pos.x][pos.y]]

对应的路径计算需要修改为:

func custom_path_cost(from: Vector2i, to: Vector2i) -> float: if astar_grid.is_point_solid(to): return -1 # 不可通行 var cost = get_move_cost(from, to) return cost

7. 完整实现示例

以下是一个集成行动力计算、地形系统和高亮显示的完整场景示例:

extends Node2D class_name TacticalMovementSystem @export var grid_width: int = 20 @export var grid_height: int = 20 @export var cell_size: int = 64 var astar_grid: AStarGrid2D var terrain_map = [] var movement_range = [] var highlight_sprites = [] func _ready(): initialize_grid() generate_terrain() update_movement_range(Vector2i(5,5), 4) func initialize_grid(): astar_grid = AStarGrid2D.new() astar_grid.size = Vector2i(grid_width, grid_height) astar_grid.cell_size = Vector2i(cell_size, cell_size) astar_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE astar_grid.update() func generate_terrain(): terrain_map.resize(grid_width) for x in range(grid_width): terrain_map[x] = [] terrain_map[x].resize(grid_height) for y in range(grid_height): # 随机生成地形,实际项目中应从地图数据读取 var terrain = randi() % 4 terrain_map[x][y] = terrain if terrain == TERRAIN.WATER: astar_grid.set_point_solid(Vector2i(x,y), true) func update_movement_range(unit_pos: Vector2i, move_points: int): clear_highlights() var result = bfs_movement_range(unit_pos, move_points) movement_range = result.reachable for cell in movement_range: add_highlight(cell) func bfs_movement_range(start: Vector2i, max_cost: int) -> Dictionary: var frontier = [start] var cost_so_far = {start: 0} while not frontier.is_empty(): var current = frontier.pop_front() for neighbor in get_neighbors(current): var new_cost = cost_so_far[current] + get_move_cost(current, neighbor) if new_cost > max_cost: continue if neighbor not in cost_so_far: cost_so_far[neighbor] = new_cost frontier.append(neighbor) return {"reachable": cost_so_far.keys()} func get_neighbors(pos: Vector2i) -> Array: var neighbors = [] # 正交邻居 for dir in [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]: var neighbor = pos + dir if astar_grid.is_in_bounds(neighbor): neighbors.append(neighbor) # 对角线邻居 if astar_grid.diagonal_mode != AStarGrid2D.DIAGONAL_MODE_NEVER: for dx in [-1, 1]: for dy in [-1, 1]: var neighbor = pos + Vector2i(dx, dy) if astar_grid.is_in_bounds(neighbor): # 根据对角线模式进一步筛选 if check_diagonal_condition(pos, neighbor): neighbors.append(neighbor) return neighbors func check_diagonal_condition(from: Vector2i, to: Vector2i) -> bool: match astar_grid.diagonal_mode: AStarGrid2D.DIAGONAL_MODE_ALWAYS: return true AStarGrid2D.DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE: return !astar_grid.is_point_solid(from + Vector2i(to.x - from.x, 0)) || !astar_grid.is_point_solid(from + Vector2i(0, to.y - from.y)) AStarGrid2D.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES: return !astar_grid.is_point_solid(from + Vector2i(to.x - from.x, 0)) && !astar_grid.is_point_solid(from + Vector2i(0, to.y - from.y)) _: return false func add_highlight(cell: Vector2i): var sprite = Sprite2D.new() sprite.texture = preload("res://highlight.png") sprite.position = Vector2(cell) * cell_size + Vector2(cell_size/2, cell_size/2) sprite.modulate = Color(0,1,0,0.5) add_child(sprite) highlight_sprites.append(sprite) func clear_highlights(): for sprite in highlight_sprites: sprite.queue_free() highlight_sprites.clear()
http://www.rkmt.cn/news/1432412.html

相关文章:

  • Mathtype 7.0 安装后Word闪退?手把手教你手动替换残留的6.9文件(附文件路径截图)
  • ChatGPT如何重塑教育:从个性化学习到教师赋能的技术实践
  • 用PyTorch实现FNO(傅里叶神经算子):一个解决偏微分方程的AI新范式
  • 基于推特数据的情感分析实战:从数据抓取到模型集成
  • 遥感顶刊GRSL投稿后,我如何用21天搞定大修并成功录用?附Response Letter模板
  • 别再为多设备同步发愁了!NI-DAQmx通道扩展功能保姆级配置指南(含9469模块跨机箱实战)
  • AI与区块链融合:构建可信高效的零工经济新生态
  • 基于GPT API的轻量级AI智能体项目构建器:从原理到实践
  • C盘红了别慌!用Windows自带的磁盘清理工具(cleanmgr)一键删除windows.old,轻松腾出10GB+空间
  • 2026年5月北京老房改造装修公司推荐:十大排名评测市场份额老旧户型翻新案例价格 - 品牌推荐
  • 2022年AI趋势:超自动化、生成式AI、MLOps与负责任AI的企业落地指南
  • 企业级 Qt 全功能项目
  • 移动应用开发趋势:AI、5G、安全与跨平台技术实战解析
  • 别再只用立创EDA画简单板子了!用标准版搞定双层板布局布线实战心得
  • LlamaIndex 的索引结构深度解析
  • 别再死记硬背了!用这份贾俊平《统计学》第七版中英对照表,搞定你的SPSS/R/Python数据分析
  • 别急着删老版本!CentOS 7升级OpenSSH 9.3p2时,/etc/pam.d/sshd文件备份有多重要?
  • 哪家北京别墅装修公司专业?2026年5月推荐TOP5对比地下室防潮评测案例适用场景 - 品牌推荐
  • 告别Excel!用SPSS 25.0做时间序列预测,从数据导入到结果解读保姆级教程
  • 超算/内网环境救星:用conda-pack离线打包迁移Python+CUDA环境(含CUDA 12.2实战)
  • 终极3DS游戏存档管理指南:用JKSM守护你的游戏回忆
  • 网络安全初创公司如何通过行业竞赛验证技术与商业模式
  • AI病历质控工具到底值不值得上?——6家三甲医院18个月真实效能对比数据,第4项结果令人震惊
  • 从热电偶到应变片:如何用一个NI-DAQmx任务搞定混合传感器采集(LabVIEW实例详解)
  • 告别手动同步!保姆级教程:为Win10/Mac双系统时间错误配置Python自动校正服务
  • MobaXterm隐藏玩法:不止远程连接,它的Server、宏录制和端口扫描功能更香
  • AI密码猜测技术解析:从生成式模型到实战攻防
  • 79.实测通杀全系高通机型!Sahara/Firehose协议原生刷写源码(带详细注释)
  • 保姆级教程:用Cheat Engine的指针扫描器搞定游戏多级指针(附Tutorial-i386.exe实战)
  • 基于FastMCP构建你的第一个MCP服务器:从协议原理到Claude集成实战