aardio 文档
aardio 范例: 自绘小游戏 - 反应堆 Stack
import win.ui;
/*DSG{{*/
var winform = win.form(text="自绘小游戏 - 反应堆 Stack";right=400;bottom=600;border="dialog frame";max=false)
winform.add(
plus={cls="plus";left=0;top=0;right=400;bottom=600;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/
var STATE_START = 0;
var STATE_PLAYING = 1;
var STATE_GAMEOVER = 2;
var colors = [
0xFF00b894, 0xFF00cec9, 0xFF0984e3, 0xFF6c5ce7,
0xFFa29bfe, 0xFFfd79a8, 0xFFe17055, 0xFFfdcb6e,
0xFF55efc4, 0xFF81ecec, 0xFF74b9ff, 0xFFf39c12
];
var game = {
state = STATE_START;
score = 0;
highScore = 0;
level = 1;
blockWidth = 180;
blockHeight = 28;
blockSpeed = 3;
blockDirection = 1;
currentBlock = null;
stack = {};
particles = {};
glowEffects = {};
viewOffset = 0;
targetViewOffset = 0;
perfectCount = 0;
cutBlock = null;
};
var renderedBlock = null;
import sys.midiOut;
var midiOut = sys.midiOut();
var getCanvasSize = function() {
var rc = winform.plus.getClientRect();
return rc.right, rc.bottom;
}
var toScreenY = function(gameY) {
return gameY + game.viewOffset;
}
var createParticles = function(x, y, width, height, color, count, particleType) {
for(i=1; count) {
var vxRange = particleType == "perfect" ? 10 : 5;
var vyBase = particleType == "perfect" ? -8 : -2;
var particle = {
x = x + math.random() * width;
y = y + math.random() * height;
vx = (math.random() - 0.5) * vxRange;
vy = (math.random() - 0.5) * 4 + vyBase;
size = math.random() * 5 + 3;
color = color;
alpha = 1.0;
gravity = particleType == "perfect" ? 0.25 : 0.4;
rotation = math.random() * 6.28;
rotationSpeed = (math.random() - 0.5) * 0.3;
};
table.push(game.particles, particle);
}
}
var createGlowEffect = function(x, y, width, height, color) {
table.push(game.glowEffects, {
x = x; y = y; width = width; height = height;
color = color; alpha = 0.8; scale = 1.0;
});
}
var updateParticles = function() {
var w, h = getCanvasSize();
for(i=#game.particles; 1; -1) {
var p = game.particles[i];
p.x = p.x + p.vx;
p.y = p.y + p.vy;
p.vy = p.vy + p.gravity;
p.alpha = p.alpha - 0.025;
p.rotation = p.rotation + p.rotationSpeed;
p.size = p.size * 0.98;
if(p.alpha <= 0 || toScreenY(p.y) > h + 50) {
table.remove(game.particles, i);
}
}
}
var updateGlowEffects = function() {
for(i=#game.glowEffects; 1; -1) {
var g = game.glowEffects[i];
g.alpha = g.alpha - 0.08;
g.scale = g.scale + 0.08;
if(g.alpha <= 0) table.remove(game.glowEffects, i);
}
}
var initGame = function() {
var w, h = getCanvasSize();
var dpiX, dpiY = winform.dpiScale(1,1);
game.score = 0;
game.level = 1;
game.blockWidth = 180 * dpiX;
game.blockHeight = 28 * dpiY;
game.blockSpeed = 5 * dpiX;
game.blockDirection = 1;
game.viewOffset = 0;
game.targetViewOffset = 0;
game.perfectCount = 0;
game.cutBlock = null;
game.particles = {};
game.glowEffects = {};
game.stack = {};
var baseBlock = {
x = (w - game.blockWidth) / 2;
y = h - game.blockHeight - 80 * dpiY;
width = game.blockWidth;
color = colors[1];
};
table.push(game.stack, baseBlock);
game.currentBlock = {
x = 0;
y = baseBlock.y - game.blockHeight;
width = game.blockWidth;
color = colors[2];
};
game.state = STATE_PLAYING;
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_", "C4", 80);
}
var placeBlock = function() {
if(!game.currentBlock) return;
var w, h = getCanvasSize();
var dpiX, dpiY = winform.dpiScale(1,1);
var topBlock = game.stack[#game.stack];
var current = game.currentBlock;
var overlapLeft = math.max(current.x, topBlock.x);
var overlapRight = math.min(current.x + current.width, topBlock.x + topBlock.width);
var overlapWidth = overlapRight - overlapLeft;
if(overlapWidth <= 0) {
game.state = STATE_GAMEOVER;
game.cutBlock = {
x = current.x; y = current.y;
width = current.width; color = current.color;
vy = 0; vx = game.blockDirection * 2 * dpiX; rotation = 0;
};
game.currentBlock = null;
if(game.score > game.highScore) game.highScore = game.score;
if(midiOut) midiOut.play("changeInstrument(47), '5_,'4_,'3__", "C3", 200);
winform.plus.redraw();
return;
}
var isPerfect = math.abs(current.x - topBlock.x) < 6 * dpiX;
var actualBlockX = current.x;
var actualBlockWidth;
if(isPerfect) {
actualBlockWidth = topBlock.width;
game.perfectCount = game.perfectCount + 1;
game.score = game.score + 10 + game.perfectCount * 5;
createParticles(current.x, current.y, topBlock.width, game.blockHeight, 0xFFFFFF00, 15, "perfect");
createParticles(current.x, current.y, topBlock.width, game.blockHeight, 0xFFFF6B6B, 10, "perfect");
if(midiOut) midiOut.play("changeInstrument(10), 1_,3_,5_,1'_", "C5", 80);
} else {
game.perfectCount = 0;
game.score = game.score + 10;
actualBlockX = overlapLeft;
actualBlockWidth = overlapWidth;
var cutX = current.x < topBlock.x ? current.x : overlapRight;
var cutWidth = current.x < topBlock.x ? (topBlock.x - current.x) : (current.x + current.width - overlapRight);
if(cutWidth > 2) {
game.cutBlock = {
x = cutX; y = current.y; width = cutWidth; color = current.color;
vy = 0; vx = (cutX < overlapLeft) ? -2 * dpiX : 2 * dpiX; rotation = 0;
};
createParticles(cutX, current.y, cutWidth, game.blockHeight, current.color, 12, "cut");
}
if(midiOut) midiOut.play("changeInstrument(115), 5_", "C5", 60);
}
var newBlock = { x = actualBlockX; y = current.y; width = actualBlockWidth; color = current.color };
table.push(game.stack, newBlock);
createGlowEffect(actualBlockX, current.y, actualBlockWidth, game.blockHeight, current.color);
game.level = game.level + 1;
game.blockSpeed = math.min((5 + game.level * 0.25) * dpiX, 16 * dpiX);
var newBlockScreenY = toScreenY(newBlock.y);
var idealScreenY = h * 0.4;
if(newBlockScreenY < idealScreenY) {
game.targetViewOffset = game.targetViewOffset + (idealScreenY - newBlockScreenY);
}
var colorIndex = ((#game.stack) % #colors) + 1;
game.currentBlock = {
x = game.blockDirection > 0 ? -actualBlockWidth : w;
y = newBlock.y - game.blockHeight;
width = actualBlockWidth;
color = colors[colorIndex];
};
game.blockDirection = -game.blockDirection;
winform.plus.redraw();
}
var drawBlock = function(graphics, x, y, width, height, color, isMoving) {
var path = gdip.path();
path.addRoundRect(x, y, width, height, 5);
var brush = gdip.solidBrush(color);
graphics.fillPath(brush, path);
brush.delete();
if(width > 10) {
var highlightPath = gdip.path();
highlightPath.addRoundRect(x + 2, y + 2, width - 4, height / 3, 3);
var highlightBrush = gdip.solidBrush(isMoving ? 0x50FFFFFF : 0x30FFFFFF);
graphics.fillPath(highlightBrush, highlightPath);
highlightBrush.delete();
highlightPath.delete();
}
if(isMoving) {
var glowPen = gdip.pen(0x60FFFFFF, 2);
graphics.drawPath(glowPen, path);
glowPen.delete();
}
path.delete();
}
winform.plus.onDrawBackground = function(graphics,rc,backgroundColor,color){
var brush = gdip.lineBrush(::POINTF(0, 0), ::POINTF(0, rc.bottom), 0xFF1a1a2e, 0xFF0f0f1a);
graphics.fillRectangle(brush, rc);
brush.delete();
}
winform.plus.onDrawForeground = function(graphics,rc,foregroundRect,foregroundColor,color,font){
graphics.smoothingMode = 4;
var w, h = rc.right, rc.bottom;
var dpiX, dpiY = winform.dpiScale(1,1);
var family = gdip.family("Tahoma");
var format = gdip.stringFormat();
format.align = 1;
format.lineAlign = 1;
if(game.state == STATE_START) {
var titleFont = family.createFont(36, 1, 3/*_UnitPoint*/);
var layoutRect = ::RECTF(0, 0, w, h);
var titleBounds = graphics.measureString("反 应 堆", titleFont, layoutRect, format);
var shadowBrush = gdip.solidBrush(0x40000000);
graphics.drawString("反 应 堆", titleFont, ::RECTF(3, h*0.22 + 3, w, titleBounds.height), format, shadowBrush);
shadowBrush.delete();
var titleBrush = gdip.lineBrush(::POINTF(0, h*0.22), ::POINTF(0, h*0.22 + titleBounds.height), 0xFFffeaa7, 0xFFfdcb6e);
graphics.drawString("反 应 堆", titleFont, ::RECTF(0, h*0.22, w, titleBounds.height), format, titleBrush);
titleBrush.delete();
titleFont.delete();
var subFont = family.createFont(14, 0, 3/*_UnitPoint*/);
var subBounds = graphics.measureString("层层堆叠,挑战极限", subFont, layoutRect, format);
var subBrush = gdip.solidBrush(0xBBFFFFFF);
graphics.drawString("层层堆叠,挑战极限", subFont, ::RECTF(0, h*0.32, w, subBounds.height), format, subBrush);
subBrush.delete();
subFont.delete();
for(i=1; 5) {
var demoW = 180 * dpiX - (i-1) * 15 * dpiX;
var demoX = w/2 - demoW/2 + math.sin(i * 0.8) * 15 * dpiX;
var demoY = h*0.42 + (i-1) * 33 * dpiY;
drawBlock(graphics, demoX, demoY, demoW, 28 * dpiY, colors[i], false);
}
var hintAlpha = math.floor(math.abs(math.sin(time.tick() / 500)) * 180 + 75);
var hintFont = family.createFont(16, 0, 3/*_UnitPoint*/);
var hintBounds = graphics.measureString("单击或按空格键开始", hintFont, layoutRect, format);
var hintBrush = gdip.solidBrush((hintAlpha << 24) | 0xFFFFFF);
graphics.drawString("单击或按空格键开始", hintFont, ::RECTF(0, h*0.78, w, hintBounds.height), format, hintBrush);
hintBrush.delete();
hintFont.delete();
} elseif(game.state == STATE_PLAYING || game.state == STATE_GAMEOVER) {
for(i=1; #game.stack) {
var block = game.stack[i];
var screenY = toScreenY(block.y);
if(screenY > -game.blockHeight && screenY < h + game.blockHeight) {
drawBlock(graphics, block.x, screenY, block.width, game.blockHeight, block.color, false);
}
}
for(i=1; #game.glowEffects) {
var g = game.glowEffects[i];
var screenY = toScreenY(g.y);
var expandX = (g.scale - 1) * g.width / 2;
var expandY = (g.scale - 1) * g.height / 2;
var alpha = math.floor(g.alpha * 200);
var path = gdip.path();
path.addRoundRect(g.x - expandX, screenY - expandY, g.width + expandX*2, g.height + expandY*2, 8);
var brush = gdip.solidBrush((alpha << 24) | 0xFFFFFF);
graphics.fillPath(brush, path);
brush.delete();
path.delete();
}
if(game.currentBlock) {
var block = game.currentBlock;
var screenY = toScreenY(block.y);
drawBlock(graphics, block.x, screenY, block.width, game.blockHeight, block.color, true);
if(#game.stack > 0) {
var topBlock = game.stack[#game.stack];
var topScreenY = toScreenY(topBlock.y);
if(topScreenY > screenY + game.blockHeight) {
var projBrush = gdip.solidBrush(0x15FFFFFF);
graphics.fillRectangle(projBrush, ::RECTF(block.x, screenY + game.blockHeight, block.width, topScreenY - screenY - game.blockHeight));
projBrush.delete();
}
}
}
if(game.cutBlock) {
var cut = game.cutBlock;
var screenY = toScreenY(cut.y);
if(screenY < h + 100) {
graphics.save();
graphics.translateTransform(cut.x + cut.width/2, screenY + game.blockHeight/2);
graphics.rotateTransform(cut.rotation * 57.3);
var alpha = math.max(0, math.min(255, 255 - (screenY - h/2) * 0.5));
var path = gdip.path();
path.addRoundRect(-cut.width/2, -game.blockHeight/2, cut.width, game.blockHeight, 5);
var brush = gdip.solidBrush((alpha << 24) | (cut.color & 0x00FFFFFF));
graphics.fillPath(brush, path);
brush.delete();
path.delete();
graphics.restore();
}
}
for(i=1; #game.particles) {
var p = game.particles[i];
var screenY = toScreenY(p.y);
var alpha = math.floor(p.alpha * 255);
graphics.save();
graphics.translateTransform(p.x, screenY);
graphics.rotateTransform(p.rotation * 57.3);
var brush = gdip.solidBrush((alpha << 24) | (p.color & 0x00FFFFFF));
graphics.fillRectangle(brush, ::RECTF(-p.size/2, -p.size/2, p.size, p.size));
brush.delete();
graphics.restore();
}
var layoutRect = ::RECTF(0, 0, w, h);
var scoreFont = family.createFont(28, 1, 3/*_UnitPoint*/);
var scoreBounds = graphics.measureString(tostring(game.score), scoreFont, layoutRect, format);
var shadowBrush = gdip.solidBrush(0x40000000);
graphics.drawString(tostring(game.score), scoreFont, ::RECTF(2, 20, w, scoreBounds.height), format, shadowBrush);
shadowBrush.delete();
var scoreBrush = gdip.solidBrush(0xFFFFFFFF);
graphics.drawString(tostring(game.score), scoreFont, ::RECTF(0, 18, w, scoreBounds.height), format, scoreBrush);
scoreBrush.delete();
scoreFont.delete();
var levelFont = family.createFont(12, 0, 3/*_UnitPoint*/);
var levelText = "第 " ++ game.level ++ " 层";
var levelBounds = graphics.measureString(levelText, levelFont, layoutRect, format);
var levelBrush = gdip.solidBrush(0x99FFFFFF);
graphics.drawString(levelText, levelFont, ::RECTF(0, 18 + scoreBounds.height + 5, w, levelBounds.height), format, levelBrush);
levelBrush.delete();
levelFont.delete();
if(game.perfectCount >= 2) {
var comboScale = 1 + math.sin(time.tick() / 150) * 0.1;
var comboFont = family.createFont(16 * comboScale, 1, 3/*_UnitPoint*/);
var comboText = "✨ 完美 x" ++ game.perfectCount ++ " ✨";
var comboBounds = graphics.measureString(comboText, comboFont, layoutRect, format);
var comboBrush = gdip.solidBrush(0xFFFFD700);
graphics.drawString(comboText, comboFont, ::RECTF(0, 18 + scoreBounds.height + levelBounds.height + 15, w, comboBounds.height), format, comboBrush);
comboBrush.delete();
comboFont.delete();
}
if(game.state == STATE_GAMEOVER) {
var maskBrush = gdip.solidBrush(0xA0000000);
graphics.fillRectangle(maskBrush, rc);
maskBrush.delete();
var titleFont = family.createFont(36, 1, 3/*_UnitPoint*/);
var titleBounds = graphics.measureString("游戏结束", titleFont, layoutRect, format);
var titleBrush = gdip.solidBrush(0xFFe74c3c);
graphics.drawString("游戏结束", titleFont, ::RECTF(0, h*0.32, w, titleBounds.height), format, titleBrush);
titleBrush.delete();
titleFont.delete();
var resultFont = family.createFont(24, 1, 3/*_UnitPoint*/);
var resultText = "得分: " ++ game.score;
var resultBounds = graphics.measureString(resultText, resultFont, layoutRect, format);
var resultBrush = gdip.solidBrush(0xFFFFFFFF);
graphics.drawString(resultText, resultFont, ::RECTF(0, h*0.42, w, resultBounds.height), format, resultBrush);
resultBrush.delete();
resultFont.delete();
if(game.score >= game.highScore && game.score > 0) {
var newRecordFont = family.createFont(16, 0, 3/*_UnitPoint*/);
var newRecordBounds = graphics.measureString("✨ 新纪录!", newRecordFont, layoutRect, format);
var newRecordBrush = gdip.solidBrush(0xFFFFD700);
graphics.drawString("✨ 新纪录!", newRecordFont, ::RECTF(0, h*0.50, w, newRecordBounds.height), format, newRecordBrush);
newRecordBrush.delete();
newRecordFont.delete();
}
var hintAlpha = math.floor(math.abs(math.sin(time.tick() / 400)) * 150 + 100);
var hintFont = family.createFont(14, 0, 3/*_UnitPoint*/);
var hintBounds = graphics.measureString("单击重新开始", hintFont, layoutRect, format);
var hintBrush = gdip.solidBrush((hintAlpha << 24) | 0xFFFFFF);
graphics.drawString("单击重新开始", hintFont, ::RECTF(0, h*0.62, w, hintBounds.height), format, hintBrush);
hintBrush.delete();
hintFont.delete();
}
}
format.delete();
family.delete();
}
winform.plus.onAnimation = function(state, beginning, change, timestamp, duration) {
var w, h = getCanvasSize();
var dpiX, dpiY = winform.dpiScale(1,1);
if(game.state == STATE_PLAYING && game.currentBlock) {
var block = game.currentBlock;
block.x = block.x + game.blockSpeed * game.blockDirection;
if(block.x <= -block.width * 0.3) game.blockDirection = 1;
elseif(block.x + block.width * 0.7 >= w) game.blockDirection = -1;
}
if(game.cutBlock) {
game.cutBlock.vy = game.cutBlock.vy + 0.6 * dpiY;
game.cutBlock.y = game.cutBlock.y + game.cutBlock.vy;
game.cutBlock.x = game.cutBlock.x + game.cutBlock.vx;
game.cutBlock.rotation = game.cutBlock.rotation + game.cutBlock.vx * 0.02;
if(toScreenY(game.cutBlock.y) > h + 150) game.cutBlock = null;
}
var viewDiff = game.targetViewOffset - game.viewOffset;
if(math.abs(viewDiff) > 0.5) {
game.viewOffset = game.viewOffset + viewDiff * 0.08;
}
updateParticles();
updateGlowEffects();
return true;
}
winform.plus.onMouseDown = function(wParam, lParam) {
if(game.state == STATE_START) initGame();
elseif(game.state == STATE_PLAYING) placeBlock();
elseif(game.state == STATE_GAMEOVER) initGame();
}
winform.plus.dlgCode = 4/*_DLGC_WANTALLKEYS*/;
winform.plus.onKeyDown = function(keyCode, lParam, repeat) {
if(keyCode == 0x20/*_VK_SPACE*/) {
if(game.state == STATE_START) initGame();
elseif(game.state == STATE_PLAYING) placeBlock();
elseif(game.state == STATE_GAMEOVER) initGame();
}
}
winform.plus.startAnimation(16);
winform.plus.setFocus();
winform.show();
win.loopMessage();
Markdown 格式