aardio 文档
aardio 范例: 自绘小游戏 - 黑洞吞噬
import win.ui;
/*DSG{{*/
var winform = win.form(text="自绘小游戏 - 黑洞吞噬";right=800;bottom=600;bgcolor=0x000000)
winform.add(
gameBox={cls="plus";left=0;top=0;right=800;bottom=600;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/
import sys.midiOut;
var midiOut = sys.midiOut();
var game = {
state = "menu";
score = 0;
timeLeft = 90;
lastTime = 0;
highScore = 0;
};
var hole = {
x = 400;
y = 300;
targetX = 400;
targetY = 300;
radius = 35;
baseRadius = 35;
rotation = 0;
glowIntensity = 1;
pulsePhase = 0;
eatPulse = 0;
};
var items = {};
var maxItems = 45;
var particles = {};
var floatingTexts = {};
var itemColors = {
0xFFe74c3c, 0xFF3498db, 0xFF2ecc71, 0xFFf39c12,
0xFF9b59b6, 0xFF1abc9c, 0xFFe91e63, 0xFF00bcd4,
0xFFff6b6b, 0xFF4ecdc4, 0xFFffe66d, 0xFF95e1d3
};
var stars = {};
var starsInitialized = false;
var initStars = function(width, height) {
stars = {};
for(i=1; 120) {
table.push(stars, {
x = math.random(0, width);
y = math.random(0, height);
size = math.random(1, 2);
alpha = math.random(30, 100);
typ = 1;
});
}
for(i=1; 25) {
table.push(stars, {
x = math.random(0, width);
y = math.random(0, height);
size = math.random(2, 4);
alpha = math.random(150, 255);
typ = 2;
});
}
starsInitialized = true;
};
var createParticles = function(x, y, color, count) {
for(i=1; count) {
var angle = math.random() * math.pi * 2;
var speed = math.random(3, 8);
table.push(particles, {
x = x; y = y;
vx = math.cos(angle) * speed;
vy = math.sin(angle) * speed;
life = 1;
decay = math.random(0.02, 0.05);
size = math.random(3, 8);
color = color;
});
}
};
var createFloatingText = function(x, y, text, color) {
table.push(floatingTexts, {
x = x; y = y;
text = text;
color = color;
life = 1;
vy = -2;
});
};
var spawnItem = function(rcWidth, rcHeight) {
var minSize = math.max(10, hole.baseRadius * 0.25);
var maxSize = math.min(55, hole.baseRadius * 1.3);
//如果参数 1 大于 参数 2 则 math.random 会报错 'interval is empty'
var size = minSize<=maxSize ? math.random(minSize, maxSize) : maxSize;
var x, y;
var safeDistance = hole.baseRadius + size + 80;
for(attempt=1; 15) {
x = math.random(size + 30, rcWidth - size - 30);
y = math.random(size + 30, rcHeight - size - 30);
var dx = x - hole.x;
var dy = y - hole.y;
if(math.sqrt(dx*dx + dy*dy) > safeDistance) break;
}
return {
x = x; y = y;
size = size;
color = itemColors[math.random(1, #itemColors)];
shape = math.random(1, 5);
state = "normal";
absorbProgress = 0;
rotation = math.random(0, 360);
rotSpeed = math.random(-3, 3);
points = math.floor(size * size * 0.5);
bobPhase = math.random(0, 628) / 100;
};
};
var initItems = function(rcWidth, rcHeight) {
items = {};
particles = {};
floatingTexts = {};
for(i=1; maxItems) {
table.push(items, spawnItem(rcWidth, rcHeight));
}
};
var resetGame = function(rcWidth, rcHeight) {
game.score = 0;
game.timeLeft = 90;
game.lastTime = time.tick();
hole.x = rcWidth / 2;
hole.y = rcHeight / 2;
hole.targetX = hole.x;
hole.targetY = hole.y;
hole.radius = 35;
hole.baseRadius = 35;
hole.rotation = 0;
hole.glowIntensity = 1;
hole.eatPulse = 0;
initItems(rcWidth, rcHeight);
};
var boxIntersects = function(item) {
var margin = 10;
var r = hole.baseRadius;
return !(item.x + item.size < hole.x - r - margin ||
item.x - item.size > hole.x + r + margin ||
item.y + item.size < hole.y - r - margin ||
item.y - item.size > hole.y + r + margin);
};
winform.gameBox.onDrawBackground = function(graphics,rc,backgroundColor,foregroundColor){
var width = rc.right;
var height = rc.bottom;
var p1 = ::POINTF(0, 0);
var p2 = ::POINTF(width, height);
var bgBrush = gdip.lineBrush(p1, p2, 0xFF080818, 0xFF1a0a2e, 0);
graphics.fillRectangle(bgBrush, rc);
bgBrush.delete();
if(!starsInitialized) {
initStars(width, height);
}
var starBrush = gdip.solidBrush(0xFFFFFFFF);
for(i=1; #stars) {
var s = stars[i];
var color = s.typ == 1 ? 0xFFFFFF : 0xE0E8FF;
starBrush.color = (s.alpha << 24) | color;
graphics.fillEllipse(starBrush, s.x, s.y, s.size, s.size);
}
starBrush.delete();
var gridPen = gdip.pen(0x10FFFFFF, 1);
for(x=0; width; 80) {
graphics.drawLine(gridPen, x, 0, x, height);
}
for(y=0; height; 80) {
graphics.drawLine(gridPen, 0, y, width, y);
}
gridPen.delete();
};
var drawParticles = function(graphics) {
var brush = gdip.solidBrush(0xFFFFFFFF);
for(i=1; #particles) {
var p = particles[i];
brush.color = (math.floor(p.life * 255) << 24) | (p.color & 0x00FFFFFF);
graphics.fillEllipse(brush, p.x - p.size/2, p.y - p.size/2, p.size, p.size);
}
brush.delete();
};
var drawFloatingTexts = function(graphics) {
if(#floatingTexts == 0) return;
var family = gdip.family("Tahoma");
var font = family.createFont(14, 1, 3);
var format = gdip.stringformat();
format.align = 1;
var measureRect = ::RECTF(0, 0, 200, 100);
var measured = graphics.measureString("+999", font, measureRect, format);
var textH = measured.height;
var brush = gdip.solidBrush(0xFFFFFFFF);
for(i=1; #floatingTexts) {
var ft = floatingTexts[i];
brush.color = (math.floor(ft.life * 255) << 24) | (ft.color & 0x00FFFFFF);
graphics.drawString(ft.text, font, ::RECTF(ft.x - 50, ft.y, 100, textH + 5), format, brush);
}
brush.delete();
font.delete();
format.delete();
family.delete();
};
var drawItem = function(graphics, item) {
var x = item.x;
var y = item.y;
var size = item.size;
var alpha = 0xFF;
if(item.state == "normal") {
y = y + math.sin(item.bobPhase) * 3;
}
elseif(item.state == "absorbing") {
var progress = item.absorbProgress;
size = item.size * math.max(0.05, 1 - progress * 0.95);
alpha = math.floor(255 * (1 - progress * 0.8));
var ease = progress * progress * progress;
x = item.x + (hole.x - item.x) * ease;
y = item.y + (hole.y - item.y) * ease;
}
var color = (alpha << 24) | (item.color & 0x00FFFFFF);
var brush = gdip.solidBrush(color);
if(item.shape == 1) {
graphics.fillEllipse(brush, x - size, y - size, size * 2, size * 2);
}
elseif(item.shape == 2) {
graphics.translateTransform(x, y);
graphics.rotateTransform(item.rotation);
graphics.fillRectangle(brush, ::RECT(-size, -size, size, size));
graphics.resetTransform();
}
elseif(item.shape == 3) {
var pts = {};
for(i=0; 2) {
var angle = item.rotation * math.pi / 180 + i * math.pi * 2 / 3 - math.pi / 2;
pts[i+1] = ::POINTF(x + size * math.cos(angle), y + size * math.sin(angle));
}
graphics.fillPolygon(brush, pts);
}
elseif(item.shape == 4) {
graphics.translateTransform(x, y);
graphics.rotateTransform(item.rotation);
var pts = {::POINTF(0, -size), ::POINTF(size*0.7, 0), ::POINTF(0, size), ::POINTF(-size*0.7, 0)};
graphics.fillPolygon(brush, pts);
graphics.resetTransform();
}
else {
var pts = {};
for(i=0; 4) {
var angle = item.rotation * math.pi / 180 + i * math.pi * 2 / 5 - math.pi / 2;
pts[i+1] = ::POINTF(x + size * math.cos(angle), y + size * math.sin(angle));
}
graphics.fillPolygon(brush, pts);
}
brush.delete();
};
var drawHole = function(graphics) {
var cx = hole.x;
var cy = hole.y;
var breathe = 1 + math.sin(hole.pulsePhase) * 0.03;
var eatEffect = 1 + hole.eatPulse * 0.3;
var r = hole.baseRadius * breathe * eatEffect;
hole.radius = r;
if(hole.eatPulse > 0.5) {
var waveR = r * (1 + (1 - hole.eatPulse) * 2);
var wavePen = gdip.pen((math.floor(hole.eatPulse * 100) << 24) | 0xFFFFFF, 2);
graphics.drawEllipse(wavePen, cx - waveR, cy - waveR, waveR * 2, waveR * 2);
wavePen.delete();
}
var glowPath = gdip.path();
var glowSize = r * 2 * (1 + hole.eatPulse * 0.5);
glowPath.addEllipse(cx - glowSize, cy - glowSize, glowSize * 2, glowSize * 2);
var glowBrush = gdip.pathGradientBrush(glowPath);
glowBrush.centerColor = 0x00000000;
glowBrush.surroundColors = {(math.floor(0x30 * hole.glowIntensity) << 24) | 0x9b59b6};
graphics.fillPath(glowBrush, glowPath);
glowBrush.delete();
glowPath.delete();
var holePath = gdip.path();
holePath.addEllipse(cx - r, cy - r, r * 2, r * 2);
var holeBrush = gdip.pathGradientBrush(holePath);
holeBrush.centerColor = 0xFF000000;
holeBrush.surroundColors = {0xFF2a1a4a};
graphics.fillPath(holeBrush, holePath);
holeBrush.delete();
holePath.delete();
var innerPath = gdip.path();
innerPath.addEllipse(cx - r * 0.6, cy - r * 0.6, r * 1.2, r * 1.2);
var innerBrush = gdip.pathGradientBrush(innerPath);
innerBrush.centerColor = 0xFF000000;
innerBrush.surroundColors = {0xFF0a0a15};
graphics.fillPath(innerBrush, innerPath);
innerBrush.delete();
innerPath.delete();
var ringAlpha = math.min(0xFF, math.floor((0x90 + hole.eatPulse * 0x60) * hole.glowIntensity));
var pen1 = gdip.pen((ringAlpha << 24) | 0x9b59b6, 3);
var pen2 = gdip.pen((ringAlpha << 24) | 0x00d4ff, 2);
var pen3 = gdip.pen((ringAlpha << 24) | 0xff6b6b, 1.5);
for(i=0; 2) {
graphics.drawArc(pen1, cx-r*0.92, cy-r*0.92, r*1.84, r*1.84, hole.rotation+i*120, 50);
}
for(i=0; 3) {
graphics.drawArc(pen2, cx-r*0.75, cy-r*0.75, r*1.5, r*1.5, -hole.rotation*1.3+i*90, 35);
}
for(i=0; 5) {
graphics.drawArc(pen3, cx-r*0.5, cy-r*0.5, r, r, hole.rotation*2+i*60, 20);
}
pen1.delete(); pen2.delete(); pen3.delete();
};
var drawUI = function(graphics, rcWidth, rcHeight) {
var family = gdip.family("Tahoma");
var font = family.createFont(18, 1, 3);
var smallFont = family.createFont(12, 0, 3);
var format = gdip.stringformat();
var measured = graphics.measureString("⭐ 测试", font, ::RECTF(0,0,500,200), format);
var fontH = measured.height;
var smallMeasured = graphics.measureString("最高: 999", smallFont, ::RECTF(0,0,500,200), format);
var smallH = smallMeasured.height;
var uiH = fontH + smallH + 16;
var uiBg = gdip.solidBrush(0x50000000);
graphics.fillRectangle(uiBg, ::RECT(0, 0, rcWidth, uiH));
uiBg.delete();
format.align = 0;
var scoreBrush = gdip.solidBrush(0xFFf39c12);
graphics.drawString("⭐ " + game.score, font, ::RECTF(15, 8, 250, fontH+5), format, scoreBrush);
scoreBrush.delete();
if(game.highScore > 0) {
var highBrush = gdip.solidBrush(0x80FFFFFF);
graphics.drawString("最高: " + game.highScore, smallFont, ::RECTF(15, 8+fontH, 150, smallH+5), format, highBrush);
highBrush.delete();
}
format.align = 2;
var timeColor = game.timeLeft <= 10 ? 0xFFe74c3c : (game.timeLeft <= 30 ? 0xFFf39c12 : 0xFF2ecc71);
var timeBrush = gdip.solidBrush(timeColor);
var timeText = string.format("⏱ %02d:%02d", math.floor(game.timeLeft/60), math.floor(game.timeLeft)%60);
graphics.drawString(timeText, font, ::RECTF(rcWidth-150, 8, 140, fontH+5), format, timeBrush);
timeBrush.delete();
format.align = 1;
var sizeBrush = gdip.solidBrush(0xFF9b59b6);
graphics.drawString("◉ " + math.floor(hole.baseRadius), font, ::RECTF(rcWidth/2-80, 8, 160, fontH+5), format, sizeBrush);
sizeBrush.delete();
font.delete(); smallFont.delete(); format.delete(); family.delete();
};
var drawMenu = function(graphics, rcWidth, rcHeight) {
var family = gdip.family("Tahoma");
var titleFont = family.createFont(42, 1, 3);
var subFont = family.createFont(16, 0, 3);
var format = gdip.stringformat();
format.align = 1;
var titleH = graphics.measureString("黑洞吞噬", titleFont, ::RECTF(0,0,rcWidth,200), format).height;
var subH = graphics.measureString("测试", subFont, ::RECTF(0,0,rcWidth,200), format).height;
var y = rcHeight * 0.25;
var glowBrush = gdip.solidBrush(0x309b59b6);
graphics.drawString("黑洞吞噬", titleFont, ::RECTF(3, y+3, rcWidth, titleH+10), format, glowBrush);
glowBrush.delete();
var titleBrush = gdip.solidBrush(0xFFFFFFFF);
graphics.drawString("黑洞吞噬", titleFont, ::RECTF(0, y, rcWidth, titleH+10), format, titleBrush);
titleBrush.delete();
y = y + titleH + 15;
var subBrush = gdip.solidBrush(0xFF9b59b6);
graphics.drawString("BLACK HOLE", subFont, ::RECTF(0, y, rcWidth, subH+5), format, subBrush);
subBrush.delete();
y = y + subH * 2.5;
var tipBrush = gdip.solidBrush(0xAAFFFFFF);
graphics.drawString("移动鼠标控制黑洞", subFont, ::RECTF(0, y, rcWidth, subH+5), format, tipBrush);
y = y + subH * 1.4;
graphics.drawString("吞噬比你小的物体来成长壮大", subFont, ::RECTF(0, y, rcWidth, subH+5), format, tipBrush);
tipBrush.delete();
y = y + subH * 2.5;
var blink = math.floor(180 + math.sin(time.tick()/300) * 75);
var startBrush = gdip.solidBrush((blink << 24) | 0x2ecc71);
graphics.drawString("[ 单击开始游戏 ]", subFont, ::RECTF(0, y, rcWidth, subH+5), format, startBrush);
startBrush.delete();
if(game.highScore > 0) {
y = y + subH * 2;
var highBrush = gdip.solidBrush(0xFFf39c12);
graphics.drawString("最高分: " + game.highScore, subFont, ::RECTF(0, y, rcWidth, subH+5), format, highBrush);
highBrush.delete();
}
titleFont.delete(); subFont.delete(); format.delete(); family.delete();
};
var drawGameOver = function(graphics, rcWidth, rcHeight) {
var maskBrush = gdip.solidBrush(0x90000000);
graphics.fillRectangle(maskBrush, ::RECT(0, 0, rcWidth, rcHeight));
maskBrush.delete();
var family = gdip.family("Tahoma");
var titleFont = family.createFont(36, 1, 3);
var scoreFont = family.createFont(48, 1, 3);
var subFont = family.createFont(18, 0, 3);
var format = gdip.stringformat();
format.align = 1;
var titleH = graphics.measureString("时间到", titleFont, ::RECTF(0,0,rcWidth,200), format).height;
var scoreH = graphics.measureString("999", scoreFont, ::RECTF(0,0,rcWidth,200), format).height;
var subH = graphics.measureString("测试", subFont, ::RECTF(0,0,rcWidth,200), format).height;
var y = rcHeight * 0.22;
var titleBrush = gdip.solidBrush(0xFFe74c3c);
graphics.drawString("时间到!", titleFont, ::RECTF(0, y, rcWidth, titleH+10), format, titleBrush);
titleBrush.delete();
y = y + titleH + 20;
var scoreBrush = gdip.solidBrush(0xFFf39c12);
graphics.drawString(tostring(game.score), scoreFont, ::RECTF(0, y, rcWidth, scoreH+10), format, scoreBrush);
scoreBrush.delete();
y = y + scoreH + 10;
var labelBrush = gdip.solidBrush(0xAAFFFFFF);
graphics.drawString("最终得分", subFont, ::RECTF(0, y, rcWidth, subH+5), format, labelBrush);
labelBrush.delete();
y = y + subH * 1.5;
if(game.score >= game.highScore && game.score > 0) {
var newBrush = gdip.solidBrush(0xFF2ecc71);
graphics.drawString("✨ 新纪录!", subFont, ::RECTF(0, y, rcWidth, subH+5), format, newBrush);
newBrush.delete();
y = y + subH * 1.5;
}
y = y + subH * 0.5;
var blink = math.floor(180 + math.sin(time.tick()/300) * 75);
var tipBrush = gdip.solidBrush((blink << 24) | 0xFFFFFF);
graphics.drawString("[ 单击重新开始 ]", subFont, ::RECTF(0, y, rcWidth, subH+5), format, tipBrush);
tipBrush.delete();
titleFont.delete(); scoreFont.delete(); subFont.delete(); format.delete(); family.delete();
};
winform.gameBox.onDrawContent = function(graphics,rc,txtColor,rcContent,foregroundColor,font){
graphics.smoothingMode = 4;
var rcWidth = rc.right;
var rcHeight = rc.bottom;
if(game.state == "menu") {
hole.x = rcWidth / 2;
hole.y = rcHeight * 0.85;
hole.baseRadius = 50;
drawHole(graphics);
drawMenu(graphics, rcWidth, rcHeight);
return;
}
for(i=1; #items) {
if(items[i].state == "normal") drawItem(graphics, items[i]);
}
drawParticles(graphics);
for(i=1; #items) {
if(items[i].state == "absorbing") drawItem(graphics, items[i]);
}
drawHole(graphics);
drawFloatingTexts(graphics);
if(game.state == "playing") drawUI(graphics, rcWidth, rcHeight);
if(game.state == "gameover") drawGameOver(graphics, rcWidth, rcHeight);
};
/*
在 onMouseMove 里密集重绘是不必要的而且对性能影响较大。
一种方法是用另一个 plus 控件调用 orphanWindow(true) 方法创建悬浮透明窗口模拟拖动物体(参考拼图游戏范例)。
另一种方法是让动画定时器负责更新画面,当前游戏就是这么做的。
*/
winform.gameBox.onMouseMove = function(wParam, lParam) {
var x, y = win.getMessagePos(lParam);
if(game.state == "playing") {
hole.targetX = x;
hole.targetY = y;
}
};
winform.gameBox.onMouseUp = function(wParam, lParam) {
var rc = owner.getClientRect();
if(game.state == "menu" || game.state == "gameover") {
if(game.score > game.highScore) game.highScore = game.score;
game.state = "playing";
resetGame(rc.right, rc.bottom);
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_,1'__", "C4", 100);
winform.gameBox.cursor = ::User32.LoadCursor(,32515/*_IDC_CROSS*/);
}
};
winform.gameBox.onAnimation = function(state) {
var rc = owner.getClientRect();
var rcWidth = rc.right;
var rcHeight = rc.bottom;
hole.rotation = (hole.rotation + 3) % 360;
hole.pulsePhase = hole.pulsePhase + 0.08;
if(hole.glowIntensity > 1) hole.glowIntensity = math.max(1, hole.glowIntensity - 0.02);
if(hole.eatPulse > 0) hole.eatPulse = math.max(0, hole.eatPulse - 0.08);
for(i=#particles; 1; -1) {
var p = particles[i];
p.x = p.x + p.vx; p.y = p.y + p.vy;
p.life = p.life - p.decay;
if(p.life <= 0) table.remove(particles, i);
}
for(i=#floatingTexts; 1; -1) {
var ft = floatingTexts[i];
ft.y = ft.y + ft.vy;
ft.life = ft.life - 0.02;
if(ft.life <= 0) table.remove(floatingTexts, i);
}
if(game.state != "playing") return true;
var now = time.tick();
if(game.lastTime == 0) game.lastTime = now;
game.timeLeft = game.timeLeft - (now - game.lastTime) / 1000;
game.lastTime = now;
if(game.timeLeft <= 0) {
game.timeLeft = 0;
game.state = "gameover";
if(game.score > game.highScore) game.highScore = game.score;
if(midiOut) midiOut.playAsync("changeInstrument(47), '5_,'4_,'3_,'2_,'1__", "C3", 180);
winform.gameBox.cursor = ::User32.LoadCursor(,32512/*_IDC_ARROW*/);
return true;
}
//带阻尼的弹性感,系数越大跟随鼠标的速度越快
hole.x = hole.x + (hole.targetX - hole.x) * 0.60;
hole.y = hole.y + (hole.targetY - hole.y) * 0.60;
hole.x = math.max(hole.baseRadius, math.min(rcWidth - hole.baseRadius, hole.x));
hole.y = math.max(hole.baseRadius, math.min(rcHeight - hole.baseRadius, hole.y));
var toRemove = {};
var absorbed = 0;
for(i=1; #items) {
var item = items[i];
if(item.state == "absorbing") {
item.absorbProgress = item.absorbProgress + 0.1;
item.rotation = item.rotation + 15;
if(item.absorbProgress >= 1) {
table.push(toRemove, i);
game.score = game.score + item.points;
hole.eatPulse = math.min(1, hole.eatPulse + 0.6);
hole.glowIntensity = math.min(2.5, hole.glowIntensity + 0.5);
hole.baseRadius = hole.baseRadius + item.size * (0.04 + item.size/hole.baseRadius * 0.02);
createParticles(hole.x, hole.y, item.color, 12);
createFloatingText(hole.x, hole.y - hole.radius - 20, "+" + item.points, 0xFFFFFF);
absorbed = absorbed + 1;
}
}
elseif(boxIntersects(item)) {
var dx = item.x - hole.x;
var dy = item.y - hole.y;
if(math.sqrt(dx*dx + dy*dy) < hole.baseRadius * 0.8 && item.size <= hole.baseRadius * 0.75) {
item.state = "absorbing";
item.absorbProgress = 0;
hole.eatPulse = math.min(1, hole.eatPulse + 0.2);
if(midiOut) {
var pitch = item.size < 20 ? "C5" : (item.size < 35 ? "C4" : "C3");
midiOut.playAsync("changeInstrument(115), 5_,3_", pitch, 80);
}
}
}
}
if(absorbed >= 2 && midiOut) midiOut.playAsync("changeInstrument(10), 5_,1'_,3'_", "C5", 60);
for(i=#toRemove; 1; -1) {
table.remove(items, toRemove[i]);
table.push(items, spawnItem(rcWidth, rcHeight));
}
for(i=1; #items) {
var item = items[i];
if(item.state == "normal") {
item.rotation = item.rotation + item.rotSpeed;
item.bobPhase = item.bobPhase + 0.05;
}
}
return true;
};
//人眼就会感觉流畅即可,过多而不必要的密集绘制反而会起到副作用
winform.gameBox.startAnimation(22);
winform.show();
win.loopMessage();
Markdown 格式