载入中...
搜索中...
未找到
CardTypes.cpp
浏览该文件的文档.
1#include "card/CardTypes.h"
2
4
5#include "graphics/Graphics.h"
6
7#include <algorithm>
8#include <random>
9
10namespace eve::card {
11
12const char *cardStateName(CardState state) {
13 switch (state) {
14 case CardState::Deck: return "deck";
15 case CardState::Hand: return "hand";
16 case CardState::Hovered: return "hovered";
17 case CardState::Dragging: return "dragging";
18 case CardState::Returning: return "returning";
19 case CardState::Played: return "played";
20 case CardState::Discarded: return "discarded";
21 case CardState::Disabled: return "disabled";
22 }
23 return "unknown";
24}
25
26// ---------------------------------------------------------------------------
27// 工具
28// ---------------------------------------------------------------------------
29
30namespace {
31
32struct RenderTransform {
33 float cx = 0.f;
34 float cy = 0.f;
35 float degrees = 0.f;
36 bool active = false;
37};
38
39RenderTransform gRenderTransform;
40
41glm::vec2 transformedCenter(float x, float y, float w, float h) {
42 glm::vec2 center{x + w * 0.5f, y + h * 0.5f};
43 if (!gRenderTransform.active || std::abs(gRenderTransform.degrees) < 0.001f) return center;
44 const float rad = gRenderTransform.degrees * 3.14159265358979323846f / 180.f;
45 const float c = std::cos(rad);
46 const float s = std::sin(rad);
47 const float dx = center.x - gRenderTransform.cx;
48 const float dy = center.y - gRenderTransform.cy;
49 return {gRenderTransform.cx + dx * c - dy * s, gRenderTransform.cy + dx * s + dy * c};
50}
51
52float clampf(float v, float lo, float hi) { return v < lo ? lo : (v > hi ? hi : v); }
53float lerpf(float a, float b, float t) { return a + (b - a) * t; }
54
55// 帧率无关的插值系数:k 是 60fps 下的每帧系数,换算到实际 dt。
56float frameRateK(float k, float dt) {
57 if (dt <= 0.f) return k;
58 const float u = 1.f - k;
59 const float n = dt * 60.f;
60 const int whole = static_cast<int>(n);
61 const float frac = n - static_cast<float>(whole);
62 float r = 1.f;
63 for (int i = 0; i < whole; ++i) r *= u;
64 r *= (1.f + frac * (u - 1.f));
65 return 1.f - r;
66}
67
68void drawSolid(graphics::Graphics *gfx, float x, float y, float w, float h, float angle,
69 const glm::vec4 &color) {
70 if (!gRenderTransform.active || std::abs(angle) < 0.001f)
71 gfx->drawSolidRect(x, y, w, h, color);
72 else {
73 const glm::vec2 center = transformedCenter(x, y, w, h);
74 gfx->drawSolidRectRotated(center.x, center.y, w, h, angle, color);
75 }
76}
77
78void drawTexture(graphics::Graphics *gfx, graphics::Texture *texture, float x, float y, float w, float h,
79 float angle, const glm::vec4 &color) {
80 if (!gRenderTransform.active || std::abs(angle) < 0.001f) {
81 gfx->drawTexturedRect(texture, x, y, w, h, color);
82 } else {
83 const glm::vec2 center = transformedCenter(x, y, w, h);
84 gfx->drawTexturedRectShaderUVRotated(texture, nullptr, center.x, center.y, w, h,
85 angle, 0.f, 0.f, 1.f, 1.f, color);
86 }
87}
88
89void drawGem(graphics::Graphics *gfx, float x, float y, float size, float angle,
90 const glm::vec3 &rgb, float a) {
91 drawSolid(gfx, x, y, size, size, angle, glm::vec4(0.15f, 0.13f, 0.10f, a));
92 const float p = size * 0.20f;
93 drawSolid(gfx, x + p, y + p, size - p * 2.f, size - p * 2.f, angle, glm::vec4(rgb, a));
94}
95
96} // namespace
97
98// ---------------------------------------------------------------------------
99// CardData
100// ---------------------------------------------------------------------------
101
103 CardData *c = CardData::create();
104 c->identity();
105 c->stats();
106 c->attributes();
107 c->effects();
108 c->visual();
109 c->layout();
110 c->state();
111 c->definitionBinding();
112 return c;
113}
114
115bool CardData::hit(float px, float py) {
116 const auto *L = layout().operator->();
117 if (L->w <= 0.f || L->h <= 0.f) return false;
118 const float cw = L->w * L->scale;
119 const float ch = L->h * L->scale;
120 return px >= L->x - cw * 0.5f && px <= L->x + cw * 0.5f && py >= L->y - ch * 0.5f &&
121 py <= L->y + ch * 0.5f;
122}
123
124std::string CardData::describe() {
125 const auto *I = identity().operator->();
127 if (!projected) projected.ignore("card description retained legacy projection after attribute failure");
128 const auto *S = stats().operator->();
129 if (I->kind == "spell") return I->name + "(法术 · 费用 " + std::to_string(S->cost) + ")";
130 return I->name + "(" + std::to_string(S->attack) + "/" + std::to_string(S->health) + " · 费用 " +
131 std::to_string(S->cost) + ")";
132}
133
134// ---------------------------------------------------------------------------
135// Deck
136// ---------------------------------------------------------------------------
137
139 Deck *d = Deck::create();
140 d->membership();
141 return d;
142}
143
145 auto &cards = membership()->cards;
146 if (cards.empty()) return nullptr;
147 CardData *top = cards.back();
148 cards.pop_back();
149 return top;
150}
151
153 auto &cards = membership()->cards;
154 return cards.empty() ? nullptr : cards.back();
155}
156
158 auto &cards = membership()->cards;
159 if (index < 0 || static_cast<size_t>(index) >= cards.size()) return nullptr;
160 return cards[static_cast<size_t>(index)];
161}
162
164 static thread_local std::mt19937 rng(std::random_device{}());
165 std::shuffle(membership()->cards.begin(), membership()->cards.end(), rng);
166}
167
168// ---------------------------------------------------------------------------
169// Zone
170// ---------------------------------------------------------------------------
171
173 Zone *z = Zone::create();
174 z->rect();
175 z->filter();
176 return z;
177}
178
179bool Zone::contains(float px, float py) {
180 const auto *R = rect().operator->();
181 return px >= R->x && px <= R->x + R->w && py >= R->y && py <= R->y + R->h;
182}
183
185 const auto *R = rect().operator->();
186 if (!R->enabled || !card) return false;
187 const auto &kinds = filter()->acceptKinds;
188 if (kinds.empty()) return true;
189 const std::string &kind = card->identity()->kind;
190 for (const auto &k : kinds)
191 if (k == kind) return true;
192 return false;
193}
194
195void Zone::render(graphics::Graphics *gfx, bool showLabel) {
196 auto *R = rect().operator->();
197 gfx->drawSolidRect(R->x, R->y, R->w, R->h, glm::vec4(R->color, R->alpha));
198 if (showLabel && gfx->getFont() && !R->label.empty()) {
199 const glm::vec4 tint(clampf(R->color.r * 0.9f + 0.3f, 0.f, 1.f),
200 clampf(R->color.g * 0.9f + 0.3f, 0.f, 1.f),
201 clampf(R->color.b * 0.9f + 0.3f, 0.f, 1.f), 1.f);
202 printCentered(gfx, R->label, R->x + R->w * 0.5f, R->y + R->h * 0.5f, 0.9f, tint);
203 }
204}
205
206// ---------------------------------------------------------------------------
207// 渲染辅助
208// ---------------------------------------------------------------------------
209
210void printCentered(graphics::Graphics *gfx, const std::string &text, float cx, float cy, float scale,
211 const glm::vec4 &color) {
212 auto *font = gfx->getFont();
213 if (!font) return;
214 const float tw = font->getWidth(text) * scale;
215 const float th = font->getHeight() * scale;
216 // drawText 的 y 是行顶;视觉中心按现有卡牌布局略作补偿。
217 gfx->drawText(font, text, cx - tw * 0.5f, cy - th * 0.35f, color, scale);
218}
219
220void renderCardBack(graphics::Graphics *gfx, float x, float y, float w, float h, float angle, float a) {
221 const RenderTransform previous = gRenderTransform;
222 gRenderTransform = {x + w * 0.5f, y + h * 0.5f, angle, true};
223 // 2D solids keep the first fragment at equal depth, so inner details go first.
224 if (gfx->getFont())
225 printCentered(gfx, "?", x + w * 0.5f, y + h * 0.5f, 1.2f * (w / 110.f),
226 glm::vec4(0.9f, 0.85f, 0.95f, a));
227 const float i2 = w * 0.10f;
228 drawSolid(gfx, x + i2, y + i2, w - i2 * 2.f, h - i2 * 2.f, angle,
229 glm::vec4(0.11f, 0.08f, 0.22f, a));
230 const float i1 = w * 0.05f;
231 drawSolid(gfx, x + i1, y + i1, w - i1 * 2.f, h - i1 * 2.f, angle,
232 glm::vec4(0.30f, 0.17f, 0.45f, a));
233 drawSolid(gfx, x, y, w, h, angle, glm::vec4(0.06f, 0.06f, 0.10f, a));
234 gRenderTransform = previous;
235}
236
237void renderCard(graphics::Graphics *gfx, const CardData &cardRef, const LayoutConfig &cfg, bool back) {
238 (void)cfg;
239 CardData &card = const_cast<CardData &>(cardRef);
241 if (!projected) {
242 projected.ignore("card renderer skipped an invalid attribute projection");
243 return;
244 }
245 const auto *L = card.layout().operator->();
246 const auto *V = card.visual().operator->();
247 const auto *I = card.identity().operator->();
248 const auto *S = card.stats().operator->();
249
250 const float w = L->w * L->scale;
251 const float h = L->h * L->scale;
252 const float x = L->x - w * 0.5f;
253 const float y = L->y - h * 0.5f;
254 const float a = L->alpha;
255
256 const RenderTransform previous = gRenderTransform;
257 gRenderTransform = {L->x, L->y, L->angle, true};
258
259 if (back || !V->faceUp) {
260 renderCardBack(gfx, x, y, w, h, L->angle, a);
261 gRenderTransform = previous;
262 return;
263 }
264
265 // 外框
266 drawSolid(gfx, x, y, w, h, L->angle, glm::vec4(0.07f, 0.07f, 0.09f, a));
267 const float inset = w * 0.035f;
268 const float ix = x + inset;
269 const float iy = y + inset;
270 const float iw = w - inset * 2.f;
271 const float ih = h - inset * 2.f;
272
273 // 卡面底色
274 drawSolid(gfx, ix, iy, iw, ih, L->angle, glm::vec4(V->tint, a));
275
276 // 头部色带
277 const float headerH = ih * 0.16f;
278 drawSolid(gfx, ix, iy, iw, headerH, L->angle,
279 glm::vec4(V->tint.r * 0.82f, V->tint.g * 0.82f, V->tint.b * 0.82f, a));
280
281 // 卡图区域
282 const float artY = iy + headerH;
283 const float artH = ih * 0.34f;
284 if (V->texture) {
285 drawTexture(gfx, V->texture, ix + 2.f, artY + 2.f, iw - 4.f, artH - 4.f, L->angle,
286 glm::vec4(1.f, 1.f, 1.f, a));
287 } else {
288 drawSolid(gfx, ix + 2.f, artY + 2.f, iw - 4.f, artH - 4.f, L->angle,
289 glm::vec4(V->tint.r * 0.55f, V->tint.g * 0.55f, V->tint.b * 0.55f, a));
290 }
291
292 // 描述文字区
293 const float bodyY = artY + artH;
294 const float bodyH = iy + ih - bodyY;
295 drawSolid(gfx, ix, bodyY, iw, bodyH, L->angle, glm::vec4(0.96f, 0.94f, 0.90f, a));
296 drawSolid(gfx, ix + iw * 0.12f, bodyY + bodyH * 0.5f - 1.f, iw * 0.76f, 2.f, L->angle,
297 glm::vec4(0.45f, 0.42f, 0.38f, a));
298
299 // 数值宝石
300 const float gem = w * 0.13f;
301 drawGem(gfx, x + inset, iy, gem, L->angle, glm::vec3(1.00f, 0.78f, 0.25f), a); // 费用(金)
302 if (I->kind != "spell") {
303 drawGem(gfx, x + inset, iy + ih - gem, gem, L->angle, glm::vec3(0.92f, 0.38f, 0.22f), a); // 攻击(橙)
304 drawGem(gfx, x + iw - inset - gem, iy + ih - gem, gem, L->angle, glm::vec3(0.30f, 0.78f, 0.42f), a); // 生命(绿)
305 }
306
307 // 文字(可选:需 gfx 已 setFont)
308 auto *font = gfx->getFont();
309 if (font) {
310 const float ts = 1.f * (w / 110.f);
311 printCentered(gfx, std::to_string(S->cost), x + inset + gem * 0.5f, iy + gem * 0.55f, ts,
312 glm::vec4(0.12f, 0.08f, 0.05f, a));
313 if (I->kind != "spell") {
314 printCentered(gfx, std::to_string(S->attack), x + inset + gem * 0.5f, iy + ih - gem * 0.45f,
315 ts, glm::vec4(0.10f, 0.05f, 0.05f, a));
316 printCentered(gfx, std::to_string(S->health), x + iw - inset - gem * 0.5f,
317 iy + ih - gem * 0.45f, ts, glm::vec4(0.05f, 0.10f, 0.05f, a));
318 }
319 printCentered(gfx, I->name, x + w * 0.5f, iy + headerH * 0.5f, ts * 0.8f,
320 glm::vec4(0.10f, 0.08f, 0.06f, a));
321 }
322 gRenderTransform = previous;
323}
324
325// ---------------------------------------------------------------------------
326// Hand
327// ---------------------------------------------------------------------------
328
330 Hand *h = Hand::create();
331 h->meta();
332 h->membership();
333 h->drag();
334 return h;
335}
336
338 membership()->cards.push_back(c);
339 if (c) {
340 auto *md = meta().operator->();
341 c->state()->phase = c->visual()->disabled ? CardState::Disabled : CardState::Hand;
342 c->layout()->w = md->config ? md->config->cardW : 110.f;
343 c->layout()->h = md->config ? md->config->cardH : 150.f;
344 c->layout()->scale = 0.01f; // 从 0 生长出牌动画
345 c->layout()->alpha = (c->visual()->disabled && md->config) ? md->config->disabledAlpha : 1.f;
346 }
347}
348
350 auto &cards = membership()->cards;
351 for (size_t i = 0; i < cards.size(); ++i) {
352 if (cards[i] == c) {
353 cards.erase(cards.begin() + static_cast<ptrdiff_t>(i));
354 return true;
355 }
356 }
357 return false;
358}
359
361 membership()->cards.clear();
362 auto *d = drag().operator->();
363 d->card = nullptr;
364 d->press = nullptr;
365}
366
368 auto &cards = membership()->cards;
369 if (index < 0 || static_cast<size_t>(index) >= cards.size()) return nullptr;
370 return cards[static_cast<size_t>(index)];
371}
372
373CardData *Hand::find(const std::string &id) {
374 for (auto *c : membership()->cards)
375 if (c && c->identity()->id == id) return c;
376 return nullptr;
377}
378
379CardData *Hand::pick(float px, float py) {
380 auto &cards = membership()->cards;
381 for (auto it = cards.rbegin(); it != cards.rend(); ++it) {
382 if (*it && (*it)->hit(px, py)) return *it;
383 }
384 return nullptr;
385}
386
387void Hand::slotTransform(int i, int n, float &ox, float &oy, float &angle) {
388 const LayoutConfig cfg = meta()->config ? *meta()->config : LayoutConfig();
389 const float totalW = static_cast<float>(n) * cfg.cardW + static_cast<float>(n - 1) * cfg.spacing;
390 const float left = cfg.handX - totalW * 0.5f;
391 ox = left + static_cast<float>(i) * (cfg.cardW + cfg.spacing) + cfg.cardW * 0.5f;
392 const float t = (n > 1) ? static_cast<float>(i) / static_cast<float>(n - 1) : 0.5f;
393 const float d = 2.f * t - 1.f;
394 oy = cfg.handY - cfg.arcHeight * (d * d);
395 angle = cfg.rotationAngle * d;
396}
397
398void Hand::update(float dt, float mx, float my, bool down, const std::vector<Zone *> &zones,
399 std::vector<CardEvent> &out) {
400 auto *md = meta().operator->();
401 auto *d = drag().operator->();
402 auto &cards = membership()->cards;
403 const LayoutConfig cfg = md->config ? *md->config : LayoutConfig();
404 const int n = static_cast<int>(cards.size());
405
406 // 1) 布局 + 悬浮
407 for (int i = 0; i < n; ++i) {
408 CardData *card = cards[static_cast<size_t>(i)];
409 if (!card || card == d->card) continue;
410
411 float sx = 0.f, sy = 0.f, sa = 0.f;
412 slotTransform(i, n, sx, sy, sa);
413
414 bool hover = false;
415 if (d->card == nullptr) hover = (card == pick(mx, my));
416 auto *st = card->state().operator->();
417 auto *vis = card->visual().operator->();
418 auto *lay = card->layout().operator->();
419 st->hovered = hover;
420 if (vis->disabled) st->phase = CardState::Disabled;
421 else if (st->dragging) st->phase = CardState::Dragging;
422 else if (hover) st->phase = CardState::Hovered;
423 else if (st->phase != CardState::Returning) st->phase = CardState::Hand;
424
425 float targetScale = 1.f;
426 float targetY = sy;
427 float targetAngle = sa;
428 if (hover) {
429 targetY = sy - cfg.hoverLift;
430 targetScale = cfg.hoverScale;
431 if (cfg.hoverRotation) targetAngle = 0.f;
432 }
433 float k = frameRateK(hover ? cfg.hoverSpeed : cfg.motionSpeed, dt);
434 lay->x = lerpf(lay->x, sx, k);
435 lay->y = lerpf(lay->y, targetY, k);
436 lay->scale = lerpf(lay->scale, targetScale, k);
437 lay->angle = lerpf(lay->angle, targetAngle, k);
438 lay->alpha = lerpf(lay->alpha, vis->disabled ? cfg.disabledAlpha : 1.f, k);
439 }
440
441 // 2) 拖拽状态机
442 const bool press = down && !d->wasDown;
443 const bool release = !down && d->wasDown;
444 d->wasDown = down;
445
446 if (press && md->interactive) {
447 d->press = pick(mx, my);
448 if (d->press && d->press->visual()->disabled) d->press = nullptr;
449 d->pressX = mx;
450 d->pressY = my;
451 }
452
453 if (down && d->press && !d->card) {
454 const float dx = mx - d->pressX;
455 const float dy = my - d->pressY;
456 if (dx * dx + dy * dy > cfg.dragThreshold) {
457 d->card = d->press;
458 d->card->state()->dragging = true;
459 d->card->state()->phase = CardState::Dragging;
460 d->ox = mx - d->card->layout()->x;
461 d->oy = my - d->card->layout()->y;
462 }
463 }
464
465 if (d->card && down) {
466 auto *lay = d->card->layout().operator->();
467 lay->x = mx - d->ox;
468 lay->y = my - d->oy;
469 lay->scale = cfg.hoverScale;
470 lay->angle = 0.f;
471 lay->alpha = 1.f;
472 }
473
474 if (release) {
475 if (d->card) {
476 d->card->state()->dragging = false;
477 d->card->state()->phase = CardState::Returning;
478 CardData *dropped = d->card;
479 d->card = nullptr; // 归位;若已从手牌移除则自然消失
480 for (Zone *z : zones) {
481 if (z && z->contains(mx, my)) {
482 const std::string cardId = dropped ? dropped->identity()->id : "";
483 if (z->accepts(dropped)) {
484 out.push_back(CardEvent{"drop", md->owner, z->rect()->id, cardId, ""});
485 } else {
486 out.push_back(CardEvent{"dropRejected", md->owner, z->rect()->id, cardId,
487 "kind_not_allowed"});
488 }
489 break;
490 }
491 }
492 } else if (d->press) {
493 out.push_back(CardEvent{"click", md->owner, "", d->press->identity()->id, ""});
494 }
495 d->press = nullptr;
496 }
497}
498
500 auto *md = meta().operator->();
501 const LayoutConfig cfg = md->config ? *md->config : LayoutConfig();
502 const bool back = md->faceDown && !md->peek;
503 const CardData *dragCard = drag()->card;
504 for (auto *c : membership()->cards) {
505 if (c && c != dragCard) renderCard(gfx, *c, cfg, back);
506 }
507 if (dragCard) renderCard(gfx, *dragCard, cfg, back);
508}
509
510} // namespace eve::card
SQInteger top
bool & active
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
const std::string & s
Selective canonical attributes for card combat values.
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float degrees
Definition CardTypes.cpp:35
Vec3 projected
Definition CaveMesh.cpp:122
float py
glm::vec4 p[6]
std::string layout
std::uint32_t dropped
glm::vec4 tint
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
double r
float v
HexVec3 left
std::int32_t c
int h
std::vector< Colorf > px
std::string text
TokenKind kind
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
graphics::Canvas * previous
OnnxTransferStats stats
Definition OnnxGpgpu.cpp:56
std::string id
Definition PlayHost.cpp:108
float d
float t
std::string filter
float dy
float dx
float size
Definition TreeMesh.cpp:156
uint32_t index
double oy
double ox
float angle
static eve::Result< void > project(CardData &card)
Project canonical values into the legacy integer Stats fields.
单张卡牌:ECS 实体,数据拆成 Identity / Stats / Visual / Layout / State。
Definition CardTypes.h:94
static CardData * createCard()
创建并触摸全部组件。
bool hit(float px, float py)
以当前中心 + 缩放判断点是否命中。
std::string describe()
卡牌描述字符串(调试用)。
牌库(栈顶在末尾)。
Definition CardTypes.h:188
CardData * get(int index)
CardData * peek()
查看栈顶卡牌(不弹出)。
void shuffle()
洗牌。
CardData * draw()
弹出栈顶卡牌(末尾);空牌库返回 nullptr。
static Deck * createDeck()
创建并触摸 Membership。
手牌:扇形布局 + 悬浮 + 拖拽 + 落区判定 + 渲染。
Definition CardTypes.h:273
void slotTransform(int i, int n, float &ox, float &oy, float &angle)
第 i 张(共 n 张)的扇形目标位置(牌中心)。
void release() override
Entity.
Definition CardTypes.h:279
CardData * get(int index)
static Hand * createHand()
创建并触摸全部组件。
bool removeCard(CardData *c)
CardData * pick(float px, float py)
命中检测:返回 (px,py) 处最上层卡牌。
void addCard(CardData *c)
手牌增删与查询。
void render(graphics::Graphics *gfx)
绘制整手牌。
void update(float dt, float mx, float my, bool down, const std::vector< Zone * > &zones, std::vector< CardEvent > &out)
每帧推进:布局 + 悬浮 + 拖拽状态机,产出事件到 out。
CardData * find(const std::string &id)
落牌区(手牌区 / 出牌区 / 弃牌区),用于拖放命中判定。
Definition CardTypes.h:223
void render(graphics::Graphics *gfx, bool showLabel)
绘制落牌区。
static Zone * createZone()
创建并触摸组件。
bool accepts(CardData *card)
该区域是否接受这张卡。
bool contains(float px, float py)
点是否落在区域内。
float getWidth(const std::string &text) const
Pixel width of text (UTF-8), including kerning; delegates to FontData.
Definition Font.cpp:176
virtual void drawSolidRect(float x, float y, float w, float h, float r, float g, float b, float a=1.f)
RGBA-float overload matching the script-facing drawSolidRect name.
virtual void drawText(Font *font, const std::string &text, float x, float y, const Color &color=Color(1.f, 1.f, 1.f, 1.f), float scale=1.f)
Draw UTF-8 text with an explicitly supplied GPU font.
Font * getFont() const
Returns the font.
Definition Graphics.h:1629
float lerpf(float a, float b, float t)
Lerpf.
Definition AnimMath.h:40
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
void renderCardBack(graphics::Graphics *gfx, float x, float y, float w, float h, float angle, float a)
绘制牌背。
const char * cardStateName(CardState state)
状态枚举的字符串名(用于脚本/调试)。
Definition CardTypes.cpp:12
CardState
单张卡牌的运行时状态。
Definition CardTypes.h:31
void printCentered(graphics::Graphics *gfx, const std::string &text, float cx, float cy, float scale, const glm::vec4 &color)
渲染辅助(也供 Zone / Deck 复用)。
void renderCard(graphics::Graphics *gfx, const CardData &cardRef, const LayoutConfig &cfg, bool back)
绘制单张卡牌(正面或牌背)。
一次交互事件。
Definition CardTypes.h:260
UiCard 风格的布局配置(脚本可实时修改,对应 UiCard 的 Configs 面板)。
Definition CardTypes.h:46
float cardW
卡牌宽/高(像素)。
Definition CardTypes.h:48
bool hoverRotation
悬浮时是否恢复为水平。
Definition CardTypes.h:60
float arcHeight
扇形弧度(两端上翘高度)。
Definition CardTypes.h:56
float disabledAlpha
禁用牌透明度。
Definition CardTypes.h:68
float handX
手牌扇形中心 X / 基准高度 Y。
Definition CardTypes.h:53
float motionSpeed
通用归位运动速度。
Definition CardTypes.h:66
float spacing
手牌水平间距。
Definition CardTypes.h:51
float hoverScale
悬浮缩放/上移像素/运动速度。
Definition CardTypes.h:62
float rotationAngle
手牌两端最大旋转角(度)。
Definition CardTypes.h:58
float dragThreshold
拖拽触发距离(像素)。
Definition CardTypes.h:75
glm::vec4 color