aardio自绘_孤单枪手小游戏-可以直接玩

mfk 10小时前 34

熬了一个晚上,给AI打下手跑出来的孤单枪手小游戏,玩了一下是哪个感觉-分享给大家。

//孤单枪手
//操作:WASD/方向键 移动,鼠标瞄准,左键开火,P 暂停,R 重开,Enter 确认

import win.ui;
import gdip;
import sys.midiOut;
import thread;
import time.performance;

/*DSG{{*/
var winform = win.form(text="孤单枪手"; right=959; bottom=639; bgcolor=0x05070E);
winform.add(
    canvas={cls="plus";left=0;top=0;right=959;bottom=639;db=1;dl=1;dr=1;dt=1;notify=1;z=1}
)
/*}}*/

//==================== 音效(sys.midiOut 合成,无外部文件)====================
var midiOut = sys.midiOut();
if(midiOut) midiOut.setVelocity(90);

var sfxMap = {
    shoot   = "changeInstrument(73),5'_";           //笛子 高音短促
    hit     = "changeInstrument(115),3_";            //木鱼 命中
    die     = "changeInstrument(0),5_3_1_";          //钢琴 下降
    hurt    = "changeInstrument(0),'6_";             //钢琴 低音
    levelUp = "changeInstrument(0),1_3_5_1'_";       //钢琴 上升音阶
    win     = "changeInstrument(0),1_3_5_1'3'5'_";    //钢琴 凯旋
    over    = "changeInstrument(0),'6_'5_'1_";       //钢琴 下降终止
    ui      = "changeInstrument(115),5_";             //木鱼 点击
    bossHit = "changeInstrument(116),1_";             //太鼓 重击
    bossDie = "changeInstrument(0),'6_'5_'3_'1_";     //钢琴 长下降
};
var sfxTempo = {
    shoot = 60; hit = 40; die = 80; hurt = 100; levelUp = 120;
    win = 140; over = 150; ui = 40; bossHit = 50; bossDie = 120;
};
// GUI 环境下 midiOut.play() 内部用「全局 win.setTimeout」排程
var playSfx = function(name){
    var s = sfxMap[name];
    if(s && midiOut){
        thread.invoke(
            function(midi, notes, baseKey, tempo){
                try{ midi.playSync(notes, baseKey, tempo); } catch(e){}
            }, midiOut, s, "C4", sfxTempo[name]
        )
    }
}

//==================== 小工具 ====================
var clamp = function(v,a,b){ if(v<a) return a; if(v>b) return b; return v; }
//var lerp  = function(a,b,t){ return a + (b-a)*t; }
var dist2 = function(x1,y1,x2,y2){ var dx=x1-x2; var dy=y1-y2; return dx*dx+dy*dy; }

//==================== 剧情与关卡 ====================
var story = {
	[1] = { name = "第 1 区 · 觉醒"; text = "废墟深处,代号「孤狼」的枪手被炮火震醒。第一波机械异变体已嗅到血腥,正循声而来。他没有退路——只有枪,和十道门。" };
	[2] = { name = "第 2 区 · 锈谷"; text = "穿过坍塌的锈谷,成群的疾奔者从断壁间倾巢而出。它们没有恐惧,只有速度。" };
	[3] = { name = "第 3 区 · 铁幕"; text = "铁幕防线后,重甲型异变体隆隆推进。子弹打在它们的外壳上火星四溅——盯住缝隙,一枪一枪磨碎它。" };
	[4] = { name = "第 4 区 · 回响"; text = "旧城回响着机械的嗡鸣。远程射手已就位,它们会从远处扣动扳机。别停下,移动才是一切。" };
	[5] = { name = "第 5 区 · 裂隙"; text = "工厂裂隙里,混合编队逼近。疾奔、重甲、射手同时扑来——这是第一次真正的围剿。" };
	[6] = { name = "第 6 区 · 深井"; text = "竖井幽深,空间被压缩到极限。密度成倍攀升,每一秒都在被压缩的死亡里喘息。" };
	[7] = { name = "第 7 区 · 熔核"; text = "逼近核心,热浪扭曲了空气。异变体陷入狂暴,蜂拥而至,如同朝圣。" };
	[8] = { name = "第 8 区 · 镜厅"; text = "镜厅之中,高速与重甲交织成网。每一面镜子里都映着同一个孤独的身影。" };
	[9] = { name = "第 9 区 · 零之门"; text = "零之门前,最后的精英守卫列阵。过了这道门,就是一切异变的源头。" };
	[10]= { name = "第 10 区 · 零·主脑"; text = "主脑「零」悬浮于核心之上。它注视着这个孤独的枪手——终结它,或被它终结。枪膛还热着。" };
	[11]= { name = "第 11 区 · 残烬"; text = "主脑崩塌后的残垣之上,异变能量二次暴走。变异疾奔者挣脱残骸束缚,带着灼烧的戾气席卷而来,新一轮屠戮已然开启。" };
	[12]= { name = "第 12 区 · 沉铁"; text = "深埋地底的废弃军械库苏醒,强化重甲异变体身披破损战甲稳步推进。坚硬的合金躯体几乎无视普通弹药,唯有精准破甲方能突围。" };
	[13]= { name = "第 13 区 · 鸦目"; text = "荒芜高台之上,高阶机械鸦眼射手隐匿暗影。它们射程翻倍、瞄准精准,无声的狙击弹贯穿废墟,封锁所有移动路径。" };
	[14]= { name = "第 14 区 · 浊流"; text = "地下水道泛滥异变浊能,混合型变异体成群涌动。疾奔者牵制、重甲者卡位、远程者输出,层层递进的攻势碾碎一切侥幸。" };
	[15]= { name = "第 15 区 · 囚笼"; text = "密闭合金囚笼空域,地形错综复杂。异变体从四面八方的夹缝中突袭,狭小空间内无处闪避,每一次开枪都要赌上性命。" };
	[16]= { name = "第 16 区 · 焚风"; text = "焚风席卷整片废墟,高温气流灼烧视野。狂暴异变体乘风提速,带着烈焰冲撞而来,在火海之中展开不死不休的猎杀。" };
	[17]= { name = "第 17 区 · 裂甲"; text = "破碎的机甲残骸遍地堆积,进阶裂甲守卫诞生。它们自带护甲自愈能力,破损的躯体不断重组,拉锯之战消耗着仅剩的弹药与体力。" };
	[18]= { name = "第 18 区 · 双影"; text = "光影交错的废弃殿堂,双重镜像战场成型。双倍数量的高阶异变体同步围剿,虚实交织的攻势,让分辨生路成为奢望。" };
	[19]= { name = "第 19 区 · 天启闸"; text = "天启巨闸缓缓开启,前沿终极守卫列队肃杀。它们是主脑残存的王牌战力,攻防兼备,封锁通往深层空域的唯一通道。" };
	[20]= { name = "第 20 区 · 烬主"; text = "残烬核心诞生次级主宰「烬主」,它掌控着残余异变能量,能召唤无尽尸潮。火海漫天,硝烟蔽日,这是第二阶段的终末考验。" };
	[21]= { name = "第 21 区 · 荒芜"; text = "烬主覆灭后,整片大地彻底沦为荒芜。虚空裂隙持续溢出异变气息,全新的初代畸变体破土而出,战力全面迭代升级。" };
	[22]= { name = "第 22 区 · 断锋"; text = "断裂的机械锋刃遍布战场,极速锋刃畸变体穿梭突袭。身形诡谲、攻速极致,残影掠过之处,便是致命伤痕。" };
	[23]= { name = "第 23 区 · 磐石"; text = "巨型磐石机甲镇守要道,超厚特种合金装甲固若金汤。常规火力难以撼动分毫,唯有击穿核心弱点,方能击碎这座移动堡垒。" };
	[24]= { name = "第 24 区 · 冥狙"; text = "高空冥影狙击点全域覆盖,幽冥射手隐匿虚空。子弹附带侵蚀伤害,一旦命中便持续瓦解护甲,无声无息间收割生命。" };
	[25]= { name = "第 25 区 · 乱阵"; text = "多维畸变混合阵型成型,锋刃、磐石、冥狙三类高阶体协同作战。远近攻防无缝衔接,无死角围剿,绝境之上再无退路。" };
	[28]= { name = "第 28 区 · 虚镜"; text = "虚空镜面领域扭曲现实,无数镜像畸变体复制真身战力。真假难辨、攻防同源,孤身一人对抗成倍的顶级战力。" };
	[31]= { name = "第 31 区 · 寂灭"; text = "虚空主宰陨落,天地陷入寂灭沉寂。极致的死寂之下,更高维度的湮灭之力苏醒,全新的神级畸变体悄然降临。" };
	[34]= { name = "第 34 区 · 星陨"; text = "高空星陨炮台全域锁定,星陨射手引动天外能量。陨落星弹贯穿天地,大范围轰炸覆盖,无处可藏、无地可避。" };
	[38]= { name = "第 38 区 · 万象"; text = "万象镜像重塑战场,复刻所有过往强敌镜像。从初代疾奔者到虚空主宰,历代强敌齐聚,孤身迎战万敌轮回。" };
	[41]= { name = "第 41 区 · 鸿蒙"; text = "万象神主覆灭,天地蜕变为鸿蒙初境。本源异变之力彻底苏醒,超脱维度的终极畸变体,打破所有战力桎梏降临世间。" };
	[42]= { name = "第 42 区 · 极影"; text = "鸿蒙空域诞生极影畸变体,融合极致速度与虚空隐匿。无形无影、无声无迹,猎杀维度拉满,唯有感知杀意方能反击。" };
	[43]= { name = "第 43 区 · 亘古"; text = "亘古神躯机甲现世,承载万年异变本源。躯体坚不可摧,力能撼天动地,每一次轰击都引发空间崩塌。" };
	[44]= { name = "第 44 区 · 穹苍"; text = "穹苍天际终极炮台列阵,苍穹神射执掌天道惩戒。贯穿天地的法则弹,可撕裂空间、湮灭生机,覆盖全域无死角打击。" };
	[45]= { name = "第 45 区 · 鸿蒙天伐"; text = "鸿蒙终极天伐战队成型,极影、亘古、穹苍三大至尊体联手。本源之力叠加,法则攻势漫天,是跨越维度的终极围剿。" };
	[46]= { name = "第 46 区 · 归一"; text = "天地法则归一,所有异变力量汇聚一体。战场压缩至核心原点,极致密度的至尊畸变体扎堆涌现,每一秒都是生死鏖战。" };
	[47]= { name = "第 47 区 · 焚穹"; text = "焚穹本源之火席卷鸿蒙,至尊火灵畸变体焚尽天地。连空间与法则都在烈焰中消融,高温绝境考验终极生存能力。" };
	[48]= { name = "第 48 区 · 轮回万象"; text = "终极轮回镜像开启,复刻所有阶段至尊强敌。五十重战力叠加,轮回厮杀无尽,孤身一人鏖战天地万敌。" };
	[49]= { name = "第 49 区 · 始源之门"; text = "异变始源之门屹立天地,世间终极本源守卫镇守于此。这是所有畸变力量的源头关口,跨过此门,便是最终宿命对决。" };
	[50]= { name = "第 50 区 · 终焉·本源零"; text = "万物终焉,本源零体悬浮鸿蒙核心。它是所有机械异变的始源,是轮回与毁灭的尽头。滚烫的枪膛、孤独的猎手,终局之战,就此落幕。" };
}

var stageNames = {"觉醒","锈谷","铁幕","回响","裂隙","深井","熔核","镜厅","零之门","主脑","残烬","沉铁","鸦目","浊流","囚笼","焚风","裂甲","双影","天启","烬主","荒芜","断锋","磐石","冥狙","乱阵","幽穴","怒焰","虚镜","玄门","主宰","寂灭","瞬杀","万钧","星陨","神伐","天狱","焚天","万象","神阙","神主","鸿蒙","极影","亘古","穹苍","天伐","归一","焚穹","轮回","始源","终焉"};
var storyText = function(n){
	var s = story[n];
	if(s) return s;
	var tier = math.floor((n-1)/10) + 1;
	var stage = stageNames[((n-1)%10)+1];
	return {
		name = "第 " ++ tostring(n) ++ " 区 · 第 " ++ tostring(tier) ++ " 阶段 · " ++ stage;
		text = "第 " ++ tostring(tier) ++ " 阶段战场开启。异变体愈发凶悍——被击碎的残体可能分裂重组,盯紧每一块碎片。";
	};
}

var enemyDefs = {
	grunt   = { r = 13; hp = 2;  spd = 62;  dmg = 8;  color = 0xFFFF5A5A; score = 10; shoot = false; canSplit = true; };
	runner  = { r = 10; hp = 1;  spd = 132; dmg = 6;  color = 0xFFFF9A3C; score = 15; shoot = false; canSplit = true; };
	brute   = { r = 20; hp = 8;  spd = 34;  dmg = 18; color = 0xFFB061FF; score = 30; shoot = false; canSplit = true; };
	shooter = { r = 13; hp = 3;  spd = 46;  dmg = 10; color = 0xFF3CE0B0; score = 25; shoot = true;  ebulletspeed = 230; ebulletdmg = 8; shootT = 2.2; canSplit = false; };
}

//==================== 关卡系统:50 关,每 10 关难度提升一个层级(tier 1~5)====================
var MAX_LEVEL = 50;                                 // 总关数
var isBossLevel = function(n){ return n % 10 == 0; } // 10/20/30/40/50 为 Boss 关
var getLevel = function(n){
	var tier = math.floor((n-1)/10) + 1;     // 1..5,每 10 关升一级
	var pos  = n - (tier-1)*10;              // 该层内第几关 1..10
	var hpMult  = 1.0 + (n-1)*0.10;          // 第1关 1.0 → 第50关 5.9
	var spdMult = 1.0 + (n-1)*0.015;         // 1.0 → 1.735
	var interval = math.max(0.35, 1.3 - n*0.018);
	var batch = 2 + math.floor(n/8);         // 2..8
	var total = math.ceil(8 + n*1.1);        // 第1关 9 → 第50关 63
	var types;
	if(tier == 1)      types = {"grunt","runner"};
	elseif(tier == 2)  types = {"grunt","runner","brute"};
	elseif(tier == 3)  types = {"grunt","runner","brute","shooter"};
	elseif(tier == 4)  types = {"runner","brute","shooter","grunt"};
	else               types = {"grunt","runner","brute","shooter"};
	// 难度升级特性:tier>=2 起普通敌人被击碎时分裂(分裂体生命 = 母体满血 * 0.6)
	var split = (tier >= 2);
	return {
		tier = tier; pos = pos;
		total = total; batch = batch; interval = interval;
		types = types; hpMult = hpMult; spdMult = spdMult;
		split = split;
	};
}

//==================== 游戏状态 ====================
var player = { x = 480; y = 320; r = 14; hp = 100; maxhp = 100; cool = 0; aim = 0; invuln = 0; speed = 235; fireRate = 0.13; bspd = 760; bdmg = 1; }

var game = {
	state = "title";        //title story playing levelClear gameover win paused
	level = 1;
	score = 0;
	W = 960; H = 640;
	t = 0;
	enemies = {}; bullets = {}; ebullets = {}; particles = {}; floats = {};
	spawned = 0; killed = 0; spawnT = 0;
	shake = 0; ox = 0; oy = 0;
	justCleared = false;
	boss = null;
	err = null;
}

var input = { mx = 480; my = 320; keys = {}; pressed = {}; }

//==================== 画刷/画笔/字体 ====================
var _bc = {};
var brushOf = function(argb){
	var b = _bc[argb];
	if(b) return b;
	b = gdip.solidBrush(argb);
	_bc[argb] = b;
	return b;
}
var penFrame  = gdip.pen(0xFF2A3A5A, 1.4, 2);
var penGrid   = gdip.pen(0xFF16203A, 1, 2);
var penBarrel = gdip.pen(0xFFE6FBFF, 4, 2);
var penBullet = gdip.pen(0xFFFFE25A, 3, 2);
var penEBullet = gdip.pen(0xFFFF6A6A, 2.4, 2);
var penCross  = gdip.pen(0x66FFFFFF, 1, 2);
var penArena     = gdip.pen(0xFF33405E, 2, 2);
var penBtnBorder = gdip.pen(0xFF2A4060, 2, 2);
var penBtnHover  = gdip.pen(0xFF36E0FF, 2.5, 2);

var fFamily = gdip.family("微软雅黑");
var fTitle  = fFamily.createFont(50, 1, 2);
var fSub    = fFamily.createFont(24, 1, 2);
var fBody   = fFamily.createFont(18, 0, 2);
var fHud    = fFamily.createFont(15, 1, 2);
var fBig    = fFamily.createFont(30, 1, 2);
var fSmall  = fFamily.createFont(13, 0, 2);
var sfC = gdip.stringFormat(); sfC.align = 1;
var sfL = gdip.stringFormat(); sfL.align = 0;

//==================== 实体生成 ====================
var randEdgePos = function(){
	var W = game.W; var H = game.H;
	var side = math.random(1,4);
	if(side == 1){ return { x = math.random(40, W-40); y = -20; } }      //上
	elseif(side == 2){ return { x = W + 20; y = math.random(40, H-40); } } //右
	elseif(side == 3){ return { x = math.random(40, W-40); y = H + 20; } } //下
	else{ return { x = -20; y = math.random(40, H-40); } }                 //左
}

var spawnEnemy = function(typeName, L){
	var d = enemyDefs[typeName]; if(!d) return;
	var p = randEdgePos();
	var e = {
		type = typeName; x = p.x; y = p.y; r = d.r;
		hp = math.ceil(d.hp * L.hpMult); maxhp = math.ceil(d.hp * L.hpMult);
		spd = d.spd * L.spdMult; dmg = d.dmg; color = d.color; score = d.score;
		shoot = d.shoot; ebspeed = d.ebulletspeed; ebdmg = d.ebulletdmg; shootT = d.shootT;
		ang = 0; flash = 0; shootCool = 1.0 + math.random()*1.5;
		canSplit = (d.canSplit == true && L.split == true);   // 仅高级别 + 允许分裂的类型会分裂
		isSplit = false;                                      // 母体标记
	};
	game.enemies[#game.enemies+1] = e;
}

// 分裂体:生命值 = 母体满血的 0.6 倍,更小更快,且不可再分裂(防无限分裂)
var spawnSplit = function(parent){
	var childHp = math.max(1, math.ceil(parent.maxhp * 0.6));
	var e = {
		type = parent.type;
		x = parent.x + (math.random()*2-1)*10; y = parent.y + (math.random()*2-1)*10;
		r = math.max(7, math.floor(parent.r*0.7));
		hp = childHp; maxhp = childHp;
		spd = parent.spd * 1.4; dmg = math.max(1, math.ceil(parent.dmg*0.7));
		color = parent.color; score = math.max(1, math.ceil(parent.score*0.5));
		shoot = false; ebspeed = 0; ebdmg = 0; shootT = 0;   // 分裂体不远程,避免弹幕爆炸
		ang = 0; flash = 0.15; shootCool = 1.0;
		canSplit = false; isSplit = true;
	};
	game.enemies[#game.enemies+1] = e;
	game.spawned++;   // 计入生成总数,HUD「剩余敌人」保持口径一致
}

var spawnBoss = function(){
	var tier = math.floor((game.level-1)/10) + 1;  // 第10关tier1 … 第50关tier5
	var hp = 170 + (tier-1)*140;                   // 170 / 310 / 450 / 590 / 730
	var dmg = 26 + (tier-1)*6;
	var spd = 55 + (tier-1)*8;
	var b = {
		type = "boss"; x = game.W/2; y = 90; r = 42;
		hp = hp; maxhp = hp; spd = spd; dmg = dmg; color = 0xFFE0364A; score = 800 + (tier-1)*400;
		ang = 0; flash = 0; phase = 1; shootCool = 1.2; spawnCool = 4.0; spreadCool = 2.4;
		isBoss = true;
	};
	game.boss = b;
	game.enemies[#game.enemies+1] = b;
}

var pickType = function(L){
	var ts = L.types;
	return ts[ math.random(1, #ts) ];
}

//==================== 粒子 / 浮字 ====================
var addParticles = function(x, y, color, count, spdMin, spdMax, life, rMin, rMax){
	for(i=1,count,1){
		var a = math.random()*2*math.pi;
		var s = math.random(spdMin, spdMax);
		game.particles[#game.particles+1] = {
			x = x; y = y; vx = math.cos(a)*s; vy = math.sin(a)*s;
			life = life*(0.6+math.random()*0.6); max = life; color = color;
			r = math.random(rMin, rMax);
		};
	}
}

var addFloat = function(x, y, text, color){
	game.floats[#game.floats+1] = { x = x; y = y; text = text; color = color; life = 0.9; };
}

var killEnemy = function(e, idx){
	if(e.isBoss){
		addParticles(e.x, e.y, 0xFFFFE25A, 60, 40, 320, 1.1, 3, 9);
		addParticles(e.x, e.y, 0xFFE0364A, 40, 20, 200, 1.3, 3, 11);
		game.shake = 1.0;
		playSfx("bossDie");
		game.score += e.score;
		game.boss = null;
		if(game.level >= MAX_LEVEL){ game.state = "win"; }               // 第50关最终 Boss → 通关
		else{ game.justCleared = true; game.level++; game.state = "story"; }  // 阶段 Boss → 进入下一关
		return;
	}
	game.killed++;
	game.score += e.score;
	addFloat(e.x, e.y-10, "+" ++ tostring(e.score), 0xFFFFE25A);
	addParticles(e.x, e.y, e.color, 10, 50, 240, 0.5, 2, 5);
	playSfx("die");
	// 难度升级特性:可分裂敌人被击碎时,分裂为 2 个残体(生命值 = 母体满血 * 0.6)
	if(e.canSplit){
		spawnSplit(e);
		spawnSplit(e);
	}
}

//==================== 玩家射击 ====================
var fireBullet = function(){
	var a = player.aim;
	var bx = player.x + math.cos(a)*player.r;
	var by = player.y + math.sin(a)*player.r;
	game.bullets[#game.bullets+1] = {
		x = bx; y = by; vx = math.cos(a)*player.bspd; vy = math.sin(a)*player.bspd;
		r = 4; life = 1.1; dmg = player.bdmg;
	};
	//枪口火花
	addParticles(bx, by, 0xFFFFE25A, 3, 40, 160, 0.18, 1, 3);
	playSfx("shoot");
	player.cool = player.fireRate;
}

//==================== 关卡流程 ====================
var startLevel = function(n){
	game.level = n;
	game.enemies = {}; game.bullets = {}; game.ebullets = {}; game.particles = {}; game.floats = {};
	game.spawned = 0; game.killed = 0; game.spawnT = 0.9;
	game.shake = 0; game.boss = null;
	player.x = game.W/2; player.y = game.H/2;
	player.hp = player.maxhp; player.cool = 0; player.invuln = 1.2;
	game.state = "playing";
	if(isBossLevel(n)){ spawnBoss(); }
}

var onLevelClear = function(){
	playSfx("levelUp");
	if(game.level < MAX_LEVEL){
		game.justCleared = true;
		game.level++;
		game.state = "story";
	}
	else{
		game.state = "win";
	}
}

var restartGame = function(){
	game.level = 1; game.score = 0; game.justCleared = false;
	game.enemies = {}; game.bullets = {}; game.ebullets = {}; game.particles = {}; game.floats = {};
	game.boss = null;
	game.state = "title";
}

//==================== 输入读取====================
var keyDown = function(vk){ return input.keys[vk] == true; }
var keyEdge = function(vk){
	var p = input.pressed[vk];
	input.pressed[vk] = false;
	return p == true;
}

var pointInRect = function(mx, my, x, y, w, h){
	return mx >= x && mx <= x+w && my >= y && my <= y+h;
}

//==================== 更新 ====================
var update = function(dt){
	game.t = game.t + dt;
	if(game.shake > 0){ game.shake = game.shake - dt*3.5; if(game.shake < 0) game.shake = 0; }
	if(game.shake > 0){
		var m = game.shake * 9;
		game.ox = (math.random()*2-1)*m; game.oy = (math.random()*2-1)*m;
	}
	else{ game.ox = 0; game.oy = 0; }

	//非游戏态:只处理菜单按键
	if(game.state == "title"){
		var bw = 240; var bh = 56;
		var bx = game.W/2 - bw/2; var by = game.H/2 + 50 - bh/2;
		if(keyEdge(0x0D) || (keyEdge(0x01) && pointInRect(input.mx, input.my, bx, by, bw, bh))){
			game.justCleared = false; game.state = "story"; playSfx("ui");
		}
		return;
	}
	elseif(game.state == "story"){
		var bw = 240; var bh = 50;
		var bx = game.W/2 - bw/2; var by = game.H/2 + 90 - bh/2;
		if(keyEdge(0x0D) || (keyEdge(0x01) && pointInRect(input.mx, input.my, bx, by, bw, bh))){
			playSfx("ui"); startLevel(game.level);
		}
		if(keyEdge(0x52)){ playSfx("ui"); restartGame(); }
		return;
	}
	elseif(game.state == "win"){
		var bw = 240; var bh = 50;
		var bx = game.W/2 - bw/2; var by = game.H/2 + 80 - bh/2;
		if(keyEdge(0x52) || (keyEdge(0x01) && pointInRect(input.mx, input.my, bx, by, bw, bh))){
			playSfx("ui"); restartGame();
		}
		return;
	}
	elseif(game.state == "gameover"){
		var bw = 240; var bh = 50;
		var bx = game.W/2 - bw/2; var by = game.H/2 + 90 - bh/2;
		if(keyEdge(0x52) || keyEdge(0x0D) || (keyEdge(0x01) && pointInRect(input.mx, input.my, bx, by, bw, bh))){
			playSfx("ui"); restartGame();
		}
		return;
	}

	//暂停切换
	if(keyEdge(0x50)){ if(game.state == "playing"){ game.state = "paused"; playSfx("ui"); } elseif(game.state == "paused"){ game.state = "playing"; playSfx("ui"); } }
	if(game.state == "paused"){
		var bw = 220; var bh = 50;
		var bx = game.W/2 - bw/2; var by = game.H/2 + 30 - bh/2;
		if(keyEdge(0x01) && pointInRect(input.mx, input.my, bx, by, bw, bh)){
			game.state = "playing"; playSfx("ui");
		}
		return;
	}

	//============ 移动 ============
	var vx = 0; var vy = 0;
	if(keyDown(0x57) || keyDown(0x26)) vy = vy - 1;   //W / Up
	if(keyDown(0x53) || keyDown(0x28)) vy = vy + 1;   //S / Down
	if(keyDown(0x41) || keyDown(0x25)) vx = vx - 1;   //A / Left
	if(keyDown(0x44) || keyDown(0x27)) vx = vx + 1;   //D / Right
	if(vx != 0 || vy != 0){
		var l = math.sqrt(vx*vx + vy*vy);
		vx = vx/l; vy = vy/l;
	}
	player.x = clamp(player.x + vx*player.speed*dt, player.r, game.W - player.r);
	player.y = clamp(player.y + vy*player.speed*dt, player.r, game.H - player.r);

	player.aim = math.atan2(input.my - player.y, input.mx - player.x);
	if(player.invuln > 0) player.invuln = player.invuln - dt;
	if(player.cool > 0) player.cool = player.cool - dt;

	//射击(按住左键自动连发)
	if(keyDown(0x01) && player.cool <= 0){ fireBullet(); }

	//============ 敌人生成 ============
	if(game.level <= MAX_LEVEL && !isBossLevel(game.level)){
		var L = getLevel(game.level);
		if(L){
			if(game.spawned < L.total){
				game.spawnT = game.spawnT - dt;
				if(game.spawnT <= 0){
					var b = L.batch;
					for(i=1,b,1){
						if(game.spawned >= L.total) break;
						spawnEnemy(pickType(L), L);
						game.spawned++;
					}
					game.spawnT = L.interval;
				}
			}
			if(game.spawned >= L.total && #game.enemies == 0){ onLevelClear(); return; }
		}
	}

	//============ 敌人更新 ============
	var na = {};
	for(i=1,#game.enemies,1){
		var e = game.enemies[i];
		if(e.flash > 0) e.flash = e.flash - dt;
		var dx = player.x - e.x; var dy = player.y - e.y;
		var dd = math.sqrt(dx*dx + dy*dy);
		e.ang = math.atan2(dy, dx);

		if(e.isBoss){
			//Boss:保持距离,环绕
			var keep = 230;
			var tx = player.x - math.cos(e.ang)*keep;
			var ty = player.y - math.sin(e.ang)*keep;
			var bdx = tx - e.x; var bdy = ty - e.y;
			var bdd = math.sqrt(bdx*bdx + bdy*bdy) + 0.0001;
			e.x = e.x + (bdx/bdd)*e.spd*dt;
			e.y = e.y + (bdy/bdd)*e.spd*dt;
			e.ang = e.ang + dt*0.6;
			//阶段
			var frac = e.hp / e.maxhp;
			if(frac < 0.6 && e.phase < 2) e.phase = 2;
			if(frac < 0.3 && e.phase < 3) e.phase = 3;
			e.shootCool = e.shootCool - dt;
			if(e.shootCool <= 0){
				e.shootCool = e.phase==3 ? 0.55 : (e.phase==2 ? 0.9 : 1.2);
				//瞄准弹
				var sa = math.atan2(player.y - e.y, player.x - e.x);
				game.ebullets[#game.ebullets+1] = { x = e.x; y = e.y; vx = math.cos(sa)*260; vy = math.sin(sa)*260; r = 6; dmg = 12; life = 3; color = 0xFFFF8A3C };
			}
			if(e.phase >= 2){
				e.spreadCool = e.spreadCool - dt;
				if(e.spreadCool <= 0){
					e.spreadCool = e.phase==3 ? 1.1 : 1.8;
					for(k=-2,2,1){
						var sa = math.atan2(player.y - e.y, player.x - e.x) + k*0.28;
						game.ebullets[#game.ebullets+1] = { x = e.x; y = e.y; vx = math.cos(sa)*200; vy = math.sin(sa)*200; r = 5; dmg = 10; life = 3; color = 0xFFFFE25A };
					}
				}
				e.spawnCool = e.spawnCool - dt;
				if(e.spawnCool <= 0){
				e.spawnCool = 3.5;
				var L9 = getLevel(9);
					spawnEnemy("runner", L9);
					spawnEnemy("runner", L9);
				}
			}
			na[#na+1] = e;
		}
		elseif(e.shoot){
			//射手:到中距即停,开火
			if(dd > 250){
				e.x = e.x + (dx/dd)*e.spd*dt;
				e.y = e.y + (dy/dd)*e.spd*dt;
			}
			else{
				//侧向移动
				e.x = e.x + (-dy/dd)*e.spd*0.5*dt;
				e.y = e.y + (dx/dd)*e.spd*0.5*dt;
			}
			e.shootCool = e.shootCool - dt;
			if(e.shootCool <= 0 && dd < 420){
				e.shootCool = e.shootT;
				var sa = math.atan2(dy, dx);
				game.ebullets[#game.ebullets+1] = { x = e.x; y = e.y; vx = math.cos(sa)*e.ebspeed; vy = math.sin(sa)*e.ebspeed; r = 5; dmg = e.ebdmg; life = 3; color = 0xFFFF6A6A };
				playSfx("shoot");
			}
			e.x = clamp(e.x, -40, game.W+40); e.y = clamp(e.y, -40, game.H+40);
			na[#na+1] = e;
		}
		else{
			//普通:直冲玩家
			if(dd > 0.001){ e.x = e.x + (dx/dd)*e.spd*dt; e.y = e.y + (dy/dd)*e.spd*dt; }
			na[#na+1] = e;
		}
	}
	game.enemies = na;

	//============ 子弹更新 + 命中 ============
	var nb = {};
	for(i=1,#game.bullets,1){
		var bl = game.bullets[i];
		bl.x = bl.x + bl.vx*dt; bl.y = bl.y + bl.vy*dt;
		bl.life = bl.life - dt;
		var dead = false;
		if(bl.x < -10 || bl.x > game.W+10 || bl.y < -10 || bl.y > game.H+10 || bl.life <= 0) dead = true;
		if(!dead){
			for(j=1,#game.enemies,1){
				var e = game.enemies[j];
				if(dist2(bl.x,bl.y,e.x,e.y) < (e.r+bl.r)*(e.r+bl.r)){
					e.hp = e.hp - bl.dmg; e.flash = 0.12;
					addParticles(bl.x, bl.y, 0xFFFFE25A, 3, 30, 120, 0.2, 1, 3);
					if(e.isBoss) playSfx("bossHit"); else playSfx("hit");
					if(e.hp <= 0){ killEnemy(e, j); }
					dead = true;
					break;
				}
			}
		}
		if(!dead) nb[#nb+1] = bl;
	}
	game.bullets = nb;
	//清理死亡敌人(hp<=0 且非 boss,boss 死已在 killEnemy 处理 state)
	var na2 = {};
	for(i=1,#game.enemies,1){
		var e = game.enemies[i];
		if(e.hp > 0) na2[#na2+1] = e;
	}
	game.enemies = na2;

	//============ 敌弹更新 + 命中玩家 ============
	var ne = {};
	for(i=1,#game.ebullets,1){
		var eb = game.ebullets[i];
		eb.x = eb.x + eb.vx*dt; eb.y = eb.y + eb.vy*dt; eb.life = eb.life - dt;
		var dead = false;
		if(eb.x < -20 || eb.x > game.W+20 || eb.y < -20 || eb.y > game.H+20 || eb.life <= 0) dead = true;
		if(!dead && player.invuln <= 0){
			if(dist2(eb.x,eb.y,player.x,player.y) < (player.r+eb.r)*(player.r+eb.r)){
				player.hp = player.hp - eb.dmg; player.invuln = 0.6; game.shake = 0.5;
				addParticles(player.x, player.y, 0xFFFF5A5A, 8, 40, 160, 0.4, 2, 4);
				playSfx("hurt");
				dead = true;
			}
		}
		if(!dead) ne[#ne+1] = eb;
	}
	game.ebullets = ne;

	//============ 敌人接触玩家 ============

	for(i=1,#game.enemies,1){
		var e = game.enemies[i];
		if(dist2(e.x,e.y,player.x,player.y) < (e.r+player.r)*(e.r+player.r)){
			if(player.invuln <= 0){
				player.hp = player.hp - e.dmg; player.invuln = 0.7; game.shake = 0.6;
				addParticles(player.x, player.y, 0xFFFF5A5A, 10, 50, 200, 0.45, 2, 5);
				playSfx("hurt");
			}
			e.flash = 0.1;
		}
	}
	//再次清理
	var na3 = {};
	for(i=1,#game.enemies,1){
		var e = game.enemies[i];
		if(e.hp > 0) na3[#na3+1] = e;
	}
	game.enemies = na3;

	//============ 粒子 / 浮字 ============
	var np = {};
	for(i=1,#game.particles,1){
		var p = game.particles[i];
		p.x = p.x + p.vx*dt; p.y = p.y + p.vy*dt;
		p.vx = p.vx*0.92; p.vy = p.vy*0.92;
		p.life = p.life - dt;
		if(p.life > 0) np[#np+1] = p;
	}
	game.particles = np;

	var nf = {};
	for(i=1,#game.floats,1){
		var f = game.floats[i];
		f.y = f.y - 30*dt; f.life = f.life - dt;
		if(f.life > 0) nf[#nf+1] = f;
	}
	game.floats = nf;

	//============ 胜负判定 ============
	if(player.hp <= 0){
		player.hp = 0;
		game.state = "gameover";
		game.shake = 1.0;
		addParticles(player.x, player.y, 0xFFFF5A5A, 40, 40, 280, 0.8, 3, 8);
		playSfx("over");
	}
	if(isBossLevel(game.level) && game.boss == null && game.state == "playing"){
	}
}

//==================== 绘制辅助 ====================
var polyVerts = function(cx, cy, r, rot, sides){
	var t = {};
	for(i=0,sides-1,1){
		var a = rot + i*2*math.pi/sides;
		t[#t+1] = cx + math.cos(a)*r;
		t[#t+1] = cy + math.sin(a)*r;
	}
	return t;
}

var starVerts = function(cx, cy, rOut, rIn, rot, points){
	var t = {};
	for(i=0,points-1,1){
		var a1 = rot + i*2*math.pi/points;
		t[#t+1] = cx + math.cos(a1)*rOut;
		t[#t+1] = cy + math.sin(a1)*rOut;
		var a2 = a1 + math.pi/points;
		t[#t+1] = cx + math.cos(a2)*rIn;
		t[#t+1] = cy + math.sin(a2)*rIn;
	}
	return t;
}

var drawBar = function(g, x, y, w, h, frac, colFg){
	g.fillRectangle(brushOf(0xFF0A0D16), x, y, w, h);
	if(frac > 0) g.fillRectangle(brushOf(colFg), x, y, w*frac, h);
	g.drawRectangle(penFrame, x, y, w, h);
}

var drawText = function(g, str, font, x, y, w, h, fmt, col){
	// drawString 的 layoutRect 必须是 ::RECTF 结构体
	g.drawString(str, font, ::RECTF(x, y, w, h), fmt, brushOf(col));
}

//绘制居中按钮,自动检测 hover 高亮
var drawBtn = function(g, cx, cy, w, h, label, font){
	var bx = cx - w/2;
	var by = cy - h/2;
	var hover = pointInRect(input.mx, input.my, bx, by, w, h);
	g.fillRectangle(brushOf(hover ? 0xE01E6FB0 : 0xE00F1A2E), bx, by, w, h);
	g.drawRectangle(hover ? penBtnHover : penBtnBorder, bx, by, w, h);
	drawText(g, label, font, bx, by, w, h, sfC, hover ? 0xFFFFFFFF : 0xFFB0C8E0);
	return { x = bx; y = by; w = w; h = h; hover = hover };
}

//==================== 渲染 ====================
var drawBackground = function(g, W, H){
    var rect = ::RECTF(0, 0, W, H);   // x, y, width, height
    var bg = gdip.lineBrush(rect, 0xFF11182B, 0xFF05070E, 0);
    g.fillRectangle(bg, 0, 0, W, H);
    bg.delete();
	//网格
	var step = 48;
	for(x=0, W, step){ g.drawLine(penGrid, x, 0, x, H); }
	for(y=0, H, step){ g.drawLine(penGrid, 0, y, W, y); }
	//远端呼吸光
	var pulse = 0.5 + 0.5*math.sin(game.t*0.8);
	var glow = brushOf( (math.floor(0x12 + 0x10*pulse) << 24) | 0x1E6FB0 );
	g.fillEllipse(glow, W/2-260, H/2-260, 520, 520);
	//竞技场边框
	g.drawRectangle(penArena, 2, 2, W-4, H-4);
}

var drawPlayer = function(g){
	var a = 0xFF;
	if(player.invuln > 0 && math.floor(game.t*24) % 2 == 0) a = 0x60;
	//光晕
	g.fillEllipse(brushOf(0x4436E0FF), player.x-22+game.ox, player.y-22+game.oy, 44, 44);
	//枪管
	var bx = player.x + math.cos(player.aim)*(player.r+6);
	var by = player.y + math.sin(player.aim)*(player.r+6);
	g.drawLine(penBarrel, player.x+game.ox, player.y+game.oy, bx+game.ox, by+game.oy);
	//身体
	var bodyCol = (a << 24) | 0x36E0FF;
	g.fillEllipse(brushOf(bodyCol), player.x-player.r+game.ox, player.y-player.r+game.oy, player.r*2, player.r*2);
	//描边
	g.drawEllipse(penFrame, player.x-player.r+game.ox, player.y-player.r+game.oy, player.r*2, player.r*2);
	//核心
	g.fillEllipse(brushOf(0xFFFFFFFF), player.x-3+game.ox, player.y-3+game.oy, 6, 6);
}

var drawEnemy = function(g, e){
	var ox = game.ox; var oy = game.oy;
	var col = e.color;
	if(e.flash > 0) col = 0xFFFFFFFF;
	if(e.isBoss){
		//光晕
		g.fillEllipse(brushOf(0x55E0364A), e.x-e.r-16+ox, e.y-e.r-16+oy, (e.r+16)*2, (e.r+16)*2);
		//外环
		var ring = polyVerts(e.x+ox, e.y+oy, e.r+10, e.ang, 6);
		g.fillPolygon(brushOf(0xFF3A0E16), 1, ring);
		g.drawPolygon(penFrame, ring);
		//星形主体
		var star = starVerts(e.x+ox, e.y+oy, e.r, e.r*0.5, e.ang, 6);
		g.fillPolygon(brushOf(col), 1, star);
		g.drawPolygon(penFrame, star);
		//核心眼
		g.fillEllipse(brushOf(0xFFFFE25A), e.x-7+ox, e.y-7+oy, 14, 14);
		return;
	}
	//光晕
	g.fillEllipse(brushOf((col & 0x00FFFFFF) | 0x33000000), e.x-e.r-6+ox, e.y-e.r-6+oy, (e.r+6)*2, (e.r+6)*2);
	if(e.type == "grunt"){
		var v = polyVerts(e.x+ox, e.y+oy, e.r, e.ang+math.pi/2, 3);
		g.fillPolygon(brushOf(col), 1, v);
		g.drawPolygon(penFrame, v);
	}
	elseif(e.type == "runner"){
		var v = polyVerts(e.x+ox, e.y+oy, e.r, e.ang+math.pi/4, 4);
		g.fillPolygon(brushOf(col), 1, v);
		g.drawPolygon(penFrame, v);
	}
	elseif(e.type == "brute"){
		var v = polyVerts(e.x+ox, e.y+oy, e.r, e.ang, 6);
		g.fillPolygon(brushOf(col), 1, v);
		g.drawPolygon(penFrame, v);
		g.fillEllipse(brushOf(0xFFFFFFFF), e.x-3+ox, e.y-3+oy, 6, 6);
	}
	elseif(e.type == "shooter"){
		var v = polyVerts(e.x+ox, e.y+oy, e.r, e.ang+math.pi/4, 4);
		g.fillPolygon(brushOf(col), 1, v);
		g.drawPolygon(penFrame, v);
		//枪口指示
		var mx = e.x + math.cos(e.ang)*(e.r+5);
		var my = e.y + math.sin(e.ang)*(e.r+5);
		g.fillEllipse(brushOf(0xFFFFE25A), mx-2+ox, my-2+oy, 4, 4);
	}
	//血量条(多血敌人)
	if(e.maxhp > 2 && e.hp < e.maxhp){
		drawBar(g, e.x-e.r+ox, e.y-e.r-9+oy, e.r*2, 3, e.hp/e.maxhp, 0xFF4ADE80);
	}
}

var drawBullets = function(g){
	for(i=1,#game.bullets,1){
		var b = game.bullets[i];
		g.fillEllipse(brushOf(0x66FFE25A), b.x-6+game.ox, b.y-6+game.oy, 12, 12);
		g.fillEllipse(brushOf(0xFFFFE25A), b.x-b.r+game.ox, b.y-b.r+game.oy, b.r*2, b.r*2);
	}
}
var drawEBullets = function(g){
	for(i=1,#game.ebullets,1){
		var b = game.ebullets[i];
		g.fillEllipse(brushOf(0x66FF6A6A), b.x-6+game.ox, b.y-6+game.oy, 12, 12);
		g.fillEllipse(brushOf(b.color), b.x-b.r+game.ox, b.y-b.r+game.oy, b.r*2, b.r*2);
	}
}
var drawParticles = function(g){
	for(i=1,#game.particles,1){
		var p = game.particles[i];
		var frac = p.life / p.max;
		if(frac < 0) frac = 0;
		var qa = math.floor(frac*15)*17;   //量化 alpha
		var col = (qa << 24) | (p.color & 0x00FFFFFF);
		var rr = p.r * (0.4 + 0.6*frac);
		g.fillEllipse(brushOf(col), p.x-rr+game.ox, p.y-rr+game.oy, rr*2, rr*2);
	}
}
var drawFloats = function(g){
	for(i=1,#game.floats,1){
		var f = game.floats[i];
		var qa = math.floor((f.life/0.9)*15)*17;
		var col = (qa << 24) | (f.color & 0x00FFFFFF);
		drawText(g, f.text, fSmall, f.x-30+game.ox, f.y-10+game.oy, 60, 16, sfC, col);
	}
}

var drawCrosshair = function(g){
	var x = input.mx; var y = input.my;
	g.drawLine(penCross, x-12, y, x-4, y);
	g.drawLine(penCross, x+4, y, x+12, y);
	g.drawLine(penCross, x, y-12, x, y-4);
	g.drawLine(penCross, x, y+4, x, y+12);
	g.drawEllipse(penCross, x-7, y-7, 14, 14);
}

var drawHUD = function(g, W, H){
	//左上:生命 + 关卡
	drawBar(g, 16, 16, 220, 16, player.hp/player.maxhp, 0xFF4ADE80);
	drawText(g, "HP " ++ tostring(math.ceil(player.hp)) ++ " / " ++ tostring(player.maxhp), fHud, 16, 16, 220, 16, sfC, 0xFFFFFFFF);
	//右上:分数 + 关卡
	var lvText = "第 " ++ tostring(game.level) ++ " 关 / " ++ tostring(MAX_LEVEL);
	var scText = "分数 " ++ tostring(game.score);
	drawText(g, scText, fHud, W-260, 14, 244, 20, sfC, 0xFFFFE25A);
	drawText(g, lvText, fHud, W-260, 34, 244, 20, sfC, 0xFFE8F0FF);
	//左下:操作提示
	drawText(g, "WASD 移动 · 鼠标瞄准 · 左键开火 · P 暂停 · R 重开", fSmall, 16, H-26, W-32, 18, sfL, 0xFF8FA0BF);
	//剩余敌人
	if(game.level <= MAX_LEVEL && !isBossLevel(game.level)){
		var L = getLevel(game.level);
		if(L){
			var remain = math.max(0, L.total - game.spawned) + #game.enemies;
			drawText(g, "剩余敌人 " ++ tostring(remain), fHud, 16, 40, 220, 18, sfL, 0xFFE8F0FF);
		}
	}
	//Boss 血条
	if(game.boss){
		var b = game.boss;
		var bw = W - 80;
		drawBar(g, 40, H-46, bw, 18, b.hp/b.maxhp, 0xFFE0364A);
		drawText(g, "零 · 主脑  阶段 " ++ tostring(b.phase), fHud, 40, H-46, bw, 18, sfC, 0xFFFFFFFF);
	}
}

var drawOverlay = function(g, W, H, title, sub){
	var ov = brushOf(0xB005070E);
	g.fillRectangle(ov, 0, 0, W, H);
	drawText(g, title, fTitle, 0, H/2-90, W, 70, sfC, 0xFFFFFFFF);
	drawText(g, sub, fSub, 0, H/2-10, W, 36, sfC, 0xFFFFE25A);
}

var drawTitle = function(g, W, H){
	//标题背景呼吸
	var pulse = 0.5 + 0.5*math.sin(game.t*1.2);
	var glow = brushOf((math.floor(0x10+0x14*pulse)<<24)|0x2E7FB0);
	g.fillEllipse(glow, W/2-300, H/2-300, 600, 600);
	drawText(g, "孤 单 枪 手", fTitle, 0, H/2-150, W, 80, sfC, 0xFF36E0FF);
	drawText(g, "LONE GUNSLINGER", fSub, 0, H/2-70, W, 30, sfC, 0xFF8FA0BF);
	//开始按钮
	drawBtn(g, W/2, H/2+50, 240, 56, "开始游戏", fBig);
	drawText(g, "或按 Enter", fSmall, 0, H/2+92, W, 20, sfC, 0xFF8FA0BF);
	drawText(g, "五十道门,一个枪手。穿越五十区,终结主脑「零」。", fBody, 0, H/2+110, W, 28, sfC, 0xFFE8F0FF);
	drawText(g, "WASD 移动 · 鼠标瞄准 · 左键开火 · P 暂停 · R 重开", fSmall, 0, H-60, W, 20, sfC, 0xFF8FA0BF);
}

var drawStory = function(g, W, H){
	var s = storyText(game.level);
	var ov = brushOf(0xD805070E);
	g.fillRectangle(ov, 0, 0, W, H);
	drawText(g, s.name, fBig, 0, H/2-150, W, 50, sfC, 0xFF36E0FF);
	//正文换行绘制
	var body = s.text;
	drawText(g, body, fBody, 80, H/2-80, W-160, 120, sfC, 0xFFE8F0FF);
	if(game.justCleared){
		drawText(g, "上一关通过!", fSub, 0, H/2-200, W, 30, sfC, 0xFF4ADE80);
	}
	drawBtn(g, W/2, H/2+90, 240, 50, "进入战场", fBig);
	drawText(g, "或按 Enter · R 重新开始", fSmall, 0, H/2+130, W, 20, sfC, 0xFF8FA0BF);
}

var drawGameOver = function(g, W, H){
	var ov = brushOf(0xC0180A0A);
	g.fillRectangle(ov, 0, 0, W, H);
	drawText(g, "阵 亡", fTitle, 0, H/2-110, W, 80, sfC, 0xFFFF5A5A);
	drawText(g, "孤狼倒在了第 " ++ tostring(game.level) ++ " 区。", fSub, 0, H/2-20, W, 30, sfC, 0xFFE8F0FF);
	drawText(g, "最终分数  " ++ tostring(game.score), fBig, 0, H/2+20, W, 40, sfC, 0xFFFFE25A);
	drawBtn(g, W/2, H/2+90, 240, 50, "重新开始", fBig);
	drawText(g, "或按 R / Enter", fSmall, 0, H/2+130, W, 20, sfC, 0xFF8FA0BF);
}

var drawWin = function(g, W, H){
	var ov = brushOf(0xC0050A18);
	g.fillRectangle(ov, 0, 0, W, H);
	drawText(g, "胜 利", fTitle, 0, H/2-130, W, 80, sfC, 0xFF4ADE80);
	drawText(g, "主脑「零」终结。五十区寂静,孤狼收枪。", fSub, 0, H/2-30, W, 30, sfC, 0xFFE8F0FF);
	drawText(g, "最终分数  " ++ tostring(game.score), fBig, 0, H/2+10, W, 40, sfC, 0xFFFFE25A);
	drawBtn(g, W/2, H/2+80, 240, 50, "再战一次", fBig);
	drawText(g, "或按 R", fSmall, 0, H/2+120, W, 20, sfC, 0xFF8FA0BF);
}

var render = function(g, W, H){
	game.W = W; game.H = H;

	drawBackground(g, W, H);

	if(game.state == "title"){ drawTitle(g, W, H); return; }
	if(game.state == "story"){ drawStory(g, W, H); return; }
	if(game.state == "gameover"){ drawGameOver(g, W, H); return; }
	if(game.state == "win"){ drawWin(g, W, H); return; }

	//playing / paused
	//场景(各绘制函数内已加 game.ox/oy 屏震偏移;背景与 HUD 不偏移)
	drawParticles(g);
	drawEBullets(g);
	for(i=1,#game.enemies,1){ drawEnemy(g, game.enemies[i]); }
	drawBullets(g);
	drawPlayer(g);
	drawFloats(g);

	drawCrosshair(g);
	drawHUD(g, W, H);

	if(game.state == "paused"){
		drawOverlay(g, W, H, "已 暂 停", "");
		drawBtn(g, W/2, H/2+30, 220, 50, "继续游戏", fBig);
		drawText(g, "或按 P 继续", fSmall, 0, H/2+70, W, 20, sfC, 0xFF8FA0BF);
	}
}

var lastTime = 0;  // dt 计时:time.performance.tick() 返回毫秒,/1000 转秒

//==================== 输入====================
var logicalMousePos = function(lParam){
	var x, y = win.getMessagePos(lParam);
	var dpiX, dpiY = winform.dpiScale(1, 1);
	return x / dpiX, y / dpiY;
}

winform.canvas.dlgCode = 4/*_DLGC_WANTALLKEYS*/;
winform.canvas.onKeyDown = function(keyCode, lParam, repeat){
	if(repeat) return;                       // 仅物理首次按下记一次边沿,避免菜单被跳过
	input.keys[keyCode] = true;
	input.pressed[keyCode] = true;
}
winform.canvas.onKeyUp = function(keyCode, lParam){
	input.keys[keyCode] = false;
}

// 鼠标:左键按下记「边沿」(菜单点击用)
winform.canvas.onMouseDown = function(wParam, lParam){
	var x, y = logicalMousePos(lParam);
	input.mx = x; input.my = y;
	input.keys[0x01] = true;
	input.pressed[0x01] = true;              // 左键按下边沿(菜单点击用)
}
winform.canvas.onMouseMove = function(wParam, lParam){
	var x, y = logicalMousePos(lParam);
	input.mx = x; input.my = y;
}
winform.canvas.onMouseUp = function(wParam, lParam){
	input.keys[0x01] = false;
}

//==================== 游戏循环 ====================
winform.canvas.onDrawBackground = function(graphics, rc, backgroundColor, color){
	return true;   // 跳过默认背景填充,全部交给 onDrawForeground 统一绘制
}

winform.canvas.onDrawForeground = function(graphics, rc, foregroundRect, foregroundColor, color, font){
	var dpiX, dpiY = winform.dpiScale(1, 1);
	graphics.smoothingMode = 4;        //抗锯齿
	graphics.textRenderingHint = 4;    //文字抗锯齿
	var saved = graphics.save();
	graphics.scale(dpiX, dpiY);
	var W = rc.width / dpiX;
	var H = rc.height / dpiY;

	if(game.err){
		graphics.fillRectangle(brushOf(0xFF10131C), 0, 0, W, H);
		graphics.drawString("运行错误:" ++ tostring(game.err), fBody, ::RECTF(20,20,W-40,400), sfL, brushOf(0xFFFF5A5A));
	}
	else{
		try{
			if(lastTime == 0) lastTime = time.performance.tick();
			var now = time.performance.tick();
			var dt = (now - lastTime) / 1000;         // tick 单位为毫秒 → 秒
			lastTime = now;
			if(dt > 0.05) dt = 0.05;                  // 防止卡顿后大跳
			if(dt <= 0) dt = 0.016;
			update(dt);
			input.pressed = {};                       // 每帧清空「按下边沿」,避免漏消费导致跨状态误触发
			render(graphics, W, H);
		}
		catch(e){ game.err = tostring(e); }
	}

	graphics.restore(saved);
	::User32.InvalidateRect(winform.canvas.hwnd, null, true);
}

//MIDI 设备清理
winform.onClose = function(){
	if(midiOut){ midiOut.reset(); midiOut.close(); }
}

winform.show();
winform.canvas.setFocus();   //键盘焦点聚焦画布
win.loopMessage();





最新回复 (3)
  • Mr_MAO 8小时前
    0 2
    👍👍👍
  • 近我者赤 3小时前
    0 3

    难度还不小来

  • mfk 2小时前
    0 4

    的确有难度。  218行改成母体0.1生命值还能多玩一会。 


    218行      var childHp = math.max(1, math.ceil(parent.maxhp * 0.1));//分裂体生命 = 母体满血 * 0.1

返回