aardio 文档

aardio 范例: 动画天气卡片

/*
动画天气卡片:plus + GDI+ 自绘范例。

此示例采用小游戏/动画常用的绘图约定:
1. 场景状态统一使用 96-DPI 逻辑坐标。
2. 绘制时使用 save → scale(dpiX,dpiY) → restore 切换到逻辑坐标。
3. 所有速度都使用“逻辑像素/秒”或“角度/秒”,并按 deltaMs 更新。
4. 限制异常大的帧间隔,避免窗口阻塞后粒子瞬移。
5. 静态背景按当前像素尺寸与 DPI 缓存;画笔、画刷、字体等对象复用。
6. 绘图函数不改变动画状态,也不在绘图阶段生成随机数。
*/
import win.ui;
/*DSG{{*/
var winform = win.form(text="动画天气卡片";right=360;bottom=535;max=false;min=false)
winform.add(
btnRain={cls="plus";text="雨";left=121;top=485;right=166;bottom=525;bgcolor=0xD9904A;border={radius=8};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);notify=1;z=3};
btnSnow={cls="plus";text="雪";left=171;top=485;right=216;bottom=525;bgcolor=0xC37C8E;border={radius=8};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);notify=1;z=4};
btnSunny={cls="plus";text="晴";left=74;top=485;right=119;bottom=525;bgcolor=0x0095FF;border={radius=8};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);notify=1;z=2};
btnWind={cls="plus";text="风";left=221;top=485;right=266;bottom=525;bgcolor=0x9AA626;border={radius=8};color=0xFFFFFF;font=LOGFONT(h=-14;weight=600);notify=1;z=5};
plus={cls="plus";left=18;top=20;right=338;bottom=470;border={color=0xD0D0D0;radius=20;width=2};notify=1;z=1}
)
/*}}*/

import sys.midiOut;
var midiOut = sys.midiOut();

//场景数据始终使用 96-DPI 逻辑坐标。窗体不可缩放时初始值即为设计尺寸。
var sceneWidth,sceneHeight = 320,450;
var weatherState = "rain";
var animTime = 0;//秒

var rainDrops = [];
var snowFlakes = [];
var clouds = [];
var sunRays = [];
var windLines = [];
var trees = [];
var leaves = [];
var splashes = [];

var lightning = {
    active=false;
    flashOpacity=0;
    bolts=[];
    nextFlash=0.45;//秒
    thunderDelay=0;
    playThunder=false;
};

var labels = {
    sunny="晴空万里";
    rain="雷雨交加";
    snow="雪花飘飘";
    wind="清风徐来";
};

var leafColors = [0xFF8BC34A,0xFFCDDC39,0xFFFFA726];
var treeLayers = [
    {offset=0;size=1;color=0xFF2E7D32},
    {offset=-0.25;size=0.8;color=0xFF388E3C},
    {offset=-0.45;size=0.6;color=0xFF43A047}
];

//动态对象共用可变画刷与画笔,避免每个粒子每帧创建 GDI+ 对象。
var sharedBrush = gdip.solidBrush(0xFFFFFFFF);
var sharedPen = gdip.pen(0xFFFFFFFF,1);
var labelFont = gdip.font("Microsoft YaHei UI",16,1/*_FontStyleBold*/,2/*_UnitPixel*/);
var labelFormat = gdip.stringFormat();
labelFormat.align = 1/*_StringAlignmentCenter*/;
labelFormat.lineAlign = 1/*_StringAlignmentCenter*/;
var labelBrush = gdip.solidBrush(0xFFFFFFFF);
var labelBackgroundBrush = gdip.solidBrush(0x60000000);

var clampByte = function(value){
    return math.clamp(math.floor(value),0,255);
}

var withAlpha = function(alpha,rgb){
    return (clampByte(alpha)<<24)|(rgb&0xFFFFFF);
}

var randomInt = function(minValue,maxValue){
    minValue = math.floor(minValue);
    maxValue = math.max(minValue,math.floor(maxValue));
    return math.random(minValue,maxValue);
}

var randomSeconds = function(minMs,maxMs){
    return math.random(minMs,maxMs)/1000;
}

var getLogicalSize = function(ctrl){
    var rc = ctrl.getClientRect();
    var dpiX,dpiY = winform.dpiScale(1,1);
    return rc.width/dpiX,rc.height/dpiY,dpiX,dpiY,rc;
}

//静态背景使用当前 DPI 下的原尺寸位图缓存。
var backgroundLayer;
var backgroundWidth,backgroundHeight,backgroundDpiX,backgroundDpiY,backgroundWeather;

var invalidateBackground = function(){
    if(backgroundLayer){
        backgroundLayer.delete();
        backgroundLayer = null;
    }
    backgroundWidth,backgroundHeight = null,null;
    backgroundDpiX,backgroundDpiY = null,null;
    backgroundWeather = null;
}

var ensureBackgroundLayer = function(pixelWidth,pixelHeight,dpiX,dpiY){
    if(pixelWidth<=0 || pixelHeight<=0) return;
    if(backgroundLayer
        && backgroundWidth==pixelWidth && backgroundHeight==pixelHeight
        && backgroundDpiX==dpiX && backgroundDpiY==dpiY
        && backgroundWeather==weatherState){
        return;
    }

    invalidateBackground();
    backgroundLayer = gdip.bitmap(pixelWidth,pixelHeight);

    var graphics = gdip.graphics(backgroundLayer);
    var saved = graphics.save();
    graphics.smoothingMode = 4/*_SmoothingModeAntiAlias*/;
    graphics.scale(dpiX,dpiY);

    var width,height = pixelWidth/dpiX,pixelHeight/dpiY;
    var topColor,bottomColor,groundColor;
    if(weatherState=="sunny"){
        topColor,bottomColor,groundColor = 0xFF4FC3F7,0xFF81C784,0xFF66BB6A;
    }
    elseif(weatherState=="rain"){
        topColor,bottomColor,groundColor = 0xFF455A64,0xFF37474F,0xFF455A64;
    }
    elseif(weatherState=="snow"){
        topColor,bottomColor,groundColor = 0xFF90A4AE,0xFFE8EAF6,0xFFFAFAFA;
    }
    else {
        topColor,bottomColor,groundColor = 0xFF64B5F6,0xFFA5D6A7,0xFF66BB6A;
    }

    var skyBrush = gdip.lineBrush(
        ::POINTF(0,0),::POINTF(0,height),
        topColor,bottomColor,0
    );
    graphics.fillRectangle(skyBrush,0,0,width,height);
    skyBrush.delete();

    var groundBrush = gdip.solidBrush(groundColor);
    graphics.fillRectangle(groundBrush,0,height-60,width,60);
    groundBrush.delete();

    graphics.restore(saved);
    graphics.delete();

    backgroundWidth,backgroundHeight = pixelWidth,pixelHeight;
    backgroundDpiX,backgroundDpiY = dpiX,dpiY;
    backgroundWeather = weatherState;
}

var clearWeatherObjects = function(){
    rainDrops = [];
    snowFlakes = [];
    clouds = [];
    sunRays = [];
    windLines = [];
    trees = [];
    leaves = [];
    splashes = [];
}

var initClouds = function(count,dark){
    clouds = [];
    for(i=1;count){
        var cloudWidth = randomInt(82,132);
        table.push(clouds,{
            x=randomInt(-cloudWidth,sceneWidth);
            y=randomInt(58,math.max(70,sceneHeight*0.38));
            width=cloudWidth;
            height=randomInt(40,62);
            speed=randomInt(10,28);//逻辑像素/秒
            opacity=dark ? randomInt(200,238) : randomInt(178,225);
            dark=dark;
        });
    }
}

var initSunRays = function(){
    sunRays = [];
    for(i=1;16){
        table.push(sunRays,{
            angle=(i-1)*22.5;
            length=randomInt(62,105);
            baseOpacity=randomInt(65,115);
            phase=math.random(0,628)/100;
        });
    }
}

var initRain = function(){
    rainDrops = [];
    splashes = [];
    var groundY = sceneHeight-60;
    for(i=1;78){
        table.push(rainDrops,{
            x=randomInt(0,sceneWidth+24);
            y=randomInt(-sceneHeight*0.45,groundY);
            speed=randomInt(430,720);//逻辑像素/秒
            drift=randomInt(35,55);
            length=randomInt(12,25);
            opacity=randomInt(105,205);
            width=randomInt(1,2);
        });
    }

    //溅水位置与相位只在初始化时随机,绘图阶段保持确定性。
    for(i=1;10){
        table.push(splashes,{
            x=randomInt(14,sceneWidth-14);
            phase=math.random(0,600)/1000;
            period=randomInt(420,680)/1000;
        });
    }
}

var initSnow = function(){
    snowFlakes = [];
    var groundY = sceneHeight-60;
    for(i=1;58){
        table.push(snowFlakes,{
            x=randomInt(0,sceneWidth);
            y=randomInt(-100,groundY);
            speed=randomInt(28,70);//逻辑像素/秒
            size=randomInt(4,11);
            swing=math.random(0,628)/100;
            swingSpeed=randomInt(8,24)/10;//弧度/秒
            driftSpeed=randomInt(12,25);//逻辑像素/秒
            opacity=randomInt(180,255);
            rotation=randomInt(0,360);
            rotSpeed=randomInt(-110,110);//度/秒
        });
    }
}

var initWindLines = function(){
    windLines = [];
    for(i=1;12){
        table.push(windLines,{
            x=randomInt(-180,sceneWidth);
            y=randomInt(75,sceneHeight-80);
            length=randomInt(52,118);
            speed=randomInt(240,430);//逻辑像素/秒
            opacity=randomInt(75,155);
            curve=randomInt(3,8);
            phase=math.random(0,628)/100;
        });
    }
}

var initTrees = function(){
    trees = [];
    var positions = [0.14,0.39,0.65,0.87];
    for(i,position in positions){
        table.push(trees,{
            x=sceneWidth*position;
            y=sceneHeight-50;
            height=randomInt(58,84);
            swing=math.random(0,628)/100;
            swingSpeed=randomInt(24,42)/10;//弧度/秒
        });
    }
}

var initLeaves = function(){
    leaves = [];
    for(i=1;12){
        table.push(leaves,{
            x=randomInt(-20,sceneWidth);
            y=randomInt(90,sceneHeight-85);
            speedX=randomInt(160,300);//逻辑像素/秒
            speedY=randomInt(-15,25);
            rotation=randomInt(0,360);
            rotSpeed=randomInt(150,360);//度/秒
            size=randomInt(7,13);
            color=randomInt(1,#leafColors);
            phase=math.random(0,628)/100;
        });
    }
}

var generateLightningBolt = function(startX,startY,endY){
    var bolt = [];
    bolt.branches = [];

    var x,y = startX,startY;
    var segments = randomInt(9,14);
    var segmentHeight = (endY-startY)/segments;
    table.push(bolt,{x=x;y=y});

    for(i=1;segments){
        y += segmentHeight;
        x += randomInt(-17,17);
        table.push(bolt,{x=x;y=y});

        if(randomInt(1,100)>72 && i>2 && i<segments-2){
            var branch = [{x=x;y=y}];
            var bx,by = x,y;
            var branchLength = randomInt(3,5);
            var direction = randomInt(1,2)==1 ? -1 : 1;
            for(j=1;branchLength){
                bx += randomInt(8,18)*direction;
                by += randomInt(10,22);
                table.push(branch,{x=bx;y=by});
            }
            table.push(bolt.branches,branch);
        }
    }

    return bolt;
}

var initLightning = function(){
    lightning = {
        active=false;
        flashOpacity=0;
        bolts=[];
        nextFlash=0.45;
        thunderDelay=0;
        playThunder=false;
    };
}

var triggerLightning = function(){
    lightning.active = true;
    lightning.flashOpacity = 210;
    lightning.bolts = [];

    var boltCount = randomInt(1,2);
    for(i=1;boltCount){
        var startX = randomInt(35,sceneWidth-35);
        var endY = randomInt(sceneHeight*0.58,sceneHeight*0.82);
        table.push(lightning.bolts,generateLightningBolt(startX,0,endY));
    }

    lightning.thunderDelay = randomSeconds(180,550);
    lightning.playThunder = true;
}

var playThunder = function(){
    if(midiOut){
        //短促离散音效;sys.midiOut 默认 autoNoteOff 会在时值结束后释放音符。
        midiOut.playAsync("changeInstrument(127),1____","C2",70);
    }
}

var setSelectedButton = function(weather){
    winform.btnSunny.disabled = weather=="sunny";
    winform.btnRain.disabled = weather=="rain";
    winform.btnSnow.disabled = weather=="snow";
    winform.btnWind.disabled = weather=="wind";
}

var switchWeather = function(weather){
    weatherState = weather;
    animTime = 0;
    clearWeatherObjects();
    invalidateBackground();
    setSelectedButton(weather);
    if(midiOut) midiOut.reset();

    if(weather=="sunny"){
        initClouds(3,false);
        initSunRays();
    }
    elseif(weather=="rain"){
        initClouds(4,true);
        initRain();
        initLightning();
    }
    elseif(weather=="snow"){
        initClouds(4,false);
        initSnow();
    }
    else {
        initClouds(3,false);
        initWindLines();
        initTrees();
        initLeaves();
    }
}

var drawCloud = function(graphics,cloud,opacity){
    var rgb = cloud.dark ? 0x6E7B8B : 0xFFFFFF;
    sharedBrush.color = withAlpha(opacity,rgb);

    var x,y,w,h = cloud.x,cloud.y,cloud.width,cloud.height;
    graphics.fillEllipse(sharedBrush,x,y+h*0.2,w*0.4,h*0.8);
    graphics.fillEllipse(sharedBrush,x+w*0.2,y-h*0.1,w*0.45,h);
    graphics.fillEllipse(sharedBrush,x+w*0.45,y+h*0.1,w*0.4,h*0.85);
    graphics.fillEllipse(sharedBrush,x+w*0.15,y+h*0.3,w*0.55,h*0.6);
}

var drawTree = function(graphics,tree){
    var swingOffset = math.sin(tree.swing)*14;
    var x,y,height = tree.x,tree.y,tree.height;

    sharedBrush.color = 0xFF5D4037;
    graphics.fillPolygon(sharedBrush,[
        ::POINTF(x-9,y),
        ::POINTF(x+9,y),
        ::POINTF(x+5+swingOffset*0.3,y-height*0.5),
        ::POINTF(x-5+swingOffset*0.3,y-height*0.5)
    ]);

    for(i,layer in treeLayers){
        var layerY = y-height*0.4+layer.offset*height;
        var layerWidth = 66*layer.size;
        var layerHeight = 52*layer.size;
        var offsetX = swingOffset*(0.48+i*0.18);
        sharedBrush.color = layer.color;
        graphics.fillEllipse(
            sharedBrush,
            x-layerWidth/2+offsetX,layerY-layerHeight/2,
            layerWidth,layerHeight
        );
    }
}

var drawSnowflake = function(graphics,flake){
    sharedBrush.color = withAlpha(flake.opacity,0xFFFFFF);
    graphics.fillEllipse(
        sharedBrush,
        flake.x-flake.size/2,flake.y-flake.size/2,
        flake.size,flake.size
    );

    if(flake.size>6){
        sharedPen.color = withAlpha(flake.opacity*0.6,0xFFFFFF);
        sharedPen.width = 1;
        for(i=0;2){
            var angle = math.rad(flake.rotation+i*60);
            var dx = math.cos(angle)*flake.size*0.42;
            var dy = math.sin(angle)*flake.size*0.42;
            graphics.drawLine(sharedPen,flake.x-dx,flake.y-dy,flake.x+dx,flake.y+dy);
        }
    }
}

var drawLightning = function(graphics,bolt,opacity){
    if(#bolt<2) return;

    sharedPen.color = withAlpha(opacity*0.3,0xE1F5FE);
    sharedPen.width = 12;
    graphics.drawLines(sharedPen,bolt);

    sharedPen.color = withAlpha(opacity*0.8,0xFFFFFF);
    sharedPen.width = 4;
    graphics.drawLines(sharedPen,bolt);

    sharedPen.color = withAlpha(opacity,0xFFFFFF);
    sharedPen.width = 2;
    graphics.drawLines(sharedPen,bolt);

    for(i,branch in bolt.branches){
        sharedPen.color = withAlpha(opacity*0.22,0xE1F5FE);
        sharedPen.width = 6;
        graphics.drawLines(sharedPen,branch);

        sharedPen.color = withAlpha(opacity*0.65,0xFFFFFF);
        sharedPen.width = 2;
        graphics.drawLines(sharedPen,branch);
    }
}

var drawSunny = function(graphics,width,height){
    var sunX,sunY = width*0.76,height*0.19;
    var sunRadius = 42;

    for(r=3;1;-1){
        var glowOpacity = 18+r*14;
        var glowSize = sunRadius+r*20;
        sharedBrush.color = withAlpha(glowOpacity,0xFFD54F);
        graphics.fillEllipse(
            sharedBrush,
            sunX-glowSize,sunY-glowSize,
            glowSize*2,glowSize*2
        );
    }

    for(i,ray in sunRays){
        var angle = math.rad(ray.angle+animTime*20);//度/秒
        var pulse = math.sin(animTime*2+ray.phase);
        var currentLength = ray.length+pulse*14;
        var opacity = ray.baseOpacity+pulse*32;
        var x2 = sunX+math.cos(angle)*currentLength;
        var y2 = sunY+math.sin(angle)*currentLength;

        sharedPen.color = withAlpha(opacity,0xFFD54F);
        sharedPen.width = 3;
        graphics.drawLine(sharedPen,sunX,sunY,x2,y2);
    }

    sharedBrush.color = 0xFFFFD54F;
    graphics.fillCircle(sharedBrush,sunX,sunY,sunRadius);

    sharedBrush.color = 0x80FFFFFF;
    graphics.fillEllipse(
        sharedBrush,
        sunX-sunRadius*0.62,sunY-sunRadius*0.62,
        sunRadius*0.82,sunRadius*0.58
    );
}

var drawRain = function(graphics,width,height,groundY){
    if(lightning.active && lightning.flashOpacity>30){
        for(i,bolt in lightning.bolts){
            drawLightning(graphics,bolt,lightning.flashOpacity);
        }
    }

    for(i,drop in rainDrops){
        var opacity = drop.opacity;
        if(lightning.flashOpacity>50){
            opacity = math.min(255,opacity+lightning.flashOpacity/2);
        }
        sharedPen.color = withAlpha(opacity,0xB3E5FC);
        sharedPen.width = drop.width;
        graphics.drawLine(
            sharedPen,
            drop.x,drop.y,
            drop.x-drop.length*0.2,drop.y+drop.length
        );
    }

    //水洼位置根据当前逻辑宽度均匀分布,不使用旧范例中超出卡片的固定坐标。
    for(i=1;4){
        var puddleWidth = math.min(58,width*0.18);
        var px = width*(i-0.5)/4-puddleWidth/2;
        var rippleOffset = math.sin(animTime*4+i)*2.5;
        var opacity = 0x60;
        if(lightning.flashOpacity>50){
            opacity = math.min(0xA0,opacity+lightning.flashOpacity/4);
        }

        sharedBrush.color = withAlpha(opacity,0x5C6BC0);
        graphics.fillEllipse(sharedBrush,px,groundY+14,puddleWidth,15);

        sharedPen.color = 0x407986CB;
        sharedPen.width = 1;
        graphics.drawEllipse(
            sharedPen,
            px+puddleWidth*0.22+rippleOffset,groundY+16,
            puddleWidth*0.56+rippleOffset*2,9
        );
    }

    for(i,splash in splashes){
        var phase = (animTime+splash.phase)%splash.period;
        var progress = phase/splash.period;
        if(progress<0.42){
            var localProgress = progress/0.42;
            var radius = localProgress*8;
            sharedPen.color = withAlpha(150*(1-localProgress),0xB3E5FC);
            sharedPen.width = 1;
            graphics.drawEllipse(
                sharedPen,
                splash.x-radius,groundY+5-radius*0.3,
                radius*2,math.max(1,radius*0.65)
            );
        }
    }
}

var drawSnow = function(graphics,width,height,groundY){
    for(i,flake in snowFlakes){
        drawSnowflake(graphics,flake);
    }

    var snowPoints = [::POINTF(0,height)];
    for(x=0;width;24){
        var snowHeight = 23+math.sin(x*0.035+animTime*0.8)*7;
        table.push(snowPoints,::POINTF(x,groundY-snowHeight));
    }
    table.push(snowPoints,::POINTF(width,groundY-20));
    table.push(snowPoints,::POINTF(width,height));

    sharedBrush.color = 0xFFFAFAFA;
    graphics.fillPolygon(sharedBrush,snowPoints);

    sharedPen.color = 0x55FFFFFF;
    sharedPen.width = 2;
    for(x=16;width-20;68){
        var highlightY = groundY-15+math.sin(x*0.05)*5;
        graphics.drawCurve(sharedPen,[
            x,highlightY,
            x+24,highlightY-3,
            x+50,highlightY+2
        ]);
    }
}

var drawWind = function(graphics,width,height){
    for(i,windLine in windLines){
        var points = [];
        for(j=0;5){
            var x = windLine.x+j*windLine.length/5;
            var y = windLine.y
                +math.sin(j*0.8+animTime*4+windLine.phase)*windLine.curve;
            table.push(points,x);
            table.push(points,y);
        }

        sharedPen.color = withAlpha(windLine.opacity,0xFFFFFF);
        sharedPen.width = 2;
        graphics.drawCurve(sharedPen,points);
    }

    for(i,tree in trees){
        drawTree(graphics,tree);
    }

    for(i,leaf in leaves){
        var angle = math.rad(leaf.rotation);
        var cosValue,sinValue = math.cos(angle),math.sin(angle);
        var halfWidth,halfHeight = leaf.size*0.5,leaf.size*0.34;
        var ux,uy = cosValue*halfWidth,sinValue*halfWidth;
        var vx,vy = -sinValue*halfHeight,cosValue*halfHeight;

        sharedBrush.color = leafColors[leaf.color];
        graphics.fillPolygon(sharedBrush,[
            ::POINTF(leaf.x+ux,leaf.y+uy),
            ::POINTF(leaf.x+vx,leaf.y+vy),
            ::POINTF(leaf.x-ux,leaf.y-uy),
            ::POINTF(leaf.x-vx,leaf.y-vy)
        ]);
    }
}

winform.plus.onDrawBackground = function(graphics,rc,backgroundColor,color){
    var width,height,dpiX,dpiY = getLogicalSize(owner);
    sceneWidth,sceneHeight = width,height;
    ensureBackgroundLayer(rc.width,rc.height,dpiX,dpiY);
    if(backgroundLayer) graphics.drawImage(backgroundLayer,rc.left,rc.top);

    //闪光是动态层,不写入静态缓存。
    if(weatherState=="rain" && lightning.flashOpacity>0){
        sharedBrush.color = withAlpha(lightning.flashOpacity,0xFFFFFF);
        graphics.fillRectangle(sharedBrush,rc);
    }

    return true;//阻止绘制默认背景色或背景图像。
}

winform.plus.onDrawForeground = function(graphics,rc,foregroundRect,foregroundColor,color,font){
    var dpiX,dpiY = winform.dpiScale(1,1);
    var width,height = rc.width/dpiX,rc.height/dpiY;
    var groundY = height-60;

    //完整隔离本次绘图的变换、剪切区与质量设置。
    var saved = graphics.save();
    graphics.smoothingMode = 4/*_SmoothingModeAntiAlias*/;
    graphics.textRenderingHint = 4/*_TextRenderingHintAntiAlias*/;
    graphics.scale(dpiX,dpiY);

    if(weatherState=="sunny"){
        drawSunny(graphics,width,height);
    }

    for(i,cloud in clouds){
        var opacity = cloud.opacity;
        if(weatherState=="rain" && lightning.flashOpacity>50){
            opacity = math.min(255,opacity+lightning.flashOpacity/3);
        }
        drawCloud(graphics,cloud,opacity);
    }

    if(weatherState=="rain"){
        drawRain(graphics,width,height,groundY);
    }
    elseif(weatherState=="snow"){
        drawSnow(graphics,width,height,groundY);
    }
    elseif(weatherState=="wind"){
        drawWind(graphics,width,height);
    }

    graphics.fillRectangle(labelBackgroundBrush,width/2-76,15,152,42);
    graphics.drawString(
        labels[weatherState],labelFont,
        ::RECTF(0,15,width,42),labelFormat,labelBrush
    );

    graphics.restore(saved);
    return true;//阻止绘制默认前景色或前景图像。
}

//全部速度按秒计算;dt 最大只取 50ms,避免窗口阻塞后对象穿越整个画面。
winform.plus.onAnimation = function(state,beginning,change,timestamp,duration,deltaMs){
    if(deltaMs===null) deltaMs = 16;
    var dt = math.min(math.max(deltaMs,0),50)/1000;
    animTime += dt;

    var width,height = getLogicalSize(owner);
    sceneWidth,sceneHeight = width,height;
    var groundY = height-60;

    for(i,cloud in clouds){
        cloud.x += cloud.speed*dt;
        if(cloud.x>width+20){
            cloud.x = -cloud.width-20;
            cloud.y = randomInt(58,math.max(70,height*0.38));
        }
    }

    if(weatherState=="rain"){
        for(i,drop in rainDrops){
            drop.y += drop.speed*dt;
            drop.x -= drop.drift*dt;
            if(drop.y>groundY+12 || drop.x < -drop.length){
                drop.y = randomInt(-110,-20);
                drop.x = randomInt(0,width+40);
            }
        }

        if(lightning.flashOpacity>0){
            lightning.flashOpacity -= 520*dt;
            if(lightning.flashOpacity<=0){
                lightning.flashOpacity = 0;
                lightning.active = false;
            }
        }

        if(lightning.playThunder){
            lightning.thunderDelay -= dt;
            if(lightning.thunderDelay<=0){
                playThunder();
                lightning.playThunder = false;
            }
        }

        lightning.nextFlash -= dt;
        if(lightning.nextFlash<=0){
            triggerLightning();
            lightning.nextFlash = randomSeconds(2400,5200);
        }
    }
    elseif(weatherState=="snow"){
        for(i,flake in snowFlakes){
            flake.y += flake.speed*dt;
            flake.swing += flake.swingSpeed*dt;
            flake.x += math.sin(flake.swing)*flake.driftSpeed*dt;
            flake.rotation = (flake.rotation+flake.rotSpeed*dt)%360;

            if(flake.y>groundY-4 || flake.x < -20 || flake.x > width+20){
                flake.y = randomInt(-70,-10);
                flake.x = randomInt(0,width);
            }
        }
    }
    elseif(weatherState=="wind"){
        for(i,windLine in windLines){
            windLine.x += windLine.speed*dt;
            if(windLine.x>width+30){
                windLine.x = -windLine.length-30;
                windLine.y = randomInt(75,height-80);
            }
        }

        for(i,tree in trees){
            tree.swing = (tree.swing+tree.swingSpeed*dt)%(math.pi*2);
        }

        for(i,leaf in leaves){
            leaf.x += leaf.speedX*dt;
            leaf.y += (
                leaf.speedY+math.sin(animTime*4+leaf.phase)*20
            )*dt;
            leaf.rotation = (leaf.rotation+leaf.rotSpeed*dt)%360;

            if(leaf.x>width+20 || leaf.y<55 || leaf.y>groundY+10){
                leaf.x = randomInt(-45,-12);
                leaf.y = randomInt(90,groundY-20);
            }
        }
    }

    //startAnimation 自动刷新;只有返回恒等于 null(或无返回值)才停止。
    return true;
}

winform.btnSunny.oncommand = function(){
    switchWeather("sunny");
}

winform.btnRain.oncommand = function(){
    switchWeather("rain");
}

winform.btnSnow.oncommand = function(){
    switchWeather("snow");
}

winform.btnWind.oncommand = function(){
    switchWeather("wind");
    if(midiOut){
        //切换时只播放一次短促阵风提示,不把持续音当背景音乐。
        midiOut.playAsync("changeInstrument(122), 1____,5____,3____,6____","C3",100);
    }
}

winform.btnSunny.skin({
    background={default=0xFFFF9500;hover=0xFFFF7F00;active=0xFFE56900;disabled=0xFFC0C0C0};
    color={default=0xFFFFFFFF};
});

winform.btnRain.skin({
    background={default=0xFF4A90D9;hover=0xFF3A7FC8;active=0xFF2A6EB7;disabled=0xFFC0C0C0};
    color={default=0xFFFFFFFF};
});

winform.btnSnow.skin({
    background={default=0xFF8E7CC3;hover=0xFF7A68AF;active=0xFF66549B;disabled=0xFFC0C0C0};
    color={default=0xFFFFFFFF};
});

winform.btnWind.skin({
    background={default=0xFF26A69A;hover=0xFF1E958A;active=0xFF16847A;disabled=0xFFC0C0C0};
    color={default=0xFFFFFFFF};
});

//统一释放缓存和原生资源;MIDI 设备关闭前先停止全部音符。
winform.onDestroy = function(){
    invalidateBackground();
    sharedBrush.delete();
    sharedPen.delete();
    labelFont.delete();
    labelFormat.delete();
    labelBrush.delete();
    labelBackgroundBrush.delete();

    if(midiOut){
        midiOut.reset();
        midiOut.close();
    }
}

switchWeather("rain");
winform.plus.startAnimation(16);
winform.show();
win.loopMessage();

Markdown 格式