Files
cocos_zhuzhan/tools/make_direction_gizmo.py

139 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""生成关卡左下角等距 X/Y 罗盘(灰箭头 + 青色标注,抗锯齿)。"""
from __future__ import annotations
import math
from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter, ImageFont
# HUD 显示约 1.55× Unity2x 出图保证清晰
OUT_W, OUT_H = 802, 430
SS = 4
ARROW = (92, 98, 108, 255)
ARROW_EDGE = (58, 62, 70, 255)
TEAL = (64, 220, 188, 255)
LABEL_STROKE = (32, 36, 40, 230)
SHADOW = (0, 0, 0, 48)
def _font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
for fp in (
"/System/Library/Fonts/Supplemental/Arial Bold.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/Library/Fonts/Arial Bold.ttf",
):
try:
return ImageFont.truetype(fp, size)
except OSError:
continue
return ImageFont.load_default()
def _arrow(draw: ImageDraw.ImageDraw, ox: float, oy: float, angle: float, length: float) -> tuple[float, float]:
"""沿 angle屏幕 Y 向上)画一支圆润箭头,返回箭头顶点。"""
dx, dy = math.cos(angle), -math.sin(angle)
px, py = -dy, dx
shaft = 12.5 * SS
head_len = 26 * SS
head_w = 20 * SS
tip_x = ox + dx * length
tip_y = oy + dy * length
base_x = ox + dx * (length - head_len)
base_y = oy + dy * (length - head_len)
# 箭身:圆头线段
draw.line([(ox, oy), (base_x, base_y)], fill=ARROW_EDGE, width=int(shaft + 2.4 * SS), joint="curve")
draw.line([(ox, oy), (base_x, base_y)], fill=ARROW, width=int(shaft), joint="curve")
hx, hy = px * head_w, py * head_w
head = [
(tip_x, tip_y),
(base_x + hx, base_y + hy),
(base_x - hx, base_y - hy),
]
draw.polygon(head, fill=ARROW_EDGE)
inset = 1.15 * SS
inner = [
(tip_x - dx * inset * 0.4, tip_y - dy * inset * 0.4),
(base_x + hx * 0.78 + dx * inset, base_y + hy * 0.78 + dy * inset),
(base_x - hx * 0.78 + dx * inset, base_y - hy * 0.78 + dy * inset),
]
draw.polygon(inner, fill=ARROW)
return tip_x, tip_y
def render() -> Image.Image:
w, h = OUT_W * SS, OUT_H * SS
layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(layer)
ox = w * 0.50
oy = h * 0.84
length = min(w, h) * 0.46
ang_x = math.atan2(0.5, 1.0) # 2:1 等距 +X → 右上
ang_y = math.atan2(0.5, -1.0) # +Y → 左上
tx, ty = _arrow(draw, ox, oy, ang_x, length)
uy, vy = _arrow(draw, ox, oy, ang_y, length)
r = 11 * SS
draw.ellipse((ox - r - SS, oy - r - SS, ox + r + SS, oy + r + SS), fill=ARROW_EDGE)
draw.ellipse((ox - r, oy - r, ox + r, oy + r), fill=ARROW)
ir = 5.2 * SS
draw.ellipse((ox - ir, oy - ir, ox + ir, oy + ir), fill=TEAL)
font = _font(int(60 * SS))
labels = (("Y", uy, vy, ang_y), ("X", tx, ty, ang_x))
stroke = max(2, int(3.6 * SS))
for text, px, py, ang in labels:
dx, dy = math.cos(ang), -math.sin(ang)
lx = px + dx * 30 * SS
ly = py + dy * 30 * SS
bbox = draw.textbbox((0, 0), text, font=font)
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
pos = (lx - tw / 2, ly - th / 2 - 6 * SS)
for oxs in range(-stroke, stroke + 1):
for oys in range(-stroke, stroke + 1):
if oxs * oxs + oys * oys > stroke * stroke:
continue
draw.text((pos[0] + oxs, pos[1] + oys), text, font=font, fill=LABEL_STROKE)
draw.text(pos, text, font=font, fill=TEAL)
alpha = layer.split()[3]
sh = Image.new("RGBA", (w, h), (0, 0, 0, 0))
sh_draw = ImageDraw.Draw(sh)
# 用同一 alpha 做淡阴影
sh.putalpha(alpha.point(lambda a: min(255, int(a * 0.28)) if a else 0))
rgb = Image.new("RGB", (w, h), (0, 0, 0))
sh = Image.merge("RGBA", (*rgb.split(), sh.split()[-1]))
sh = sh.filter(ImageFilter.GaussianBlur(radius=1.6 * SS))
composed = Image.new("RGBA", (w, h), (0, 0, 0, 0))
composed.alpha_composite(sh, (int(1.2 * SS), int(1.4 * SS)))
composed.alpha_composite(layer)
out = composed.resize((OUT_W, OUT_H), Image.Resampling.LANCZOS)
alpha = out.split()[3].point(lambda p: 255 if p >= 16 else 0)
box = alpha.getbbox()
if box:
pad = 16
x0 = max(0, box[0] - pad)
y0 = max(0, box[1] - pad)
x1 = min(out.width, box[2] + pad)
y1 = min(out.height, box[3] + pad)
out = out.crop((x0, y0, x1, y1))
return out
def write(path: Path) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
render().save(path, format="PNG", optimize=True)
return path
if __name__ == "__main__":
out = Path(__file__).resolve().parents[1] / "assets/resources/textures/ui/ui-direction.png"
write(out)
print(f"wrote {out}")