图片裁剪与动态血条
一张图片不一定要整张显示。ChaUI 的“源 X、源 Y、源宽、源高”可以从素材中选出一块区域,再把这块区域绘制到组件的宽高里。
这篇教程会完成两个效果:
- 从一张 2×2 图集中显示右下角图标;
- 制作随玩家血量缩短、纹理不会被压扁的血条。
开始前,请先完成制作第一个界面,了解页面、图片组件和编辑器属性面板的基本操作。
先分清两组宽高
图片组件同时有“布局宽高”和“源图宽高”,它们负责不同的事情。
| 属性 | 决定什么 | 单位 |
|---|---|---|
layout.width、layout.height | 最终在界面上占多大 | GUI 像素 |
sourceX、sourceY | 从原图哪个位置开始取 | 原图像素 |
sourceWidth、sourceHeight | 从原图取多大一块 | 原图像素 |
源图坐标从左上角 (0, 0) 开始,X 向右增加,Y 向下增加。选中的区域会缩放到 layout.width 和 layout.height,所以这两组尺寸不能混为一谈。
上图只展示当前概念涉及的关键字段。准备直接制作组件时,请继续使用后面的完整组件配置。
第一个练习:从 2×2 图集中取图标
教程图集是 64×64 像素,共有四格,每格 32×32 像素。右下角图标的左上角正好位于 (32, 32)。
下载教程图集,放到当前游戏版本目录:
resourcepacks/ChaUI/gui/tutorial/icon-atlas.png从原图到最终图标
先看完整图集,再确定选区起点和大小,最后设置界面中的显示尺寸。三组尺寸按顺序填写,就不容易把源图像素和 GUI 像素混在一起。
这一步只需要关注下面这些差异字段:
layout:
width: 64
height: 64
image:
sourceX: 32
sourceY: 32
sourceWidth: 32
sourceHeight: 32这是关键字段对照,不是完整组件。可直接使用的完整组件配置在下一节。
在编辑器中新建图片组件,填写:
| 编辑器属性 | 填写值 | 作用 |
|---|---|---|
| 图片路径 | gui/tutorial/icon-atlas.png | 使用刚才保存的图集。 |
| 源 X | 32 | 跳过左边 32 像素。 |
| 源 Y | 32 | 跳过上边 32 像素。 |
| 源宽 | 32 | 取一格宽度。 |
| 源高 | 32 | 取一格高度。 |
| 宽 | 64 | 最终放大到 64 GUI 像素宽。 |
| 高 | 64 | 最终放大到 64 GUI 像素高。 |
对应组件配置如下:
- id: atlas_icon
type: image
parent: ""
visible: true
enabled: false
pointerEvents: pass
scale: 1
opacity: 1
z: 10
layout:
x: 20
y: 20
width: 64
height: 64
image:
path: gui/tutorial/icon-atlas.png
gifLoop: true
gifLoopCount: 0
sourceX: 32
sourceY: 32
sourceWidth: 32
sourceHeight: 32
events: {}这里从原图取出的仍是 32×32 图标,只是最终显示为 64×64。改变布局宽高只改变显示尺寸,不会改成图集里的另一格。
坐标填错时会发生什么
选区大小相同并不代表会显示同一个图标。sourceX 和 sourceY 决定从哪一格开始取图;两者都填 0 时,会取到左上角。
# 反例:起点在左上角,最终显示左上角图标
sourceX: 0
sourceY: 0
# 正例:起点移动到右下角,最终显示目标图标
sourceX: 32
sourceY: 32这里只对比坐标差异;两种写法的 sourceWidth 和 sourceHeight 都是 32。
第二个练习:不会变形的动态血条
先下载两张 100×8 像素素材:
放到:
resourcepacks/ChaUI/gui/tutorial/health-empty.png
resourcepacks/ChaUI/gui/tutorial/health-fill.png血量变化时应该看到什么
空槽始终保持完整,只有上方填充层随血量变化。正确裁剪时,各段纹理的宽度不会改变;血量为 0 时,填充层不绘制。
# 两个字段使用同一个血量比例
layout:
width: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 200
image:
sourceWidth: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 100这是公式关系的简要对照;完整图片组件仍需包含路径、坐标、源高和其他通用字段。
血条由两张图片叠在一起:
health_empty在下方,始终显示完整空槽;health_fill在上方,同时缩短显示宽度和源图选区宽度。
假设玩家有 35% 血量。如果只把 layout.width 改成 35%,ChaUI 仍会把完整 100 像素纹理塞进较短区域,纹理刻度会一起被压扁。正确做法是同时把 sourceWidth 改成 35%,只截取纹理左侧 35 像素。
# 反例:完整纹理被压缩
layout:
width: 70
image:
sourceWidth: 100
# 正例:35% 的选区显示为 35% 的宽度
layout:
width: 70
image:
sourceWidth: 35上面的固定数值用于直观看出 35% 血量时的区别。实际动态血条继续使用下面的完整公式配置。
核心配置是:
- id: health_fill
type: image
z: 11
layout:
x: 20
y: 20
width: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 200
height: 16
image:
path: gui/tutorial/health-fill.png
sourceX: 0
sourceY: 0
sourceWidth: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 100
sourceHeight: 8同一个血量比例分别乘以两个基准:
* 200控制界面上最多显示 200 GUI 像素宽;* 100控制从 100 像素宽的原图中截取多少像素。
公式结果有小数时,源图坐标和尺寸会向下取整。例如结果为 73.8,实际截取 73 个源图像素。血量为 0 时,sourceWidth 得到 0,填充图片不绘制,只留下空血条。
可直接使用的完整页面
下面的页面同时包含图集图标和动态血条。素材按前面的目录放好后,可以将配置保存为 source_region_demo.yml。
id: source_region_demo
version: 1
title: 图片裁剪与动态血条
size:
width: 320
height: 140
coordinateMode: absolute
display:
mode: screen
screen:
dimBackground: false
vars: {}
papi:
refreshTicks: 20
values: {}
events:
open: ""
close: ""
methods: {}
elements:
- id: atlas_icon
type: image
parent: ""
visible: true
enabled: false
pointerEvents: pass
scale: 1
opacity: 1
z: 10
layout:
x: 20
y: 20
width: 64
height: 64
image:
path: gui/tutorial/icon-atlas.png
gifLoop: true
gifLoopCount: 0
sourceX: 32
sourceY: 32
sourceWidth: 32
sourceHeight: 32
events: {}
- id: health_empty
type: image
parent: ""
visible: true
enabled: false
pointerEvents: pass
scale: 1
opacity: 1
z: 10
layout:
x: 100
y: 32
width: 200
height: 16
image:
path: gui/tutorial/health-empty.png
gifLoop: true
gifLoopCount: 0
sourceX: 0
sourceY: 0
sourceWidth: 100
sourceHeight: 8
events: {}
- id: health_fill
type: image
parent: ""
visible: true
enabled: false
pointerEvents: pass
scale: 1
opacity: 1
z: 11
layout:
x: 100
y: 32
width: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 200
height: 16
image:
path: gui/tutorial/health-fill.png
gifLoop: true
gifLoopCount: 0
sourceX: 0
sourceY: 0
sourceWidth: clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 100
sourceHeight: 8
events: {}vals.player.health 和 vals.player.maxHealth 来自当前客户端玩家,页面每次渲染都会重新计算。它们只适合显示,不应当作为服务端奖励、扣费或权限判断依据。
其他组件也能裁剪
源图公式不只属于图片组件:
| 组件 | 可用选区 |
|---|---|
| 图片 | sourceX/Y/Width/Height |
| 按钮 | 普通、悬浮、选中三组选区 |
| 输入框 | 普通、聚焦两组选区 |
| 物品槽 | 可选静态背景选区 |
| 物品展示 | 可选静态背景选区 |
这些字段都接受非负整数或公式。按钮与输入框需要分别配置各状态的选区;切换状态不会自动沿用另一张尺寸不同的图集区域。
在脚本中改变和读取选区
直接赋数字会把当前字段改成固定值;使用 formula(...) 才会继续跟随变量。下面的完整按钮事件会绑定公式,再读取当前结果和原公式:
events:
leftClick: |-
component("health_fill").image.sourceWidth = formula(
clamp(vals.player.health / vals.player.maxHealth, 0, 1) * 100
)
vars.currentSourceWidth = component("health_fill").image.sourceWidth
vars.currentSourceFormula = component("health_fill").image.expression("sourceWidth").source
log("当前源宽:{vars.currentSourceWidth}")
log("源宽公式:{vars.currentSourceFormula}")脚本修改只影响当前打开的页面会话,不会写回服务端 yml。sourceWidth 读取的是这次渲染条件下算出的整数;expression("sourceWidth").source 读取的是保存的公式文字。
带端帽的血条怎么做
有些血条左右两端是圆角、金属扣或其他不能裁掉的图案。不要把整根血条做成一张会变化宽度的图片,建议拆成三层:
- 左端帽使用固定图片和固定宽度;
- 中间填充使用本教程的
layout.width + sourceWidth同步公式; - 右端帽使用固定图片,并让 X 跟随中间填充的右边。
这样血量变化时只裁剪中间的可重复区域,两端不会变形或突然消失。
常见问题
图片完全不显示
先清空四个源图字段测试整张图片。如果整图能显示,再检查选区是否越过图片边界。负数、非数字、未知变量、非有限结果或越界选区只会跳过当前素材,不会关闭页面。
源宽或源高填 0 后不显示
这是正常行为。sourceWidth: 0 或 sourceHeight: 0 表示当前素材不绘制,适合让血量为 0 时隐藏填充层。
调整源宽后纹理还是被压扁
确认 layout.width 也使用了同一个比例。只改源宽会把较短的源区域重新拉满旧布局宽度;只改布局宽则会把完整源图压进较短区域。
编辑器保存后公式变成数字
当前版本会保留源图公式原文。输入时要先写完整公式再离开输入框,半截公式会被拒绝,不会覆盖上一次合法值。
公式读取的是当前值还是原文
component("id").image.sourceWidth 是当前整数;component("id").image.expression("sourceWidth").source 才是原始公式。两者用途不同,不要混用。
继续阅读图片组件了解完整字段,或阅读组件对象、布局公式与临时组件学习脚本动态绑定。
猹件开发组