aardio 文档
aardio 范例: 鹈鹕骑自行车 2D 动画 - aardio GDI+ 自绘
import win.ui;
/*DSG{{*/
var winform = win.form(text="鹈鹕骑自行车 2D 动画 - aardio GDI+ 自绘";right=794;bottom=437;bgcolor=0xFFFFFF;border="dialog frame";max=false)
winform.add(
canvas={cls="canvas";left=0;top=0;right=795;bottom=438;anchors={left=1;top=1;right=1;bottom=1};z=1}
)
/*}}*/
// ---------------- 动画状态变量 ----------------
var angle = 0; // 脚踏板/车轮旋转角度(弧度)
var bgPos = 0; // 马路与视差背景平移位置(逻辑像素)
var pedalSpeed = 9; // 脚踏板旋转速度(弧度/秒)
var backgroundSpeed = 300; // 背景平移速度(逻辑像素/秒)
// ---------------- GDI+ 逐帧绘制回调 ----------------
winform.canvas.onDrawForeground = function(graphics, rc, foregroundRect, foregroundColor, color, font){
// 获取当前窗口的 DPI 缩放倍数
var dpiX, dpiY = winform.dpiScale(1, 1);
// 启用 GDI+ 画板全局 DPI 自动缩放,所有线条、形状、画笔粗细均自动缩放
graphics.scale(dpiX, dpiY);
// 将实际像素尺寸换算为设计时的逻辑坐标尺寸
var width = rc.width / dpiX;
var height = rc.height / dpiY;
// --- 1. 天空与地面背景 ---
var skyBrush = gdip.solidBrush(0xFF87CEEB); // 天蓝色
graphics.fillRect(skyBrush, 0, 0, width, 320);
skyBrush.delete();
var groundBrush = gdip.solidBrush(0xFF7CFC00); // 草绿色
graphics.fillRect(groundBrush, 0, 320, width, height - 320);
groundBrush.delete();
// --- 2. 沥青马路 ---
var roadBrush = gdip.solidBrush(0xFF505050); // 深灰色
graphics.fillRect(roadBrush, 0, 350, width, 100);
roadBrush.delete();
// --- 3. 马路中心动态虚线 ---
var lineBrush = gdip.solidBrush(0xFFFFFFFF);
var dashWidth = 40;
var dashGap = 30;
var totalDash = dashWidth + dashGap;
var offsetX = (bgPos % totalDash);
for(x = -totalDash; width + totalDash; totalDash){
graphics.fillRect(lineBrush, x - offsetX, 395, dashWidth, 10);
}
lineBrush.delete();
// --- 4. 远景白云(慢速视差平移)---
var cloudBrush = gdip.solidBrush(0xFFFFFFFF);
var cloudOffset = (bgPos * 0.3) % (width + 200);
for(i = 0; 2; 1){
var cx = (i * 400 - cloudOffset) + 100;
if(cx < -100) cx += width + 200;
graphics.fillEllipse(cloudBrush, cx, 60, 80, 40);
graphics.fillEllipse(cloudBrush, cx + 30, 45, 70, 50);
graphics.fillEllipse(cloudBrush, cx + 60, 60, 80, 40);
}
cloudBrush.delete();
// --- 5. 远景树木(中速视差平移)---
var treeTrunkBrush = gdip.solidBrush(0xFF8B4513);
var treeLeavesBrush = gdip.solidBrush(0xFF228B22);
var treeOffset = (bgPos * 0.6) % 300;
for(x = -100; width + 200; 300){
var tx = x - treeOffset;
graphics.fillRect(treeTrunkBrush, tx + 35, 270, 20, 50);
graphics.fillEllipse(treeLeavesBrush, tx, 200, 90, 80);
}
treeTrunkBrush.delete();
treeLeavesBrush.delete();
// ---------------- 自行车与鹈鹕坐标参数 ----------------
var bob = math.sin(angle * 2) * 4; // 骑行时的上下颠簸幅度
var bikeX = 400; // 自行车中心 X
var bikeY = 360; // 车轮接地点 Y
var wheelR = 45; // 车轮半径
var rearWheelX = bikeX - 110;
var rearWheelY = bikeY - wheelR;
var frontWheelX = bikeX + 110;
var frontWheelY = bikeY - wheelR;
var crankX = bikeX - 10;
var crankY = bikeY - wheelR;
var crankR = 22; // 脚踏板旋转半径
var seatX = bikeX - 45;
var seatY = bikeY - wheelR - 85;
var handleX = bikeX + 60;
var handleY = bikeY - wheelR - 95;
var thickPen = gdip.pen(0xFF202020, 5);
var framePen = gdip.pen(0xFFE63946, 5); // 经典红车架
var chromePen = gdip.pen(0xFFCCCCCC, 3);
// --- 绘制车轮闭包 ---
var drawWheel = function(wx, wy){
// 轮胎
var tirePen = gdip.pen(0xFF1E1E1E, 8);
graphics.drawCircle(tirePen, wx, wy, wheelR);
tirePen.delete();
// 金属轮辋
graphics.drawCircle(chromePen, wx, wy, wheelR - 4);
// 轮毂
var hubBrush = gdip.solidBrush(0xFF505050);
graphics.fillCircle(hubBrush, wx, wy, 6);
hubBrush.delete();
// 动态辐条(随 angle 角度旋转)
var spokePen = gdip.pen(0xFFAAAAAA, 1.5);
for(spokeDeg = 0; 360; 45){
var rad = (spokeDeg * math.pi / 180) + angle;
var sx = wx + math.cos(rad) * (wheelR - 4);
var sy = wy + math.sin(rad) * (wheelR - 4);
graphics.drawLine(spokePen, wx, wy, sx, sy);
}
spokePen.delete();
};
drawWheel(rearWheelX, rearWheelY);
drawWheel(frontWheelX, frontWheelY);
// --- 绘制自行车车架 ---
// 后叉
graphics.drawLine(framePen, rearWheelX, rearWheelY, crankX, crankY);
graphics.drawLine(framePen, rearWheelX, rearWheelY, seatX, seatY);
// 三角车架
var headTubeX = bikeX + 45;
var headTubeY = bikeY - wheelR - 75;
graphics.drawLine(framePen, crankX, crankY, seatX, seatY);
graphics.drawLine(framePen, seatX, seatY, headTubeX, headTubeY);
graphics.drawLine(framePen, crankX, crankY, headTubeX, headTubeY);
// 前叉与车把
graphics.drawLine(framePen, frontWheelX, frontWheelY, handleX, handleY);
graphics.drawLine(thickPen, headTubeX, headTubeY, handleX, handleY);
graphics.drawLine(thickPen, handleX, handleY, handleX + 15, handleY - 15);
// 车座鞍座
var seatBrush = gdip.solidBrush(0xFF111111);
graphics.fillEllipse(seatBrush, seatX - 18, seatY - 8, 36, 12);
seatBrush.delete();
// --- 左右脚踏板坐标计算 ---
var p1X = crankX + math.cos(angle) * crankR; // 近侧脚踏
var p1Y = crankY + math.sin(angle) * crankR;
var p2X = crankX - math.cos(angle) * crankR; // 远侧脚踏
var p2Y = crankY - math.sin(angle) * crankR;
// --- 鹈鹕脚腿绘制闭包 ---
var drawLeg = function(hipX, hipY, footX, footY, isFront){
var legPen = isFront ? gdip.pen(0xFFFF8C00, 6) : gdip.pen(0xFFCC7000, 5);
// 膝盖向前方折弯的运动骨骼
var midX = (hipX + footX) / 2 + 18;
var midY = (hipY + footY) / 2 - 5;
graphics.drawLine(legPen, hipX, hipY, midX, midY);
graphics.drawLine(legPen, midX, midY, footX, footY);
legPen.delete();
// 鸭掌/脚蹼
var footBrush = isFront ? gdip.solidBrush(0xFFFF8C00) : gdip.solidBrush(0xFFCC7000);
graphics.fillEllipse(footBrush, footX - 10, footY - 3, 20, 8);
footBrush.delete();
};
var pelicanHipX = seatX + 5;
var pelicanHipY = seatY - 20 + bob;
// 1. 远侧腿与曲柄 (位于后方图层)
drawLeg(pelicanHipX, pelicanHipY, p2X, p2Y, false);
graphics.drawLine(chromePen, crankX, crankY, p2X, p2Y);
var pedalBrush = gdip.solidBrush(0xFF333333);
graphics.fillRect(pedalBrush, p2X - 8, p2Y - 3, 16, 6);
// --- 2. 鹈鹕身体组件 (Pelican Body) ---
var bodyX = seatX + 10;
var bodyY = seatY - 45 + bob;
// 尾巴羽毛
var featherBrush = gdip.solidBrush(0xFFE0E6ED);
graphics.fillEllipse(featherBrush, bodyX - 45, bodyY + 5, 30, 18);
// 白色羽毛躯干
var pelicanBodyBrush = gdip.solidBrush(0xFFF8F9FA);
graphics.fillEllipse(pelicanBodyBrush, bodyX - 25, bodyY - 30, 55, 65);
// 脖子与头部
var headX = bodyX + 25;
var headY = bodyY - 70;
var neckPen = gdip.pen(0xFFF8F9FA, 22);
graphics.drawCurve(neckPen, [
[bodyX + 5, bodyY - 15],
[bodyX + 15, bodyY - 45],
[headX - 10, headY + 15],
[headX, headY]
]);
neckPen.delete();
// 头部圆润结构
graphics.fillEllipse(pelicanBodyBrush, headX - 18, headY - 20, 36, 36);
// --- 3. 核心特征:长嘴喙与巨型喉囊 (Beak & Pouch) ---
var beakX = headX + 10;
var beakY = headY - 5;
// 坚硬的黄色长上嘴喙
var upperBeakBrush = gdip.solidBrush(0xFFFFB703);
graphics.fillPolygon(upperBeakBrush, [
[beakX, beakY - 8],
[beakX + 75, beakY + 2],
[beakX + 72, beakY + 8],
[beakX, beakY + 5]
]);
upperBeakBrush.delete();
// 嘴尖钩 Hook
var hookBrush = gdip.solidBrush(0xFFE07A5F);
graphics.fillCircle(hookBrush, beakX + 74, beakY + 6, 4);
hookBrush.delete();
// 巨大膨胀喉囊(带有骑行呼吸晃动感)
var pouchPuff = math.sin(angle * 3) * 3;
var pouchBrush = gdip.solidBrush(0xFFF4A261);
graphics.fillClosedCurve2(pouchBrush, 0.35, [
[beakX + 5, beakY + 5],
[beakX + 35, beakY + 45 + pouchPuff],
[beakX + 68, beakY + 10]
]);
pouchBrush.delete();
// 嘴缝线
var mouthPen = gdip.pen(0xFFD4A373, 2);
graphics.drawLine(mouthPen, beakX, beakY + 2, beakX + 72, beakY + 5);
mouthPen.delete();
// 眼睛与高光
var eyeX = headX + 2;
var eyeY = headY - 8;
var eyeOuterBrush = gdip.solidBrush(0xFFFFFFFF);
var eyePupilBrush = gdip.solidBrush(0xFF111111);
graphics.fillCircle(eyeOuterBrush, eyeX, eyeY, 6);
graphics.fillCircle(eyePupilBrush, eyeX, eyeY - 1, 3);
var eyeGlintBrush = gdip.solidBrush(0xFFFFFFFF);
graphics.fillCircle(eyeGlintBrush, eyeX - 1, eyeY - 3, 1);
eyeOuterBrush.delete();
eyePupilBrush.delete();
eyeGlintBrush.delete();
// 翅膀(扶在车把上)
var wingBrush = gdip.solidBrush(0xFFE9ECEF);
var wingPen = gdip.pen(0xFFCED4DA, 2);
var wingPoints = [
[bodyX - 10, bodyY - 15],
[bodyX + 20, bodyY - 5],
[handleX, handleY - 5], // 延伸扶在车把位置
[bodyX + 15, bodyY + 15]
];
graphics.fillClosedCurve2(wingBrush, 0.35, wingPoints);
graphics.drawClosedCurve2(wingPen, 0.35, wingPoints);
wingBrush.delete();
wingPen.delete();
// 绅士小礼帽 (Hat)
var hatBrush = gdip.solidBrush(0xFF1D3557);
graphics.fillRect(hatBrush, headX - 15, headY - 22, 30, 4); // 帽檐
graphics.fillRect(hatBrush, headX - 10, headY - 36, 20, 14); // 帽体
var hatRibbonBrush = gdip.solidBrush(0xFFE63946); // 红色丝带
graphics.fillRect(hatRibbonBrush, headX - 10, headY - 25, 20, 3);
hatBrush.delete();
hatRibbonBrush.delete();
// --- 4. 近侧脚踏板与腿 (前方图层) ---
var chainringBrush = gdip.solidBrush(0xFF888888); // 牙盘
graphics.fillCircle(chainringBrush, crankX, crankY, 16);
chainringBrush.delete();
graphics.drawLine(chromePen, crankX, crankY, p1X, p1Y);
graphics.fillRect(pedalBrush, p1X - 8, p1Y - 3, 16, 6);
pedalBrush.delete();
drawLeg(pelicanHipX, pelicanHipY, p1X, p1Y, true);
// 释放图层常用笔刷
pelicanBodyBrush.delete();
featherBrush.delete();
thickPen.delete();
framePen.delete();
chromePen.delete();
// 还原画板变换矩阵,清除 DPI 缩放
graphics.resetTransform();
};
// ---------------- 动画定时器更新逻辑 ----------------
winform.canvas.onAnimation = function(prevState, beginning, change, timestamp, duration, deltaMs){
var deltaSeconds = deltaMs / 1000;
angle = (angle + pedalSpeed * deltaSeconds) % (math.pi * 2);
bgPos += backgroundSpeed * deltaSeconds;
return true; // 保持动画持续循环
};
// 启动动画循环(约 16毫秒/帧)
winform.canvas.startAnimation(16);
winform.onEraseBkgnd = lambda () 0;
winform.show();
win.loopMessage();
Markdown 格式