left0ver's picture
Publish com.left0ver.agentcade 0.2.0
21e8d36 verified
Raw History Blame Contribute Delete
4.11 kB
"""常用的设备效果:点头、摇头、颜文字表情、逐笔画图和进度条。
这些是游戏端的辅助函数,把一个效果拼成几次机器人 SDK 调用,通过游戏的 self.robot 发出,
和直接调用 SDK 一样:用到动作要在 SKILL.md 里声明 motion,用到屏幕要声明 expression.runtime.v3。
"""
import asyncio
import logging
import math
import time
from agentcade.expressions import expression_vector, segmented_progress_vector
from agentcade.eyes import HIDDEN_EYES, STANDBY_EYES
from agentcade.robot_calls import RobotProxy
logger = logging.getLogger(__name__)
# 点头、摇头依次转到的(水平角, 俯仰角),从正前方出发再回到正前方。
GESTURES = {
"nod": ((90, 115), (90, 105), (90, 115)),
"shake_head": ((90, 115), (105, 115), (75, 115), (90, 115)),
}
# 颜文字表情的图案编号。
EXPRESSIONS = {"happy": "009", "sorry": "141"}
# 逐笔画图时每一步之间的间隔秒数。
DRAW_INTERVAL = 0.16
async def gesture(robot: RobotProxy, name: str) -> None:
"""点头(nod)或摇头(shake_head)。主 Agent 等动作做完再执行后面的指令和播报。"""
for pan, tilt in GESTURES[name]:
await robot.motion.move_to(pan_deg=pan, tilt_deg=tilt, duration_ms=350, profile="ease_in_out")
async def show(robot: RobotProxy, path: str) -> None:
"""屏幕只显示这个矢量图案,眼睛藏起来。"""
await robot.expression_runtime.start("standby", **HIDDEN_EYES, tag="none", custom_vector_path=path)
async def standby(robot: RobotProxy) -> None:
"""屏幕回到待机的眼睛。"""
await robot.expression_runtime.start("standby", **STANDBY_EYES)
async def react(robot: RobotProxy, expression: str, gesture_name: str, *, then: str | None = None) -> None:
"""对用户的回答做出反应:显示开心(happy)或抱歉(sorry)的颜文字,同时点头或摇头;
做完后屏幕换回 then 这个图案,没有时回到待机的眼睛。"""
await show(robot, expression_vector(EXPRESSIONS[expression]))
await gesture(robot, gesture_name)
await (show(robot, then) if then else standby(robot))
async def draw(robot: RobotProxy, steps: list[str], *, interval: float = DRAW_INTERVAL) -> None:
"""逐笔画出图案:每一项是一步的完整矢量路径,最后一步留在屏幕上。"""
await show(robot, steps[0])
for path in steps[1:]:
await asyncio.sleep(interval)
await robot.expression_runtime.update(custom_vector_path=path)
class Progress:
"""显示进度条,预计 seconds 秒走完,用在耗时的操作外面:
async with Progress(self.robot, 30):
...
操作正常完成时进度条走满;出错或者被取消时停在当前位置。
"""
def __init__(self, robot: RobotProxy, seconds: float):
if not math.isfinite(seconds) or seconds <= 0:
raise ValueError("预计秒数必须是有限正数")
self.robot, self.seconds = robot, seconds
self.task: asyncio.Task | None = None
async def __aenter__(self):
await show(self.robot, segmented_progress_vector(0))
self.task = asyncio.create_task(self.animate())
return self
async def animate(self) -> None:
started = time.monotonic()
for step in range(1, 10):
await asyncio.sleep(max(0.0, started + step * self.seconds / 10 - time.monotonic()))
await show(self.robot, segmented_progress_vector(step))
async def __aexit__(self, kind, error, traceback):
self.task.cancel()
# 用 wait 等它停下,不吞掉外面对游戏的取消。
await asyncio.wait({self.task})
if not self.task.cancelled() and self.task.exception() is not None:
# 进度条只是提示,显示失败不影响正在做的事。
logger.warning("进度条没有显示完整", exc_info=self.task.exception())
if kind is None:
await show(self.robot, segmented_progress_vector(10))
await asyncio.sleep(0.25)
return False