载入中...
搜索中...
未找到
Pathfinder.cpp
浏览该文件的文档.
1#include "map/Pathfinder.h"
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <functional>
9#include <limits>
10#include <queue>
11#include <tuple>
12#include <unordered_set>
13#include <vector>
14
15namespace eve::map {
16namespace {
17
18constexpr float kInf = std::numeric_limits<float>::infinity();
19constexpr float kSqrt2 = 1.41421356237f;
20
21enum class Topology { Ortho4, Ortho8, Hex };
22
23using NeighborFn = std::function<void(int nx, int ny, float moveCost)>;
24
25Topology parseTopology(const std::string &name, Topology fallback) {
26 if (name == "ortho4" || name == "orthogonal4" || name == "4") return Topology::Ortho4;
27 if (name == "ortho8" || name == "orthogonal8" || name == "8") return Topology::Ortho8;
28 if (name == "hex" || name == "hexagonal" || name == "staggered") return Topology::Hex;
29 if (name == "auto") return fallback;
30 return fallback;
31}
32
33const char *topologyName(Topology t) {
34 switch (t) {
35 case Topology::Ortho4:
36 return "ortho4";
37 case Topology::Ortho8:
38 return "ortho8";
39 case Topology::Hex:
40 return "hex";
41 }
42 return "ortho4";
43}
44
45Topology topologyFromOrientation(MapOrientation orientation, bool preferDiagonal) {
46 switch (orientation) {
49 return Topology::Hex;
52 default:
53 return preferDiagonal ? Topology::Ortho8 : Topology::Ortho4;
54 }
55}
56
57void forEachNeighbor(Topology topology, int x, int y, bool staggerAxisY, bool staggerOdd,
58 const NeighborFn &fn) {
59 if (!fn) return;
60 switch (topology) {
61 case Topology::Ortho4: {
62 static const int dx[4] = {1, -1, 0, 0};
63 static const int dy[4] = {0, 0, 1, -1};
64 for (int i = 0; i < 4; ++i) fn(x + dx[i], y + dy[i], 1.f);
65 break;
66 }
67 case Topology::Ortho8: {
68 static const int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1};
69 static const int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1};
70 for (int i = 0; i < 8; ++i) {
71 const float c = (i < 4) ? 1.f : kSqrt2;
72 fn(x + dx[i], y + dy[i], c);
73 }
74 break;
75 }
76 case Topology::Hex: {
77 if (staggerAxisY) {
78 const bool rowOdd = ((y & 1) != 0);
79 const bool shifted = staggerOdd ? rowOdd : !rowOdd;
80 if (shifted) {
81 fn(x + 1, y, 1.f);
82 fn(x - 1, y, 1.f);
83 fn(x, y - 1, 1.f);
84 fn(x + 1, y - 1, 1.f);
85 fn(x, y + 1, 1.f);
86 fn(x + 1, y + 1, 1.f);
87 } else {
88 fn(x + 1, y, 1.f);
89 fn(x - 1, y, 1.f);
90 fn(x - 1, y - 1, 1.f);
91 fn(x, y - 1, 1.f);
92 fn(x - 1, y + 1, 1.f);
93 fn(x, y + 1, 1.f);
94 }
95 } else {
96 const bool colOdd = ((x & 1) != 0);
97 const bool shifted = staggerOdd ? colOdd : !colOdd;
98 if (shifted) {
99 fn(x, y + 1, 1.f);
100 fn(x, y - 1, 1.f);
101 fn(x - 1, y, 1.f);
102 fn(x - 1, y + 1, 1.f);
103 fn(x + 1, y, 1.f);
104 fn(x + 1, y + 1, 1.f);
105 } else {
106 fn(x, y + 1, 1.f);
107 fn(x, y - 1, 1.f);
108 fn(x - 1, y - 1, 1.f);
109 fn(x - 1, y, 1.f);
110 fn(x + 1, y - 1, 1.f);
111 fn(x + 1, y, 1.f);
112 }
113 }
114 break;
115 }
116 }
117}
118
119float heuristic(Topology topology, int x0, int y0, int x1, int y1) {
120 const int dx = std::abs(x1 - x0);
121 const int dy = std::abs(y1 - y0);
122 switch (topology) {
123 case Topology::Ortho4:
124 return float(dx + dy);
125 case Topology::Ortho8:
126 return float(std::max(dx, dy)) + (kSqrt2 - 1.f) * float(std::min(dx, dy));
127 case Topology::Hex: {
128 auto toCube = [](int x, int y) {
129 const int q = x - (y - (y & 1)) / 2;
130 const int r = y;
131 const int s = -q - r;
132 return std::tuple<int, int, int>{q, r, s};
133 };
134 auto [q0, r0, s0] = toCube(x0, y0);
135 auto [q1, r1, s1] = toCube(x1, y1);
136 return float(std::max({std::abs(q0 - q1), std::abs(r0 - r1), std::abs(s0 - s1)}));
137 }
138 }
139 return float(dx + dy);
140}
141
143struct Grid {
144 int width = 0;
145 int height = 0;
146 TileLayer *layer = nullptr;
147 Topology topology = Topology::Ortho4;
148 bool topologyManual = false;
149 bool diagonal = false;
150 bool blockEmpty = true;
151 bool staggerAxisY = true;
152 bool staggerOdd = true;
153 bool dirty = true;
154 std::vector<float> cost; // ≤0 => blocked
155 std::unordered_set<uint32_t> blockedGids;
156
157 int index(int x, int y) const { return y * width + x; }
158 bool inBounds(int x, int y) const { return x >= 0 && y >= 0 && x < width && y < height; }
159
160 void resize(int w, int h) {
161 width = w > 0 ? w : 0;
162 height = h > 0 ? h : 0;
163 cost.assign(size_t(width * height), 1.f);
164 dirty = true;
165 }
166
167 void applyAutoTopologyFromLayer() {
168 if (!layer) return;
169 topology = topologyFromOrientation(layer->config()->orientation, diagonal);
170 }
171
172 void bindLayer(TileLayer *l) {
173 layer = l;
174 if (!layer) return;
175 auto cfg = layer->config();
176 resize(cfg->mapW, cfg->mapH);
177 staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
178 staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
179 if (!topologyManual) applyAutoTopologyFromLayer();
180 syncFromLayer();
181 }
182
183 void clearLayer() { layer = nullptr; }
184
185 void syncFromLayer() {
186 if (!layer) return;
187 auto cfg = layer->config();
188 auto tiles = layer->tiles();
189 if (cfg->mapW != width || cfg->mapH != height) resize(cfg->mapW, cfg->mapH);
190 staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
191 staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
192 if (!topologyManual) applyAutoTopologyFromLayer();
193
194 const int n = width * height;
195 for (int i = 0; i < n; ++i) {
196 const uint32_t gid = (i < int(tiles->gids.size())) ? tileGid(tiles->gids[size_t(i)]) : 0u;
197 bool blocked = false;
198 if (blockEmpty && gid == 0u) blocked = true;
199 if (blockedGids.count(gid)) blocked = true;
200 cost[size_t(i)] = blocked ? 0.f : 1.f;
201 }
202 dirty = true;
203 }
204
205 void setTopology(const std::string &name) {
206 if (name == "auto") {
207 topologyManual = false;
208 if (layer) applyAutoTopologyFromLayer();
209 else topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
210 } else {
211 topologyManual = true;
212 topology = parseTopology(name, topology);
213 }
214 dirty = true;
215 }
216
217 void setDiagonal(bool enable) {
218 diagonal = enable;
219 if (!topologyManual && layer) applyAutoTopologyFromLayer();
220 else if (!topologyManual) topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
221 dirty = true;
222 }
223
224 void blockGid(int gid) {
225 if (gid < 0) return;
226 blockedGids.insert(uint32_t(gid));
227 dirty = true;
228 if (layer) syncFromLayer();
229 }
230
231 void unblockGid(int gid) {
232 if (gid < 0) return;
233 blockedGids.erase(uint32_t(gid));
234 dirty = true;
235 if (layer) syncFromLayer();
236 }
237
238 void clearBlockedGids() {
239 blockedGids.clear();
240 dirty = true;
241 if (layer) syncFromLayer();
242 }
243
244 void setBlockEmpty(bool enable) {
245 blockEmpty = enable;
246 dirty = true;
247 if (layer) syncFromLayer();
248 }
249
250 bool isWalkable(int x, int y) const {
251 if (!inBounds(x, y)) return false;
252 return cost[size_t(index(x, y))] > 0.f;
253 }
254
255 void setBlocked(int x, int y, bool blocked) {
256 if (!inBounds(x, y)) return;
257 cost[size_t(index(x, y))] = blocked ? 0.f : 1.f;
258 dirty = true;
259 }
260
261 void setCellCost(int x, int y, float c) {
262 if (!inBounds(x, y)) return;
263 cost[size_t(index(x, y))] = c;
264 dirty = true;
265 }
266
267 float getCellCost(int x, int y) const {
268 if (!inBounds(x, y)) return 0.f;
269 return cost[size_t(index(x, y))];
270 }
271
272 void forEachWalkableNeighbor(int x, int y, const NeighborFn &fn) const {
273 if (!fn || !inBounds(x, y)) return;
274 forEachNeighbor(topology, x, y, staggerAxisY, staggerOdd, [&](int nx, int ny, float moveCost) {
275 if (!isWalkable(nx, ny)) return;
276 if (topology == Topology::Ortho8) {
277 const int dx = nx - x;
278 const int dy = ny - y;
279 if (dx != 0 && dy != 0) {
280 if (!isWalkable(x + dx, y) || !isWalkable(x, y + dy)) return;
281 }
282 }
283 fn(nx, ny, moveCost * getCellCost(nx, ny));
284 });
285 }
286};
287
288struct HeapNode {
289 float f = 0.f;
290 int idx = 0;
291 bool operator>(const HeapNode &o) const { return f > o.f; }
292};
293
294} // namespace
295
303
304Pathfinder::Pathfinder() : impl_(std::make_unique<Impl>()) {}
305
307
309
310Pathfinder::~Pathfinder() = default;
311Pathfinder::Pathfinder(Pathfinder &&) noexcept = default;
312Pathfinder &Pathfinder::operator=(Pathfinder &&) noexcept = default;
313
314void Pathfinder::bindLayer(TileLayer *layer) {
315 impl_->grid.bindLayer(layer);
316 invalidateCache();
317}
318
320 impl_->grid.clearLayer();
321 impl_->grid.resize(width, height);
323}
324
325void Pathfinder::setTopology(const std::string &name) {
326 impl_->grid.setTopology(name);
328}
329
330std::string Pathfinder::getTopology() const { return topologyName(impl_->grid.topology); }
331
332void Pathfinder::setDiagonal(bool enable) {
333 impl_->grid.setDiagonal(enable);
335}
336
337bool Pathfinder::getDiagonal() const { return impl_->grid.diagonal; }
338
339void Pathfinder::blockGid(int gid) {
340 impl_->grid.blockGid(gid);
342}
343
345 impl_->grid.unblockGid(gid);
347}
348
350 impl_->grid.clearBlockedGids();
352}
353
354void Pathfinder::setBlockEmpty(bool enable) {
355 impl_->grid.setBlockEmpty(enable);
357}
358
359bool Pathfinder::getBlockEmpty() const { return impl_->grid.blockEmpty; }
360
361void Pathfinder::setBlocked(int x, int y, bool blocked) {
362 impl_->grid.setBlocked(x, y, blocked);
364}
365
366bool Pathfinder::isWalkable(int x, int y) const { return impl_->grid.isWalkable(x, y); }
367
368void Pathfinder::setCellCost(int x, int y, float cost) {
369 impl_->grid.setCellCost(x, y, cost);
371}
372
373float Pathfinder::getCellCost(int x, int y) const { return impl_->grid.getCellCost(x, y); }
374
376 impl_->grid.syncFromLayer();
378}
379
381 impl_->hasCachedField = false;
382 impl_->cachedField.clear();
383}
384
385namespace {
386
387bool ensureSynced(Grid &grid) {
388 if (grid.layer && grid.dirty) grid.syncFromLayer();
389 return grid.width > 0 && grid.height > 0;
390}
391
392FlowField *buildFlowFieldUncached(Grid &grid, int gx, int gy) {
393 auto *field = new FlowField();
394 if (!ensureSynced(grid) || !grid.isWalkable(gx, gy)) {
395 field->resize(grid.width, grid.height);
396 field->setGoal(gx, gy);
397 return field;
398 }
399
400 const int w = grid.width;
401 const int h = grid.height;
402 field->resize(w, h);
403 field->setGoal(gx, gy);
404
405 const auto idx = [w](int x, int y) { return y * w + x; };
406 std::vector<float> dist(size_t(w * h), kInf);
407 std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
408
409 dist[size_t(idx(gx, gy))] = 0.f;
410 field->setCost(gx, gy, 0.f);
411 field->setNext(gx, gy, gx, gy);
412 open.push(HeapNode{0.f, idx(gx, gy)});
413
414 while (!open.empty()) {
415 const HeapNode cur = open.top();
416 open.pop();
417 if (cur.f > dist[size_t(cur.idx)]) continue;
418 const int cx = cur.idx % w;
419 const int cy = cur.idx / w;
420 const float enterCur = grid.getCellCost(cx, cy);
421 forEachNeighbor(grid.topology, cx, cy, grid.staggerAxisY, grid.staggerOdd,
422 [&](int nx, int ny, float moveCost) {
423 if (!grid.isWalkable(nx, ny)) return;
424 if (grid.topology == Topology::Ortho8) {
425 const int dx = nx - cx;
426 const int dy = ny - cy;
427 if (dx != 0 && dy != 0) {
428 if (!grid.isWalkable(cx + dx, cy) || !grid.isWalkable(cx, cy + dy))
429 return;
430 }
431 }
432 const int ni = idx(nx, ny);
433 const float tentative = dist[size_t(cur.idx)] + moveCost * enterCur;
434 if (tentative + 1e-6f >= dist[size_t(ni)]) return;
435 dist[size_t(ni)] = tentative;
436 field->setCost(nx, ny, tentative);
437 field->setNext(nx, ny, cx, cy);
438 open.push(HeapNode{tentative, ni});
439 });
440 }
441 return field;
442}
443
444} // namespace
445
446Path *Pathfinder::findPath(int sx, int sy, int gx, int gy) {
447 auto *path = new Path();
448 Grid &grid = impl_->grid;
449 if (!ensureSynced(grid)) return path;
450 if (!grid.isWalkable(sx, sy) || !grid.isWalkable(gx, gy)) return path;
451 if (sx == gx && sy == gy) {
452 path->add(sx, sy);
453 path->setTotalCost(0.f);
454 return path;
455 }
456
457 const int w = grid.width;
458 const int n = w * grid.height;
459 const auto idx = [w](int x, int y) { return y * w + x; };
460
461 std::vector<float> gScore(size_t(n), kInf);
462 std::vector<int> parent(size_t(n), -1);
463 std::vector<uint8_t> closed(size_t(n), 0);
464
465 std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
466 const int start = idx(sx, sy);
467 const int goal = idx(gx, gy);
468 gScore[size_t(start)] = 0.f;
469 open.push(HeapNode{heuristic(grid.topology, sx, sy, gx, gy), start});
470
471 bool found = false;
472 while (!open.empty()) {
473 const HeapNode cur = open.top();
474 open.pop();
475 if (closed[size_t(cur.idx)]) continue;
476 closed[size_t(cur.idx)] = 1;
477 if (cur.idx == goal) {
478 found = true;
479 break;
480 }
481 const int cx = cur.idx % w;
482 const int cy = cur.idx / w;
483 grid.forEachWalkableNeighbor(cx, cy, [&](int nx, int ny, float edgeCost) {
484 const int ni = idx(nx, ny);
485 if (closed[size_t(ni)]) return;
486 const float tentative = gScore[size_t(cur.idx)] + edgeCost;
487 if (tentative >= gScore[size_t(ni)]) return;
488 gScore[size_t(ni)] = tentative;
489 parent[size_t(ni)] = cur.idx;
490 open.push(HeapNode{tentative + heuristic(grid.topology, nx, ny, gx, gy), ni});
491 });
492 }
493
494 if (!found) return path;
495
496 int cur = goal;
497 while (cur != -1) {
498 path->add(cur % w, cur / w);
499 if (cur == start) break;
500 cur = parent[size_t(cur)];
501 }
502 path->reverse();
503 path->setTotalCost(gScore[size_t(goal)]);
504 return path;
505}
506
507FlowField *Pathfinder::buildFlowField(int gx, int gy) {
508 Grid &grid = impl_->grid;
509 if (!ensureSynced(grid)) {
510 auto *empty = new FlowField();
511 empty->resize(0, 0);
512 empty->setGoal(gx, gy);
513 return empty;
514 }
515
516 if (impl_->hasCachedField && !grid.dirty && impl_->cachedGoalX == gx && impl_->cachedGoalY == gy &&
517 impl_->cachedField.getWidth() == grid.width &&
518 impl_->cachedField.getHeight() == grid.height) {
519 auto *copy = new FlowField();
520 *copy = impl_->cachedField;
521 return copy;
522 }
523
524 FlowField *field = buildFlowFieldUncached(grid, gx, gy);
525 impl_->cachedField = *field;
526 impl_->cachedGoalX = gx;
527 impl_->cachedGoalY = gy;
528 impl_->hasCachedField = true;
529 grid.dirty = false;
530 return field;
531}
532
533Path *Pathfinder::followFlow(FlowField *field, int sx, int sy) {
534 auto *path = new Path();
535 Grid &grid = impl_->grid;
536 if (!field || !ensureSynced(grid)) return path;
537 if (!field->isReachable(sx, sy)) return path;
538
539 const int gx = field->getGoalX();
540 const int gy = field->getGoalY();
541 const int maxSteps = grid.width * grid.height + 2;
542 int x = sx;
543 int y = sy;
544 path->add(x, y);
545 float total = 0.f;
546 for (int step = 0; step < maxSteps; ++step) {
547 if (x == gx && y == gy) break;
548 const int nx = field->nextX(x, y);
549 const int ny = field->nextY(x, y);
550 if (nx == x && ny == y) break;
551 const float c0 = field->costAt(x, y);
552 const float c1 = field->costAt(nx, ny);
553 if (c0 < kInf && c1 < kInf && c0 >= c1) total += (c0 - c1);
554 x = nx;
555 y = ny;
556 path->add(x, y);
557 }
558 path->setTotalCost(total);
559 if (path->getLength() > 0) {
560 const int lx = path->getX(path->getLength() - 1);
561 const int ly = path->getY(path->getLength() - 1);
562 if (lx != gx || ly != gy) path->clear();
563 }
564 return path;
565}
566
567Path *Pathfinder::findGroupPath(int sx, int sy, int gx, int gy) {
568 Grid &grid = impl_->grid;
569 if (!ensureSynced(grid)) return new Path();
570 if (!impl_->hasCachedField || grid.dirty || impl_->cachedGoalX != gx || impl_->cachedGoalY != gy ||
571 impl_->cachedField.getWidth() != grid.width ||
572 impl_->cachedField.getHeight() != grid.height) {
573 FlowField *tmp = buildFlowFieldUncached(grid, gx, gy);
574 impl_->cachedField = *tmp;
575 delete tmp;
576 impl_->cachedGoalX = gx;
577 impl_->cachedGoalY = gy;
578 impl_->hasCachedField = true;
579 grid.dirty = false;
580 }
581 return followFlow(&impl_->cachedField, sx, sy);
582}
583
584// --- Path / FlowField (same TU; keep export surface small on Windows) ---
585
586void Path::clear() {
587 cells_.clear();
588 totalCost_ = 0.f;
589}
590
591void Path::add(int x, int y) { cells_.push_back(Cell{x, y}); }
592
593void Path::reverse() {
594 for (size_t i = 0, j = cells_.size(); i + 1 < j; ++i, --j) std::swap(cells_[i], cells_[j - 1]);
595}
596
597int Path::getLength() const { return int(cells_.size()); }
598
599int Path::getX(int index) const {
600 if (index < 0 || index >= int(cells_.size())) return 0;
601 return cells_[size_t(index)].x;
602}
603
604int Path::getY(int index) const {
605 if (index < 0 || index >= int(cells_.size())) return 0;
606 return cells_[size_t(index)].y;
607}
608
609float Path::getTotalCost() const { return totalCost_; }
610
611void Path::setTotalCost(float cost) { totalCost_ = cost; }
612
613void FlowField::clear() {
614 width_ = height_ = 0;
615 goalX_ = goalY_ = 0;
616 cost_.clear();
617 nextX_.clear();
618 nextY_.clear();
619}
620
621void FlowField::resize(int width, int height) {
622 width_ = width > 0 ? width : 0;
623 height_ = height > 0 ? height : 0;
624 const int n = width_ * height_;
625 cost_.assign(size_t(n), kUnreachable);
626 nextX_.assign(size_t(n), 0);
627 nextY_.assign(size_t(n), 0);
628 for (int y = 0; y < height_; ++y) {
629 for (int x = 0; x < width_; ++x) {
630 const int i = index(x, y);
631 nextX_[size_t(i)] = x;
632 nextY_[size_t(i)] = y;
633 }
634 }
635}
636
637void FlowField::setGoal(int x, int y) {
638 goalX_ = x;
639 goalY_ = y;
640}
641
642bool FlowField::inBounds(int x, int y) const {
643 return x >= 0 && y >= 0 && x < width_ && y < height_;
644}
645
646float FlowField::costAt(int x, int y) const {
647 if (!inBounds(x, y)) return kUnreachable;
648 return cost_[size_t(index(x, y))];
649}
650
651void FlowField::setCost(int x, int y, float cost) {
652 if (!inBounds(x, y)) return;
653 cost_[size_t(index(x, y))] = cost;
654}
655
656int FlowField::nextX(int x, int y) const {
657 if (!inBounds(x, y)) return x;
658 return nextX_[size_t(index(x, y))];
659}
660
661int FlowField::nextY(int x, int y) const {
662 if (!inBounds(x, y)) return y;
663 return nextY_[size_t(index(x, y))];
664}
665
666void FlowField::setNext(int x, int y, int nx, int ny) {
667 if (!inBounds(x, y)) return;
668 const int i = index(x, y);
669 nextX_[size_t(i)] = nx;
670 nextY_[size_t(i)] = ny;
671}
672
673bool FlowField::isReachable(int x, int y) const {
674 const float c = costAt(x, y);
675 return c < kUnreachable && c >= 0.f;
676}
677
678} // namespace eve::map
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
int y
Definition Grass.cpp:135
float height
Definition Grass.cpp:235
float u
Definition Grass.cpp:234
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
uint32_t c
Topology topology
int width
bool staggerOdd
std::unordered_set< uint32_t > blockedGids
bool topologyManual
bool diagonal
bool staggerAxisY
bool blockEmpty
TileLayer * layer
bool dirty
std::vector< float > cost
int idx
float f
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int parent
Definition TreeMesh.cpp:175
float step
Definition TreeMesh.cpp:196
uint32_t s
Definition Weather.cpp:28
std::vector< WfcTile > tiles
Definition WfcSimple.cpp:22
Integration + direction field for group pathfinding to a single goal. nextX/nextY point to the neighb...
Definition FlowField.h:13
bool isReachable(int x, int y) const
int getGoalX() const
Definition FlowField.h:22
float costAt(int x, int y) const
int getGoalY() const
Definition FlowField.h:23
int nextY(int x, int y) const
int nextX(int x, int y) const
Ordered tile-index waypoints from start to goal (inclusive).
Definition Path.h:8
Pathfinding facade. Single-agent: A*. Group (same goal): Flow Field + follow. Grid/topology internals...
Definition Pathfinder.h:17
std::string getTopology() const
void invalidateCache()
Invalidate cached flow field (also called when grid dirties).
void setBlockEmpty(bool enable)
float getCellCost(int x, int y) const
void bindLayer(TileLayer *layer)
void setCellCost(int x, int y, float cost)
void setTopology(const std::string &name)
bool getBlockEmpty() const
bool isWalkable(int x, int y) const
bool getDiagonal() const
void blockGid(int gid)
void setBlocked(int x, int y, bool blocked)
void setSize(int width, int height)
void unblockGid(int gid)
void setDiagonal(bool enable)
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
Definition TileLayer.h:30
void bindLayer(Shader *shader, bool alwaysDark)
Definition Grass.cpp:331
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
MapOrientation
Tile map layout: orthogonal, isometric, staggered, or hexagonal.
uint32_t tileGid(uint32_t raw)
Strip Tiled flip / rotate flags; keep low 28 bits.
Definition TileLayer.h:161
One waypoint.
Definition Path.h:30