开维游戏引擎(Kaiwei Engine)是基于js设计的跨平台游戏引擎。内核c++编写,v8引擎封装游戏函数,Assembly实现htm跨平台高效运行。
下面以“水果忍者”为例,用Gemini大模型自动生成游戏代码,手把手教你一步一步生成游戏,即便是不懂代码,也可以利用工具做出想要的游戏。
Gemini学习了gitbub上所有的源代码,只需要给出链接即可吐出代码,代码中所用的图片名称和路径都可对应。
最终运行效果,包括:windows版(exe)、网页版(html)、微信小游戏:
生成游戏主要步骤:
- 打开Geimin和安装开维游戏引擎。
- 问询Gemini生成代码;打开游戏引擎自带的第二个实例,。
- Gemini生成的代码覆盖打开的实例main.js,包括图片资源,运行即可。
- 继续让Gemini优化。
- 导出游戏exe版本、html网页版本、微信小游戏工程。
开维游戏引擎原理和AI使用方法:
开维游戏引擎js代码跨平台通用,一次编写,多端运行。支持导出exe、网页html、微信小游戏。网页使用wasm运行,小游戏运行效率网页版和exe无太大差别,比原生javacript游戏引擎运行速度快数倍。
开维游戏引擎适合AI写小游戏、特效或功能模块。利用AI模型自动生成特效代码,代码短小精炼,函数简单易懂,200行左右即可完成独立功能。导出的html网页可以内嵌到各种网站、安卓苹果手机app中。
开维引擎的底层是C++编写的高性能内核,通过V8引擎封装JavaScript接口,再通过WASM实现网页端高效运行。这意味着,虽然你用的是JavaScript,但实际执行效率远高于普通HTML5游戏引擎(如Phaser),接近原生应用。这一架构决定了它特别适合AI生成代码——因为AI模型只需理解JavaScript API层,无需关心底层C++实现,即可生成可直接运行的高效代码。因此以API为标准,避免参考其他引擎(如Unity或Cocos)的思维模式,否则容易混淆概念。
AI模型应用:示例代码中包含几乎所有基本控件函数,可以给AI 模型,例如:DeepSeek,Gemini,豆包等。 AI游戏生成:以示例为基础自动生成AI游戏代码,例如贪吃蛇、飞机大战、俄罗斯方块等。 AI提问:“**AI提问:“参考开维游戏引擎实例,把这个网址github.com/ChineseDron…
经过多次迭代,基本可以商用。
Gemini最终生成代码如下:
// 水果忍者小游戏,gemini生成,手工略微润色
// 微信导出小游戏时,选择横屏
// AI提问:“参考开维游戏引擎实例,把这个网址https://github.com/ChineseDron/fruit-ninja/tree/master的游戏转为开维游戏引擎js代码。参考代码如下:(拷贝002实例代码)”
// ----------------------------------------------------------------------------------------------
// 1. 初始化引擎与环境
// ----------------------------------------------------------------------------------------------
var system = game.getSystemName(); // 获取系统名称
var w, h; // 屏幕宽高
var window;
var screenType; // 横屏还是竖屏
if (system =="WINDOWS" || system =="WEB")
{
game.init() // windows默认窗口大小为800*600;web网页默认全屏
window = game.getWindow(); // 获取资源对象
w = window.getWidth(); // 屏幕宽带
h = window.getHeight(); // 屏幕高度
}
else if(system =="WEIXIN")
{
game.initSize(canvas.width,canvas.height); // 微信窗口
window = game.getWindow(); // 获取资源对象
w = canvas.width; // 微信窗口宽度
h = canvas.height;// 微信窗口高度
}
// 判断横屏还是竖屏
screenType = (w>h)?"Landscape":"Portrait"; // 横屏Landscape 竖屏Portrait
game.setFPS(60); // 设置帧率
// 游戏主窗口设置图标和标题
// ----------------------------------------------------------------------------------------------
var texture = game.getResource().getTexture("img/logo1.png"); // 获取纹理数据对象
window.setIcon(texture); // 设置主游戏窗口图标
window.setTitle("开维游戏引擎 - 水果忍者"); // 设置主游戏窗口标题
var scene = new Scene();
// 音频全局变量,一个音频变量即可,所有声音都用这个类。声音放在前面初始化
var audio = new Audio(); // 定义全局菜单背景音乐句柄
var resBg = game.getResource().getTexture("img/background.jpg");
scene.setBg(resBg);
// 加载关键 UI 资源
var resGameOver = game.getResource().getTexture("img/game-over.png");
var resScoreBg = game.getResource().getTexture("img/score.png");
var resNewGame = game.getResource().getTexture("img/new-game.png");
var resX = [game.getResource().getTexture("img/x.png"), game.getResource().getTexture("img/xx.png"), game.getResource().getTexture("img/xxx.png")];
var resXF = [game.getResource().getTexture("img/xf.png"), game.getResource().getTexture("img/xxf.png"), game.getResource().getTexture("img/xxxf.png")];
// 新增:加载lose图片资源
var resLose = game.getResource().getTexture("img/lose.png");
// 游戏状态
var score = 0;
var missCount = 0; // 记录掉落的水果数量
var fruits = [];
var GRAVITY = 0.5;
var isGameOver = false;
var canReturnToMenu = false; // 新增:控制是否可以点击返回主页面
// --- 配置表:地雷现在和水果一样定义在数组中 ---
var FRUIT_TYPES = [
{ name: "apple", color: [128, 255, 0], img: "img/fruit/apple.png", cut1: "img/fruit/apple-1.png", cut2: "img/fruit/apple-2.png", size: 70 },
{ name: "banana", color: [255, 255, 0], img: "img/fruit/banana.png", cut1: "img/fruit/banana-1.png", cut2: "img/fruit/banana-2.png", size: 120 },
{ name: "peach", color: [255, 255, 0],img: "img/fruit/peach.png", cut1: "img/fruit/peach-1.png", cut2: "img/fruit/peach-2.png", size: 70 },
{ name: "basaha", color: [204, 0, 0],img: "img/fruit/basaha.png", cut1: "img/fruit/basaha-1.png", cut2: "img/fruit/basaha-2.png", size: 80 },
{ name: "sandia", color: [204, 0, 0],img: "img/fruit/sandia.png", cut1: "img/fruit/sandia-1.png", cut2: "img/fruit/sandia-2.png", size: 100 },
// 地雷配置:指定 boom.png 和灰色爆炸粒子
{ name: "bomb", color: [80, 80, 80], img: "img/fruit/boom.png", size: 70, isBomb: true }
];
// ----------------------------------------------------------------------------------------------
// 2. 贝塞尔轨迹系统——对象池与插值优化
// ----------------------------------------------------------------------------------------------
var linePool = [];
var poolSize = 150;
var poolIndex = 0;
var isPressing = false;
var trailPoints = [];
for (var i = 0; i < poolSize; i++) {
var l = new Node();
l.setColor(255, 255, 255, 0);
l.setHide(true);
scene.addNode(l);
linePool.push(l);
}
// --- 粒子对象池预申请 ---
// 微信优化,circle.png不能太大,减少加载内存
var particlePool = [];
var partPoolSize = 20;
var partIndex = 0;
var circleTexture = game.getResource().getTexture("img/circle.png");
for (var i = 0; i < partPoolSize; i++) {
var p = new Sprite();
p.setTexture(circleTexture);
p.setHide(true);
scene.addNode(p);
particlePool.push(p);
}
function drawSegment(x1, y1, x2, y2, thickness) {
var dx = x2 - x1;
var dy = y2 - y1;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 0.5) return;
var line = linePool[poolIndex];
line.setHide(false);
line.setSize(distance + 2.5, thickness);
line.setPosition(x1, y1);
line.setRotate(Math.atan2(dy, dx) * 180 / Math.PI);
var alpha = 1.0;
line.setColor(255, 255, 255, alpha);
line.upDate((time) => {
alpha -= 0.15;
if (alpha <= 0) { line.setHide(true); }
else {
line.setColor(255, 255, 255, alpha);
line.setSize(distance + 2.5, thickness * (alpha * alpha));
}
});
poolIndex = (poolIndex + 1) % poolSize;
}
function drawBezierTrail(p0, p1, p2) {
var steps = 6;
var prevX = p0.x; var prevY = p0.y;
for (var i = 1; i <= steps; i++) {
var t = i / steps;
var tx = Math.pow(1 - t, 2) * p0.x + 2 * t * (1 - t) * p1.x + Math.pow(t, 2) * p2.x;
var ty = Math.pow(1 - t, 2) * p0.y + 2 * t * (1 - t) * p1.y + Math.pow(t, 2) * p2.y;
drawSegment(prevX, prevY, tx, ty, 12);
prevX = tx; prevY = ty;
}
}
function spawnFireworkParticles(x, y, rgb) {
var count = 45;
var r = rgb[0], g = rgb[1], b = rgb[2];
for (var i = 0; i < count; i++) {
var p = particlePool[partIndex];
p.setHide(false);
var size = Math.random() * 5 + 3;
p.setSize(size, size);
p.setPosition(x, y);
var angle = Math.random() * Math.PI * 2;
var speed = Math.random() * 15 + 5;
p.vx = Math.cos(angle) * speed;
p.vy = Math.sin(angle) * speed;
var life = 1.0;
(function(particle, pr, pg, pb) {
particle.upDate((time) => {
life -= 0.0016;
if (life <= 0) { particle.setHide(true); }
else {
var pos = particle.getPosition();
particle.vy += 0.12; particle.vx *= 0.975; particle.vy *= 0.975;
particle.setPosition(pos.x + particle.vx, pos.y + particle.vy);
particle.setColor(pr, pg, pb, life);
}
});
})(p, r, g, b);
partIndex = (partIndex + 1) % partPoolSize;
}
}
// ----------------------------------------------------------------------------------------------
// 3. 标签显示
// ----------------------------------------------------------------------------------------------
// 左上角 Score 背景图
var sprScoreBg = new Sprite();
sprScoreBg.setTexture(resScoreBg);
sprScoreBg.setPosition(10, 10);
sprScoreBg.setSize(30, 30);
scene.addNode(sprScoreBg);
var labScore = new Label();
labScore.setPosition(45, 12); // 调整位置使其位于图片中心偏右
labScore.setSize(100, 30);
labScore.setFont("font/st.ttf", 25);
labScore.setColor(0,0,0,0); // 标签背景颜色为黑色并透明
labScore.setTextColor(255, 255, 0, 1);
labScore.setText("0");
scene.addNode(labScore);
// 右上角图标显示 - 修改点:增加绽放和缩小消失效果
var lifeIcons = [];
var lifeSizes = [25, 30, 33]; // 定义三个图标的原始大小
for(var i = 0; i < 3; i++) {
var icon = new Sprite();
icon.setTexture(resX[i]);
var s = lifeSizes[i];
icon.setSize(0, 0); // 初始大小为0,实现“绽放”前置状态
icon.originSize = s; // 记录原始尺寸
icon.setPosition(w - 130 + i * lifeSizes[i], 25 - (s / 2));
scene.addNode(icon);
lifeIcons.push(icon);
// 初始绽放动画
(function(target, targetSize){
var growScale = 0;
target.upDate((dt) => {
if (growScale < 1.0) {
growScale += 0.05; // 控制绽放速度
if (growScale > 1.0) growScale = 1.0;
// 模拟花朵绽放:从小到大,可以稍微超过1.0再回到1.0(弹性效果)
var elasticScale = growScale == 1.0 ? 1.0 : growScale * 1.1;
target.setSize(targetSize * elasticScale, targetSize * elasticScale);
}
});
})(icon, s);
}
// ----------------------------------------------------------------------------------------------
// 4. 物体生成逻辑
// ----------------------------------------------------------------------------------------------
// 新增:强制清理屏幕上所有水果和碎片
function clearAllActiveFruits() {
for (var i = 0; i < fruits.length; i++) {
fruits[i].setHide(true);
scene.removeNode(fruits[i]);
}
fruits = [];
}
function createFruit() {
if (isGameOver) return;
var f = new Sprite();
var type = FRUIT_TYPES[Math.floor(Math.random() * FRUIT_TYPES.length)];
var resTex = game.getResource().getTexture(type.img);
f.setTexture(resTex);
f.setSize(type.size, type.size);
f.fruitType = type;
var startX = Math.random() * (w - 100) + 50;
var startY = h + 50;
f.setPosition(startX, startY);
f.vx = (w / 2 - startX) * 0.02 + (Math.random() - 0.5) * 5;
f.vy = -(Math.random() * 5 + 18);
f.isCut = false;
f.tag = type.isBomb ? "bomb" : "whole";
// 每个水果弹出时播放 throw.mp3
audio.playSound("sound/throw.mp3");
f.upDate((time) => {
if (f.isCut || isGameOver) return;
var pos = f.getPosition();
f.vy += GRAVITY;
f.setPosition(pos.x + f.vx, pos.y + f.vy);
// 只有不是地雷时才旋转(地雷不旋转)
if (f.fruitType.name !== "bomb") {
f.setRotate(f.getRotate() + 5);
}
// 掉落检测逻辑
if (pos.y > h + 50) {
if (f.tag == "whole") {
handleMiss();
// 新功能:水果落下时,在落下位置显示 lose.png,3秒后消失
var loseIcon = new Sprite();
loseIcon.setTexture(resLose);
loseIcon.setSize(50, 50); // 设置合适的大小
loseIcon.setPosition(pos.x, h - 60); // 在屏幕底部附近显示
scene.addNode(loseIcon);
var loseTimer = 0;
loseIcon.upDate((dt) => {
loseTimer += dt;
if (loseTimer >= 3.0) {
loseIcon.setHide(true);
scene.removeNode(loseIcon);
}
});
}
f.setHide(true);
}
});
scene.addNode(f);
fruits.push(f);
}
function handleMiss() {
if (isGameOver) return;
missCount++;
if (missCount <= 3) {
var currentIcon = lifeIcons[missCount-1];
// 修改点:实现原叉号图片“缩小消失”,然后“绽放显示”红色叉号
var shrinkScale = 1.0;
currentIcon.upDate((dt) => {
if (shrinkScale > 0) {
shrinkScale -= 0.1; // 缩小消失
if (shrinkScale < 0) shrinkScale = 0;
currentIcon.setSize(currentIcon.originSize * shrinkScale, currentIcon.originSize * shrinkScale);
} else if (shrinkScale == 0) {
// 彻底消失后,更换纹理并重新绽放
currentIcon.setTexture(resXF[missCount-1]);
shrinkScale = -0.01; // 进入绽放阶段的标记
} else if (shrinkScale < 0) {
// 红色叉号向花朵一样绽放
var growScale = Math.abs(shrinkScale);
growScale += 0.1;
if (growScale > 1.0) growScale = 1.0;
currentIcon.setSize(currentIcon.originSize * growScale, currentIcon.originSize * growScale);
shrinkScale = -growScale;
}
});
}
if (missCount >= 3) {
triggerGameOver();
}
}
// ----------------------------------------------------------------------------------------------
// 5. 切割逻辑与交互
// ----------------------------------------------------------------------------------------------
// 封装重置函数
function resetGame() {
score = 0;
missCount = 0;
canReturnToMenu = false; // 重置返回标志
spawnTimer = 0; // 核心修改:确保计时器重置,否则进入游戏不会立刻出水果
labScore.setText("0");
clearAllActiveFruits(); // 使用清理函数重置水果列表
// 强制重置生命图标状态
for (var i = 0; i < 3; i++) {
lifeIcons[i].setTexture(resX[i]);
lifeIcons[i].setHide(false);
lifeIcons[i].setSize(lifeIcons[i].originSize, lifeIcons[i].originSize);
}
if (maskNode) { maskNode.setHide(true); }
if (gameOverNode) {
gameOverNode.setHide(true);
// 核心修改:移除可能残留的计时回调,防止第二次瞬间触发
gameOverNode.upDate(() => {});
}
if (whiteFlashNode) { whiteFlashNode.setHide(true); }
if(menuSandia) menuSandia.setHide(false);
if(menuCircle) menuCircle.setHide(false);
if(menuPeach) menuPeach.setHide(false);
if(menuCircleLeft) menuCircleLeft.setHide(false);
if(menuBomb) menuBomb.setHide(false);
if(menuCircleRight) menuCircleRight.setHide(false);
isGameOver = false; // 核心修改:放在最后重置状态位
}
scene.onPress((x, y) => {
// 修改点:必须在 GameOver 状态且 3 秒延时结束(canReturnToMenu 为 true)时才处理返回
if (isGameOver) {
if (!canReturnToMenu) return; // 3秒没到,点击无效
resetGame();
audio.playSound("sound/start.mp3");
audio.playMusic("sound/menu.mp3");
// 返回主菜单场景
game.popScene();
return;
}
isPressing = true;
trailPoints = [{x: x, y: y}];
hasSwiped = false; // 释放时重置状态
});
scene.onMove((x, y) => {
if (isGameOver) return;
if (!isPressing) return;
// 计算当前位移距离
var moveDist = Math.sqrt(Math.pow(x - mStartX, 2) + Math.pow(y - mStartY, 2));
// 如果手指移动距离超过 100 像素,判定为滑动行为(非单纯点击)
if (moveDist > 100) {
hasSwiped = true;
}
if (!hasSwiped) return;
trailPoints.push({x: x, y: y});
if (trailPoints.length >= 3) {
var p0 = trailPoints[trailPoints.length - 3];
var p1 = trailPoints[trailPoints.length - 2];
var p2 = trailPoints[trailPoints.length - 1];
drawBezierTrail(p0, p1, p2);
if (trailPoints.length > 5) trailPoints.shift();
}
for (var i = 0; i < fruits.length; i++) {
var f = fruits[i];
if (!f.isCut && f.isContainPostion(x, y)) {
if (f.tag == "whole") sliceFruit(f);
else if (f.tag == "bomb") triggerBomb(f);
}
}
});
scene.onRelease((x, y) => {
isPressing = false;
trailPoints = [];
hasSwiped = false; // 释放时重置状态
});
/**
* 新增:更密集的粒子生成函数,专门用于爆炸效果
* @param {number} x 坐标
* @param {number} y 坐标
* @param {Array} rgb 颜色
* @param {number} count 粒子数量
* @param {number} maxSpeed 最大速度
*/
/**
* 严格修改后的粒子生成函数:实现随机五颜六色的爆炸效果
* @param {number} x 爆炸中心X
* @param {number} y 爆炸中心Y
* @param {Array} rgb 原参数保留(但在内部会被随机颜色覆盖)
* @param {number} count 粒子数量
* @param {number} maxSpeed 粒子最大扩散速度
*/
function spawnDenseParticles(x, y, rgb, count, maxSpeed) {
for (var i = 0; i < count; i++) {
var p = particlePool[partIndex];
p.setHide(false);
var size = Math.random() * 8 + 4;
p.setSize(size, size);
p.setPosition(x, y);
var angle = Math.random() * Math.PI * 2;
var speed = Math.random() * maxSpeed + 2;
p.vx = Math.cos(angle) * speed;
p.vy = Math.sin(angle) * speed;
// --- 核心修改:生成随机颜色 (参考雷引线吐出的五颜六色) ---
// 逻辑:确保三个通道中至少有一个高亮,一个低亮,产生高饱和度的鲜艳色彩
var colors = [255, 0, Math.floor(Math.random() * 255)];
colors.sort(() => Math.random() - 0.5); // 随机打乱 RGB 顺序
var r = colors[0];
var g = colors[1];
var b = colors[2];
var life = 1.0;
(function(particle, pr, pg, pb) {
particle.upDate((time) => {
// 保持你原来的生命值衰减逻辑
life -= 0.0001; // 注意:0.0002 可能过慢导致粒子堆积,建议使用 0.01 左右,你可以根据实际需要微调
if (life <= 0) {
particle.setHide(true);
} else {
var pos = particle.getPosition();
// 粒子空气阻力
particle.vx *= 0.96;
particle.vy *= 0.96;
// 轻微重力
particle.vy += 0.05;
particle.setPosition(pos.x + particle.vx, pos.y + particle.vy);
// 应用随机生成的颜色及透明度
particle.setColor(pr, pg, pb, life);
}
});
})(p, r, g, b);
partIndex = (partIndex + 1) % partPoolSize;
}
}
// --- 修改点:地雷爆炸时序优化 ---
var whiteFlashNode;
function triggerBomb(bombNode) {
if (isGameOver) return;
isGameOver = true;
// 修改点:触雷瞬间立即清除所有水果
clearAllActiveFruits();
// 隐藏生命叉号
for (var i = 0; i < lifeIcons.length; i++) {
lifeIcons[i].setHide(true);
}
var pos = bombNode.getPosition();
bombNode.setHide(true);
audio.playSound("sound/boom.mp3");
// 3. 【核心替换】:使用粒子发散替代原来的条幅
// 喷射三层粒子:白色核心粒子、灰色烟雾粒子、金色火花粒子
spawnDenseParticles(pos.x, pos.y, [255, 255, 255], 60, 20); // 白色核心
spawnDenseParticles(pos.x, pos.y, [100, 100, 100], 40, 15); // 灰色烟雾
spawnDenseParticles(pos.x, pos.y, [255, 200, 50], 30, 25); // 金色火花
/*// 1. 立即喷射白条特效
for (var i = 0; i < 10; i++) {
var ray = new Node();
ray.setColor(255, 255, 255, 1);
var angle = i * 36;
ray.setRotate(angle);
ray.setPosition(pos.x, pos.y);
ray.setSize(10, 2);
scene.addNode(ray);
(function(node, startAngle) {
var rayLife = 0;
node.upDate((dt) => {
rayLife += dt;
var currentLen = rayLife * 100;
var currentAlpha = 1.0 - (rayLife / 3.0);
if (currentAlpha <= 0) {
node.setHide(true);
scene.removeNode(node);
} else {
node.setSize(currentLen, 45 * currentAlpha);
node.setColor(255, 255, 255, currentAlpha);
node.setRotate(startAngle + rayLife * 50);
}
});
})(ray, angle);
}*/
// 2. 时序控制 - 闪白特效
var sequenceTimer = 0;
var flashStarted = false;
// 核心修改:使用一个独立的全局计时逻辑,确保闪白后一定会触发 GameOver UI
var bombProcessNode = new Node();
scene.addNode(bombProcessNode);
var bombone = false; // 增加变量,只运行一次后结束回调
bombProcessNode.upDate((dt) => {
if (bombone) return;
sequenceTimer += dt;
if (sequenceTimer > 0.2 && !flashStarted) {
flashStarted = true;
if (!whiteFlashNode) {
whiteFlashNode = new Node();
whiteFlashNode.setSize(w, h);
whiteFlashNode.setPosition(0, 0);
scene.addNode(whiteFlashNode);
}
whiteFlashNode.setHide(false);
whiteFlashNode.setColor(255, 255, 255, 1);
var flashAlpha = 1.0;
var flashHoldTimer = 0;
var whiteFlash = false; // 增加变量,只运行一次后结束回调
whiteFlashNode.upDate((fdt) => {
if (whiteFlash) return;
flashHoldTimer += fdt;
if (flashHoldTimer > 0.5) {
flashAlpha -= 0.015;
if (flashAlpha <= 0) {
flashAlpha = 0;
whiteFlashNode.setHide(true);
triggerGameOverUI(); // 闪白结束后进入结算停留阶段
scene.removeNode(bombProcessNode); // 处理完毕移除
whiteFlash = true;
}
whiteFlashNode.setColor(255, 255, 255, flashAlpha);
}
});
bombone = true;
}
});
}
var maskNode, gameOverNode;
// 修改点:结算 UI 显示后,增加 3 秒倒计时才允许返回
function triggerGameOverUI() {
clearAllActiveFruits();
// 隐藏叉号
for (var i = 0; i < lifeIcons.length; i++) {
lifeIcons[i].setHide(true);
}
if (!maskNode) {
maskNode = new Node();
maskNode.setSize(w, h);
maskNode.setColor(0, 0, 0, 0.6);
maskNode.setPosition(0, 0);
scene.addNode(maskNode);
}
maskNode.setHide(false);
if (!gameOverNode) {
gameOverNode = new Sprite();
gameOverNode.setTexture(resGameOver);
var imgW = 500; var imgH = 100;
gameOverNode.setSize(imgW, imgH);
gameOverNode.setPosition((w - imgW) / 2, (h - imgH) / 2);
scene.addNode(gameOverNode);
}
gameOverNode.setHide(false);
audio.playSound("sound/over.mp3");
// 关键修改:开始停留计时
var returnWaitTimer = 0;
canReturnToMenu = false; // 先锁定返回功能
gameOverNode.upDate((dt) => {
if (!canReturnToMenu) {
returnWaitTimer += dt;
if (returnWaitTimer >= 3.0) {
canReturnToMenu = true; // 3秒后解锁
}
}
});
}
function triggerGameOver() {
if (isGameOver) return;
isGameOver = true;
clearAllActiveFruits();
triggerGameOverUI();
}
function sliceFruit(f) {
if (isGameOver) return;
f.isCut = true; f.tag = "half";
audio.playSound("sound/splatter.mp3"); // 用全局声音,一直申请内存会变大变卡
var oldPos = f.getPosition();
var oldVx = f.vx; var oldVy = f.vy;
var type = f.fruitType;
spawnFireworkParticles(oldPos.x, oldPos.y, type.color);
var resCut1 = game.getResource().getTexture(type.cut1);
f.setTexture(resCut1);
f.vx = oldVx - 2; f.vy = oldVy;
f.upDate((time) => {
if (isGameOver) { f.setHide(true); return; }
var pos = f.getPosition();
f.vy += GRAVITY;
f.setPosition(pos.x + f.vx, pos.y + f.vy);
f.setRotate(f.getRotate() + 2);
});
var f2 = new Sprite();
f2.setTexture(game.getResource().getTexture(type.cut2));
f2.setSize(type.size, type.size);
f2.setPosition(oldPos.x, oldPos.y);
f2.vx = oldVx + 2; f2.vy = oldVy; f2.isCut = true;
f2.upDate((time) => {
if (isGameOver) { f2.setHide(true); return; }
var pos = f2.getPosition();
f2.vy += GRAVITY;
f2.setPosition(pos.x + f2.vx, pos.y + f2.vy);
f2.setRotate(f2.getRotate() - 2);
if (pos.y > h + 100) f2.setHide(true);
});
scene.addNode(f2);
fruits.push(f2);
score += 1;
labScore.setText("" + score);
}
// ----------------------------------------------------------------------------------------------
// 6. 游戏主循环
// ----------------------------------------------------------------------------------------------
var spawnTimer = 0;
scene.upDate((time) => {
if (isGameOver) return;
spawnTimer += time;
if (spawnTimer > 1.2) {
var count = Math.floor(Math.random() * 2) + 1;
for (var i = 0; i < count; i++) { createFruit(); }
spawnTimer = 0;
}
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i].isHide()) {
scene.removeNode(fruits[i]);
fruits.splice(i, 1);
}
}
});
// ----------------------------------------------------------------------------------------------
// 7. 启动页面逻辑
// ----------------------------------------------------------------------------------------------
var menuScene = new Scene();
menuScene.setBg(resBg);
// 启动页增加音乐逻辑
audio.playSound("sound/start.mp3");
// 特效播放后开始循环播放菜单背景音乐
audio.playMusic("sound/menu.mp3");
for (var i = 0; i < poolSize; i++) { menuScene.addNode(linePool[i]); }
for (var i = 0; i < partPoolSize; i++) { menuScene.addNode(particlePool[i]); }
// --- 启动页新增 UI 动画组件 ---
// 1. home-mask 和 logo 显示(从上到下)
var sprHomeMask = new Sprite();
sprHomeMask.setTexture(game.getResource().getTexture("img/home-mask.png"));
sprHomeMask.setSize(w, 180);
sprHomeMask.setPosition(0, -180); // 初始在屏幕上方
menuScene.addNode(sprHomeMask);
var sprLogo = new Sprite();
sprLogo.setTexture(game.getResource().getTexture("img/logo.png"));
sprLogo.setSize(270, 120);
sprLogo.setPosition(20, -150); // 初始在屏幕上方
menuScene.addNode(sprLogo);
// 2. ninja 显示(重力加速度下落)
var sprNinja = new Sprite();
sprNinja.setTexture(game.getResource().getTexture("img/ninja.png"));
sprNinja.setSize(240, 80);
sprNinja.setPosition((w - 200) / 1.8, -100); // 初始在屏幕上方
sprNinja.vY = 0; // 重力速度
menuScene.addNode(sprNinja);
// 3. home-desc 显示(左到右)
var sprHomeDesc = new Sprite();
sprHomeDesc.setTexture(game.getResource().getTexture("img/home-desc.png"));
sprHomeDesc.setSize(180, 80);
sprHomeDesc.setPosition(-200, 140); // 初始在左侧屏幕外
menuScene.addNode(sprHomeDesc);
// --- 菜单配置与生成逻辑 ---
// 中间菜单 (Sandia)
var menuSandia = new Sprite();
menuSandia.setTexture(game.getResource().getTexture("img/fruit/sandia.png"));
menuSandia.setSize(100, 100);
menuSandia.setPosition((w - 100) / 2, (h - 100) / 1.5);
menuScene.addNode(menuSandia);
var menuCircle = new Sprite();
menuCircle.setTexture(resNewGame);
menuCircle.setSize(200, 200);
menuCircle.setPosition((w - 200) / 2, (h - 200) / 1.4);
menuScene.addNode(menuCircle);
// 左侧菜单 (Peach) - 圆圈略小
var menuPeach = new Sprite();
menuPeach.setTexture(game.getResource().getTexture("img/fruit/peach.png"));
menuPeach.setSize(70, 70);
menuPeach.setPosition(w * 0.15, (h - 70) / 1.5);
menuScene.addNode(menuPeach);
var menuCircleLeft = new Sprite();
menuCircleLeft.setTexture(game.getResource().getTexture("img/dojo.png"));
menuCircleLeft.setSize(160, 160);
menuCircleLeft.setPosition(w * 0.15 - 45, (h - 160) / 1.4);
menuScene.addNode(menuCircleLeft);
// 右侧菜单 (Bomb) - 圆圈最小
var menuBomb = new Sprite();
menuBomb.setTexture(game.getResource().getTexture("img/fruit/boom.png"));
menuBomb.setSize(70, 70);
menuBomb.setPosition(w * 0.8 - 70, (h - 70) / 1.45);
menuScene.addNode(menuBomb);
var menuCircleRight = new Sprite();
menuCircleRight.setTexture(game.getResource().getTexture("img/quit.png"));
menuCircleRight.setSize(130, 130);
menuCircleRight.setPosition(w * 0.8 - 100, (h - 130) / 1.4);
menuScene.addNode(menuCircleRight);
var resFlash = game.getResource().getTexture("img/circle.png");
var flashTimer = 0;
// 启动页物体更新逻辑
menuScene.upDate((time) => {
// Mask 滑入
var maskPos = sprHomeMask.getPosition();
if (maskPos.y < 0) sprHomeMask.setPosition(0, maskPos.y + 10);
// Logo 滑入
var logoPos = sprLogo.getPosition();
if (logoPos.y < 10) sprLogo.setPosition(logoPos.x, logoPos.y + 8);
// Ninja 下落(带回弹效果)
var ninjaPos = sprNinja.getPosition();
var targetY = 50;
var bounceLimit = 0.5;
if (Math.abs(ninjaPos.y - targetY) > 0.1 || Math.abs(sprNinja.vY) > bounceLimit) {
sprNinja.vY += 0.4;
var nextY = ninjaPos.y + sprNinja.vY;
if (nextY >= targetY) {
nextY = targetY;
if (Math.abs(sprNinja.vY) < 1.5) {
sprNinja.vY = 0;
} else {
sprNinja.vY *= -0.5;
}
}
sprNinja.setPosition(ninjaPos.x, nextY);
} else {
sprNinja.setPosition(ninjaPos.x, targetY);
sprNinja.vY = 0;
}
// Desc 从左滑入
var descPos = sprHomeDesc.getPosition();
if (descPos.x < 20) sprHomeDesc.setPosition(descPos.x + 8, descPos.y);
// 原有旋转逻辑
menuSandia.setRotate(menuSandia.getRotate() - 1);
menuCircle.setRotate(menuCircle.getRotate() - 0.5);
// 新增菜单旋转
menuPeach.setRotate(menuPeach.getRotate() + 1);
menuCircleLeft.setRotate(menuCircleLeft.getRotate() - 0.5);
menuCircleRight.setRotate(menuCircleRight.getRotate() - 0.5);
// 启动页面右侧累烟花的吐出特效
if (!menuBomb.isHide()) {
flashTimer += time;
if (flashTimer > 0.15) {
flashTimer = 0;
var f = new Sprite();
f.setTexture(resFlash);
var s = 5 + Math.random() * 8;
f.setSize(s, s);
var bPos = menuBomb.getPosition();
f.setPosition(bPos.x, bPos.y);
f.vx = -1.5 - Math.random() * 3;
f.vy = -2 - Math.random() * 4;
var life = 0.8;
var initS = s;
var colors = [255, 0, Math.floor(Math.random() * 255)];
colors.sort(() => Math.random() - 0.5);
var r = colors[0];
var g = colors[1];
var b = colors[2];
f.upDate((time) => {
life -= time;
if (life <= 0) {
f.setHide(true);
menuScene.removeNode(f); // 删除节点,微信优化
} else {
var pos = f.getPosition();
f.vy += GRAVITY * 0.3;
f.setPosition(pos.x + f.vx, pos.y + f.vy);
var scale = life / 0.8;
f.setSize(initS * scale, initS * scale);
f.setColor(r, g, b, scale);
}
});
menuScene.addNode(f); // 只有一个参数,微信优化
}
}
});
var mPress = false;
var mPoints = [];
// --- 修改点:增加启动页面的位移判定变量 ---
var mStartX = 0;
var mStartY = 0;
var hasSwiped = false; // 是否已经判定为滑动的标志位
menuScene.onPress((x, y) => {
mPress = true;
mPoints = [{x: x, y: y}];
// 记录起始点
mStartX = x;
mStartY = y;
hasSwiped = false; // 每次按下重置状态
});
menuScene.onMove((x, y) => {
if (!mPress) return;
mPoints.push({x: x, y: y});
// 计算当前位移距离
var moveDist = Math.sqrt(Math.pow(x - mStartX, 2) + Math.pow(y - mStartY, 2));
// 如果手指移动距离超过 15 像素,判定为滑动行为(非单纯点击)
if (moveDist > 15) {
hasSwiped = true;
}
if (mPoints.length >= 3) {
var p0 = mPoints[mPoints.length - 3];
var p1 = mPoints[mPoints.length - 2];
var p2 = mPoints[mPoints.length - 1];
var steps = 6; var prevX = p0.x; var prevY = p0.y;
for (var i = 1; i <= steps; i++) {
var t = i / steps;
var tx = Math.pow(1 - t, 2) * p0.x + 2 * t * (1 - t) * p1.x + Math.pow(t, 2) * p2.x;
var ty = Math.pow(1 - t, 2) * p0.y + 2 * t * (1 - t) * p1.y + Math.pow(t, 2) * p2.y;
drawSegment(prevX, prevY, tx, ty, 12);
prevX = tx; prevY = ty;
}
if (mPoints.length > 5) mPoints.shift();
}
// 核心判定修改:只有当 hasSwiped 为 true 时,才允许切开西瓜或触发按钮
if (hasSwiped) {
// 中间西瓜:进入游戏
if (!menuSandia.isHide() && menuSandia.isContainPostion(x, y)) {
audio.stopMusic();
audio.playSound("sound/splatter.mp3");
spawnFireworkParticles(menuSandia.getPosition().x, menuSandia.getPosition().y, [204, 0, 0]);
menuSandia.setHide(true);
menuCircle.setHide(true);
menuPeach.setHide(true);
menuCircleLeft.setHide(true);
menuBomb.setHide(true);
menuCircleRight.setHide(true);
resetGame();
game.pushScene(scene);
}
// 左侧桃子:切割效果,先不做跳转处理
if (!menuPeach.isHide() && menuPeach.isContainPostion(x, y)) {
audio.playSound("sound/splatter.mp3");
spawnFireworkParticles(menuPeach.getPosition().x, menuPeach.getPosition().y, [255, 255, 0]);
menuPeach.setHide(true);
menuCircleLeft.setHide(true);
}
// 右侧地雷:切割效果,先不做跳转处理
if (!menuBomb.isHide() && menuBomb.isContainPostion(x, y)) {
audio.playSound("sound/boom.mp3");
spawnFireworkParticles(menuBomb.getPosition().x, menuBomb.getPosition().y, [80, 80, 80]);
menuBomb.setHide(true);
menuCircleRight.setHide(true);
}
}
});
menuScene.onRelease(() => {
mPress = false;
mPoints = [];
hasSwiped = false; // 释放时重置状态
});
// ----------------------------------------------------------------------------------------------
// 8. 运行
// ----------------------------------------------------------------------------------------------
game.pushScene(menuScene);
game.run();
生成应用:将工程导出exe,html,微信小游戏。
点击IDE中“工具”->“一键生成全部应用”选项,选择微信横屏。
网页版游戏运行:
微信小游戏运行:
网页版:水果忍者
源码下载:
开维游戏引擎下载:
www.ikaiwei.com/download/ga… www.ikaiwei.com/download/ga…
开发文档:
游戏演示:普通代码和AI 代码