下载 App

UI动画的技巧

07/251 浏览综合
  1. 悬停动画(缩放+旋转)
技巧:分离X/Y轴缩放时间,配合随机旋转方向,使用EASE_OUT + TRANS_BACK曲线。 关键代码:
func hover() -> void: if disabled: return pivot_offset = size / 2.0 var scale_ratio = clampf(width_full_rot / size.x, 0.5, 1.0) var scale_target = 1.0 + (0.2) * scale_ratio if tween and tween.is_running(): tween.kill() tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) tween.tween_property(self, "scale:x", scale_target, 0.2) tween.parallel().tween_property(self, "scale:y", scale_target, 0.35) tween.parallel().tween_property(self, "rotation_degrees", 5.0 * scale_ratio * [-1.0, 1.0].pick_random(), 0.1) tween.parallel().tween_property(self, "rotation_degrees", 0.0, 0.1).set_delay(0.1) 2. 根据按钮大小调整动画强度
技巧:通过width_full_rot/size.x计算缩放比例,限制为0.5~1.0,防止长按钮旋转过度。 关键代码:
var scale_ratio = clampf(width_full_rot / size.x, 0.5, 1.0) var scale_target = 1.0 + (0.2) * scale_ratio 3. 容器内子元素错序入场动画
技巧:_ready中遍历子节点,设置modulate.a = 0(不改变visible),然后用并行tween加延迟实现错开。 关键代码:
func _ready() -> void: for c in target.get_children(): c.modulate.a = 0.0 # 动画触发 tween.set_parallel(true) var idx = 0 for c in children: tween.tween_property(c, "scale", Vector2.ONE, duration).from(Vector2.ZERO).set_delay(delay_between_elements * idx) tween.tween_property(c, "modulate:a", 1.0, 0.01).set_delay(delay_between_elements * idx) idx += 1 4. 自动补间脚本(基于可见性变化)
技巧:用_set函数捕获属性变化,自动触发补间动画,无需修改原有代码。 关键代码:
@tool extends Control @export var target_property: String = "property"
func _set(property: StringName, value: Variant) -> bool: if property == target_property: if get(property) == value: return false animate() return false return false
func animate() -> void: pivot_offset = size / 2.0 if tween and tween.is_running(): tween.kill() tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) tween.tween_property(self, "scale:x", 1.2, 0.1) tween.parallel().tween_property(self, "scale:y", 1.2, 0.15) tween.parallel().tween_property(self, "rotation_degrees", 15.0 * [-1.0, 1.0].pick_random(), 0.1) tween.parallel().tween_property(self, "scale:x", 1.0, 0.2).set_delay(0.2) tween.parallel().tween_property(self, "scale:y", 1.0, 0.3).set_delay(0.25) tween.parallel().tween_property(self, "rotation_degrees", 0.0, 0.1).set_delay(0.15) 5. 显示/隐藏自动补间(容器脚本)
技巧:通过show()/hide()函数封装,支持缩放或淡入淡出,用set_parallel(true)和set_delay让子元素依次出现。 关键代码(显示):
func show() -> void: show_started.emit() tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC) if anim_type == ANIM_TYPE.SCALE: tween.tween_property(target, "scale", Vector2.ONE, duration).from(Vector2.ZERO) elif anim_type == ANIM_TYPE.FADE: tween.tween_property(target, "modulate:a", 1.0, duration).from(0.0) tween.tween_callback(show_finished.emit)
关键代码(隐藏):
func hide(should_free: bool = false) -> void: hide_started.emit() tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC) if anim_type == ANIM_TYPE.SCALE: tween.tween_property(target, "scale", Vector2.ZERO, duration) elif anim_type == ANIM_TYPE.FADE: tween.tween_property(target, "modulate:a", 0.0, duration) if should_free: tween.tween_callback(target.queue_free) 6. 数值变化时的弹跳动画(_set重写)
技巧:重写_set函数,当某个属性值改变时触发一次弹跳动画(缩放+旋转)。 关键代码:
func _set(property: StringName, value: Variant) -> bool: if property == target_property: if get(property) == value: return false animate() return false return false
func animate() -> void: pivot_offset = size / 2.0 if tween and tween.is_running(): tween.kill() tween = create_tween().set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) tween.tween_property(self, "scale:x", 1.2, 0.1) tween.parallel().tween_property(self, "scale:y", 1.2, 0.15) tween.parallel().tween_property(self, "rotation_degrees", 15.0 * [-1.0, 1.0].pick_random(), 0.1) tween.parallel().tween_property(self, "scale:x", 1.0, 0.2).set_delay(0.2) tween.parallel().tween_property(self, "scale:y", 1.0, 0.3).set_delay(0.25) tween.parallel().tween_property(self, "rotation_degrees", 0.0, 0.15).set_delay(0.15)
这些技巧的核心思想是复用补间动画预设,通过tween设置并行效果、延迟和曲线(EASE_OUT + TRANS_BACK),并利用_set函数或单独的脚本实现对属性变化的自动响应。
右键拖拽平移
一、问题本质 不是平移公式写错,而是 UrhoX UI 库指针事件派发的两个不对称陷阱叠加: 陷阱 现象 根因文件 OnPointerMove 不向父冒泡 右键按下 isPanning_=true,但 move 事件永远到不了 mapView urhox-libs/UI/Core/Widget.lua(OnPointerDown/Up 有 parent 冒泡,OnPointerMove 没有) H
官方
4 赞
地图编辑器地面交互与视觉优化
一、问题链 用户截图反馈了三个递进的问题: 问题 1:点击地面类型(如食堂地砖),弹窗不关闭,挡住画布无法操作 ↓ 修复 v0.10.94 问题 2:地面贴图太大,一块地砖比小人还宽,比例失调 ↓ 修复 v0.10.96(改配置值) 问题 3:改了配置值后刷新仍然太大——旧存档的 tileSize=256 优先级高于新 config ↓ 修复 v0.10.97(反转渲染优先级) 二、关键代码
官方
4 赞
地图编辑器仿真 tick 重建抑制
UI 重建频率 = 事件分发的可靠性上限。 只要有一帧 SetRoot 销毁了正在处理 pointer 事件的控件,跨重建边界的 Down/Up 对就会被吞。高频仿真 tick 必须在 UI 边界按页面过滤。 关键代码(scripts/main.lua HandleUpdate) 四步走:先原地处理人员 → 聚合所有仿真 changed → 地图页强制压制 → 只放行显式/登录态变化。 -- ma
官方
4 赞
修复"地图编辑器刷新数据丢失"
问题诊断过程 用户报告:编辑器建好房间 → 保存 → 刷新页面 → 进编辑器 → 数据全没了。 关键线索:用户发现"校园日"模式刷新后能看到房间,但一进编辑器就没了。 这直接指向了编辑器页面自身的初始化逻辑,而非持久化链路。 根因 地图编辑模式.lua 的 EnsureBlankEditorLayout() 设计意图是"首次打开编辑器提供空白画布",用模块级布尔 editorLayoutClear
官方
4 赞
四个原则(数据驱动、组件化、组合优于继承、对象池)
四个原则(数据驱动、组件化、组合优于继承、对象池)确实是系统从"能跑"到"能维护"的分水岭。 先看看你的游戏现状,再说怎么落地。 你的游戏现状分析 你的《自由的校园》不是传统战斗游戏,但已经有大量"对抗性交互": 已有机制 对应战斗系统概念 主角投掷物品(粉笔、扫把) 投射物 / 技能 行为违规 → 目击 → 怀疑累积 Debuff 叠加 冲突事件 → 保安响应 Boss 技能 / AI 响应链
官方
4 赞
拾取 / 丢弃物品
拾取的坑是「点击被遮挡」,丢弃的坑是「坐标反解会偏」。两者解法都是绕开复杂机制、用最直接的方式。 问题 根因 解法 走到物品旁点不动、捡不起来 物品命中层和主角标记同层、都无 zIndex,数组顺序靠后的主角标记盖在上面,点击命中的是主角而非物品 给可交互物品显式 zIndex = 20 拖拽丢东西飞到远处 用 ScreenToWorld 把鼠标像素反解成世界坐标当落点,依赖 zoom/offse
官方
4 赞
图片加载三层优化设计
目标场景:地图编辑器"物品/墙体/地面/房间/人员"调色板弹窗的图片首帧显示 文档目的:沉淀"图片为什么慢、怎么治、发布后玩家体验如何、未来加资源时 preload_groups 怎么取舍"的完整结论,避免重复踩坑。 一句话结论 WASM 下图片"慢"不是单一问题,而是下载 / 解码 / 上传 GPU 三件事全堆在"玩家点弹窗那一刻"做导致的。优化的本质是把这三件事提前到启动期做(让玩家点的时候图
官方
1 赞
槽位变更只走原子方法
经验 下标 = 视觉格子,空槽是合法状态。定长 9 槽,inventory[i]=nil 即第 i 格空着,中间允许空。任何"把物品往前挪/压缩/重排"的操作都会让"你看到的格子"和"数组真实下标"脱节 → 物品消失。渲染直映 inventory[i],数据层绝不重排。 稀疏表禁用 #。带 nil 洞的表 #inv 是未定义的(可能=0 直接读成空包,或=洞前边界)。所有遍历上界一律 Player
官方
4 赞
精灵平滑移动、墙碰撞体积
一、 渲染层用连续坐标(scripts/页面/校园地图.lua,三处统一): -- NPC 定位(第408-410行) local contLeft = (character.x or 0) * 5 - 18 - charLeft local contTop = (character.y or 0) * 5 - 36 - charTop -- 主角定位(第504-506行) local con
官方
1 赞
以鼠标为锚点缩放
核心数学原理 缩放前后,鼠标指向的那个世界坐标点必须保持在同一屏幕位置。 已知:旧缩放 oldZoom、新缩放 newZoom、鼠标锚点屏幕坐标 (ax, ay)、当前偏移 (panX, panY) 步骤1:算出旧缩放下鼠标指向的世界坐标 wx = (ax - panX) / oldZoom wy = (ay - panY) / oldZoom 步骤2:新缩放后反解新偏移,让 wx/wy
官方
1 赞