Repository Wiki
isHarryh/Ark-Pets

鼠标交互与手动模式

ArkPets 桌面宠物支持两类与鼠标相关的核心交互:指针拾取与拖拽(通过 libGDX 输入回调驱动角色移动与"被拎起/放下"动画)以及手动模式(Action Mode)(通过托盘菜单开启后,tray.keepAnim 锁定当前动画,用户可用鼠标与键盘直接控制角色行走、切换动作)。本页基于 v3.x 源码,剖析这两条交互链路从输入事件到渲染输出的完整控制流。

Purpose and Scope

本页覆盖以下内容:

  • libGDX 输入事件如何经由 InputApplicationAdaptor 进入应用主循环;
  • 手动模式的状态载体 tray.keepAnim 的生命周期与托盘菜单入口("手动模式" / "退出手动");
  • 手动模式下的动画决策逻辑:按住鼠标行走、松开拖拽后的 dropped() 落地动画、按键切换动画;
  • 自动行走到达屏幕边界时的方向翻转与手动模式的互斥处理;
  • 手动模式启用时的描边强调渲染(render_outline_emphasis 系列);
  • 拖拽移动背后的缓动过渡(TransitionVector3 / transition_duration)。

以下内容属于兄弟页面范围,本页仅引用不展开:

  • 动画剪辑、动画分组与动画合成的完整实现(AnimClip / AnimClipGroup / AnimComposer)——见"动画系统"相关页面;
  • 行为模型与随机状态机(Behavior / GeneralBehavior / StochasticMatrix)——见"行为模型"相关页面;
  • 托盘菜单的整体架构与宿主托盘——见托盘相关章节;
  • 渲染管线(SpineRenderPass / PostProcessRenderPass)的着色器细节。

Overview

ArkPets 的角色窗口是一个透明、可穿透点击的桌面窗口,鼠标交互由 libGDX 桌面后端(LWJGL)捕获后转发给应用层:

  1. 拖拽移动:用户按住角色即可拖动窗口;由于角色位置通过带缓动函数的 TransitionVector3 插值,拖拽释放后角色仍会平滑滑向目标位置,而不是瞬移。
  2. 手动模式:托盘菜单中的"手动模式"会把成员托盘的 keepAnim 字段从 null 置为当前动画,此后主循环不再依赖行为模型的自动决策,而是:
    • 按住鼠标左键时调用 behavior.walkAnim(-1) 让角色向左行走(松开后对称地向右);
    • 拖拽结束时调用 behavior.dropped() 播放"落地"动画;
    • 键盘按键可在 keepAnim != null 时直接切换到上一个/下一个动画;
    • 到达屏幕边界时对带位移(mobility != 0)的手动动画执行方向翻转 derive(...)。
  3. 视觉反馈:手动模式启用期间,角色描边改用强调配置 render_outline_emphasis / render_outline_emphasis_color,让用户一眼识别"手动控制中"的状态。

Architecture

Loading diagram...

图中各组件的职责与连接依据:

  • InputApplicationAdaptor 是输入适配器,实现 libGDX 的 InputProcessor 风格回调(如 touchDown),并记录 lastActiveNanoTime 等活跃时间戳,供主循环查询输入状态:
java
@Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { lastActiveNanoTime = System.nanoTime();

Source: InputApplicationAdaptor.java

  • MemberTray(成员托盘)持有手动模式的两个菜单项与状态字段。keepAnim 为 null 表示自动模式,非 null 表示手动模式并锁定指定动画:
java
public abstract class MemberTray { protected JMenuItem optKeepAnimEn = new JMenuItem("手动模式"); protected JMenuItem optKeepAnimDis = new JMenuItem("退出手动");

Source: MemberTray.java

  • ArkPets 主循环 每帧读取 tray.keepAnim,决定是走自动行为(behavior.isAutoAnimExpired() 触发随机切换)还是手动控制(walkAnim / dropped / 键盘切换),最终通过 ArkChar.setAnimation() 把动画提交给合成器:
java
1 /** Requests to set the current animation of the character. 2 * @param animData The animation data. 3 * @return true if success. 4 */ 5 public boolean setAnimation(AnimData animData) { 6 return composer.offer(animData); 7 }

Source: ArkChar.java

  • PostProcessRenderPass 负责描边后处理,手动模式时改用强调描边参数,形成可见的状态反馈。

核心控制流:每帧动画决策

ArkPets 主循环对 tray.keepAnim 的判读是整个手动模式的"心脏"。核心逻辑(位于 ArkPets 渲染循环内)分三个分支:

1. 自动模式分支(keepAnim == null)

java
AnimData newAnim; if (tray.keepAnim == null) { if (behavior.isAutoAnimExpired()) {

Source: ArkPets.java

行为模型到期时重新抽取随机动画;随后针对自动行走做边界保护:

java
int mobility = cha.getPlaying().mobility(); if (tray.keepAnim == null && willReachBorder(mobility)) { // Turn around if auto-walk cause the collision from screen border.

Source: ArkPets.java

关键细节:即便发生转向,tray.keepAnim 仍保持原值不变(null 时仍为 null),即自动模式的转向不改变模式本身:

java
newAnim = new AnimData(newAnim.animClip(), null, newAnim.isLoop(), newAnim.isStrict(), mobility); tray.keepAnim = tray.keepAnim == null ? null : newAnim;

Source: ArkPets.java

这行 tray.keepAnim = tray.keepAnim == null ? null : newAnim 是刻意设计:把方向翻转后的动画重新写回 keepAnim,仅在手动模式下生效(替换为翻转后的版本),自动模式下保持 null 避免意外开启手动模式。

2. 手动模式分支(keepAnim != null)

java
newAnim = tray.keepAnim; }

Source: ArkPets.java

手动模式下,默认动画即用户锁定的 keepAnim;只有用户主动输入时才临时覆盖:

java
newAnim = behavior.dropped(); } else if (tray.keepAnim != null) { // If action-mode is enabled. if (isLeftPressed()) newAnim = behavior.walkAnim(-1); // Left pressed

Source: ArkPets.java

设计意图:手动模式是"锁定 + 临时覆盖"模型 —— 平时播放用户选定的动画,按住鼠标左键时切换为行走动画(walkAnim(-1) 表示向左),松开时回到 keepAnim;拖拽被释放(dropped())的瞬间优先播放落地动画,营造"被扔下"的物理感。

3. 键盘切换动画(仅手动模式可用)

java
protected void onKeyDown(int keycode) { if (tray.keepAnim != null) { // Switch animation in action mode AnimData data;

Source: ArkPets.java

切换成功后写回 keepAnim,因此"手动选中的动画"会作为新的锁定值持续播放:

java
if (data != null) { tray.keepAnim = data; Logger.debug("Animation", "Switch to previous " + data);

Source: ArkPets.java

手动模式的边界翻转

当用户在手动模式下把角色拖到屏幕边缘,带位移分量的动画会在边界处自动调头,防止角色"走出"屏幕。翻转通过 AnimData.derive(...) 派生一个仅修改 mobility 符号的新动画对象:

java
1 if (tray.keepAnim != null && tray.keepAnim.mobility() != 0) { 2 AnimData anim = tray.keepAnim; 3 tray.keepAnim = anim.derive(Math.abs(anim.mobility()) * sign); 4 }

Source: ArkPets.java

要点:

  • 只对 mobility() != 0 的动画生效——原地动作(如待机、挥手)没有方向概念,无需翻转;
  • 使用 Math.abs(anim.mobility()) * sign 而非直接取反,保证翻转结果是"严格按边界方向"的移动量,避免符号叠加错误;
  • 翻转直接写回 tray.keepAnim,因此用户锁定状态会随翻转更新,下一次切换方向时基于翻转后的值。

手动模式的视觉反馈:强调描边

手动模式不仅改变动画决策,也改变渲染参数。主循环在设置描边透明度与颜色时依据 tray.keepAnim 选择普通或强调配置:

java
boolean renderOutline = switch (ArkConfig.getRenderOutlineFrom( tray.keepAnim != null ? config.render_outline_emphasis : config.render_outline )) {

Source: ArkPets.java

java
cha.setOutlineColor(ArkConfig.getGdxColorFrom( tray.keepAnim != null ? config.render_outline_emphasis_color : config.render_outline_color ));

Source: ArkPets.java

设计意图:用户在手动模式下往往正在拖拽或操控角色,视线不一定停留在托盘菜单上。通过描边颜色/样式的变化提供"即时、无歧义"的模式反馈,是一个零成本的 UX 增强——同一套后处理管线 PostProcessRenderPass 只是换了输入参数,没有引入新的渲染路径。

拖拽移动与位置过渡

角色位置并非直接赋值,而是通过 TransitionVector3(配以 EasingFunction)做帧间插值。ArkChar 构造函数中依据配置初始化过渡参数:

java
1 // 2.Geometry setup 2 EasingFunction easingFunction = ArkConfig.getEasingFunctionFrom(config.transition_type); 3 float easingDuration = Math.max(0, config.transition_duration); 4 position = new TransitionVector3(easingFunction, easingDuration); 5 offsetY = new TransitionFloat(easingFunction, easingDuration);

Source: ArkChar.java

渲染时每帧推进过渡进度并应用到骨架:

java
1 protected void render() { 2 // Update skeleton position and geometry 3 position.reset(camera.getWidth() >> 1, position.end().y, position.end().z); 4 position.addProgress(Gdx.graphics.getDeltaTime()); 5 offsetY.addProgress(Gdx.graphics.getDeltaTime()); 6 skeleton.setPosition(position.now().x, position.now().y + offsetY.now()); 7 skeleton.setScaleX(position.now().z); 8 skeleton.updateWorldTransform();

Source: ArkChar.java

解读:

  • position 的三维含义是 (x, y, 面朝方向z),其中 z 通过 setScaleX 映射为水平镜像(正=朝右,负=朝左);
  • position.reset(...) 每帧把 x 锚定到画布中心 camera.getWidth() >> 1,只让 y 与 z 参与缓动——这样角色始终居中于画布,拖拽改变的是窗口位置而非角色相对画布位置;
  • 动画切换时(composer.onApply)会把当前动画的 mobility 与 offsetY 写入过渡终点,使不同动作的姿态差异也以缓动方式过渡:
java
1 composer = new AnimComposer(animationState) { 2 @Override 3 protected void onApply(AnimData playing) { 4 Logger.debug("Animation", "Apply " + playing); 5 // Sync skeleton position data 6 offsetY.reset(playing.animClip().type.offsetY * scale); 7 position.reset(position.end().x, position.end().y, playing.mobility() != 0 ? playing.mobility() : position.end().z); 8 } 9 };

Source: ArkChar.java

注意 playing.mobility() != 0 ? playing.mobility() : position.end().z:只有带位移的动画才会改变面朝方向;原地动画(mobility 为 0)保持当前朝向不变,避免待机时角色无故翻转。

配置选项

与鼠标交互 / 手动模式直接相关的用户配置(定义于 ArkConfig,由桌面端 SettingsModule 提供 UI):

选项类型默认说明
transition_typestring—拖拽/方向变化的缓动函数类型,经 ArkConfig.getEasingFunctionFrom 解析
transition_durationfloat—过渡时长(秒),Math.max(0, ...) 保证非负;0 表示瞬移无缓动
render_outlinestring—自动模式下的描边样式
render_outline_colorstring—自动模式下的描边颜色
render_outline_emphasisstring—手动模式专用的描边样式
render_outline_emphasis_colorstring—手动模式专用的描边颜色

API Reference

ArkChar.setAnimation(animData: AnimData): boolean

请求切换角色当前动画。

Parameters:

  • animData (AnimData): 目标动画数据,含剪辑、mobility、是否循环/严格模式等。

Returns: boolean — 是否成功提交给动画合成器(内部委托 composer.offer(animData))。

Source: ArkChar.java

InputApplicationAdaptor.touchDown(screenX, screenY, pointer, button): boolean

libGDX 输入回调,鼠标/触摸按下时触发,记录 lastActiveNanoTime 用于活跃状态判定。

Parameters:

  • screenX, screenY (int): 屏幕坐标
  • pointer, button (int): 指针与按钮编号

Source: InputApplicationAdaptor.java

ArkPets.onKeyDown(keycode: int): void

键盘按下回调;仅在手动模式(tray.keepAnim != null)下生效,用于切换到上一个/下一个锁定动画。

Source: ArkPets.java

失败模式与边界情况

  • keepAnim 指向不存在的动画:键盘切换时通过 if (data != null) 防御,切换失败时保持原锁定动画不变,不会崩溃。
  • 手动模式与自动行走互斥:边界检测条件 tray.keepAnim == null && willReachBorder(mobility) 确保自动转向只发生在自动模式;手动模式的翻转走独立分支,二者不会同时干预同一帧决策。
  • mobility 为 0 的动画:边界翻转、方向镜像同步均显式跳过(mobility() != 0 判断),避免原地动画被错误赋予方向语义。
  • 配置非法值:transition_duration 通过 Math.max(0, ...) 钳制,负时长不会破坏过渡逻辑。
  • 模型加载失败(与交互链路相关但属初始化阶段):ArkChar 构造中骨架加载异常会被包装为 RuntimeException("Launch ArkPets failed, ...") 抛出,窗口不会以半初始化状态进入交互循环。

Source: ArkChar.java

手动模式状态机

Loading diagram...

扩展点

  • 新增输入手势:在 InputApplicationAdaptor 中扩展回调,并在 ArkPets 主循环中参照 isLeftPressed() 的模式读取状态、用 behavior.xxxAnim(...) 或直接构造 AnimData 写入 tray.keepAnim 即可参与手动模式。
  • 自定义模式反馈:手动模式的判定只有一个布尔量 tray.keepAnim != null,可在此基础上叠加自定义渲染效果(透明度、附加粒子等),调用 ArkChar.setAlpha() / setOutlineColor()。
  • 行为动画定制:手动模式的行走/落地动画来自 Behavior 抽象(walkAnim(int) / dropped()),新行为模型只需重写这两个方法即可改变手动交互的表现。
  • 行为模型与随机动画决策(Behavior / GeneralBehavior / StochasticMatrix):见"动画与行为"章节相关页面
  • 动画剪辑与合成(AnimClip / AnimClipGroup / AnimComposer):见"动画系统"相关页面
  • 渲染管线与描边后处理(PostProcessRenderPass):见渲染章节
  • 托盘系统(MemberTray / MemberTrayImpl / HostTray):见桌面集成章节

Sources

(1 files)