diff --git a/src/activities/land_safe/LandSafe.qml b/src/activities/land_safe/LandSafe.qml index 45df2a2cc..c264f7428 100644 --- a/src/activities/land_safe/LandSafe.qml +++ b/src/activities/land_safe/LandSafe.qml @@ -1,530 +1,563 @@ /* GCompris - LandSafe.qml * * Copyright (C) 2016 Holger Kaelberer * * Authors: * Matilda Bernard (GTK+ version) * Holger Kaelberer (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ import QtQuick 2.1 import Box2D 2.0 import QtQuick.Particles 2.0 import GCompris 1.0 import "../../core" import "land_safe.js" as Activity ActivityBase { id: activity onStart: focus = true onStop: {} Keys.onPressed: Activity.processKeyPress(event) Keys.onReleased: Activity.processKeyRelease(event) pageComponent: Image { id: background source: Activity.baseUrl + "/background4.jpg"; anchors.centerIn: parent anchors.fill: parent signal start signal stop Component.onCompleted: { activity.start.connect(start) activity.stop.connect(stop) } // Add here the QML items you need to access in javascript QtObject { id: items property Item main: activity.main property alias background: background property alias bar: bar property alias bonus: bonus property alias rocket: rocket property alias world: physicsWorld property alias landing: landing property alias ground: ground property alias stats: stats + property alias intro: intro + property alias ok: ok property var rocketCategory: Fixture.Category1 property var groundCategory: Fixture.Category2 property var landingCategory: Fixture.Category3 property var borderCategory: Fixture.Category4 property string mode: "rotate" // "simple" property double lastVelocity: 0.0 } onStart: { Activity.start(items) } onStop: { Activity.stop() } World { id: physicsWorld running: false; gravity: Qt.point(0, Activity.gravity) pixelsPerMeter: 0 autoClearForces: false //timeStep: 1.0/60.0 // default: 60Hz onStepped: { if (Math.abs(rocket.body.linearVelocity.y) > 0.01) // need to store velocity before it is aaaalmost 0 because of ground/landing contact items.lastVelocity = stats.velocity.y; stats.velocity = rocket.body.linearVelocity; stats.height = Math.max(0, Math.round(Activity.getRealHeight())); // update fuel: var dt = timeStep; var dFuel = -(dt * (items.rocket.accel + items.rocket.leftAccel + items.rocket.rightAccel)); Activity.currentFuel = Math.max(0, Activity.currentFuel + dFuel); stats.fuel = Math.round(Activity.currentFuel / Activity.maxFuel * 100); if (Activity.currentFuel === 0) // fuel consumed: items.rocket.accel = items.rocket.leftAccel = items.rocket.rightAccel = 0; // console.log("VVV changed: " + items.lastVelocity + " --> " + stats.velocity.y + " / " + rocket.body.linearVelocity.y); } } MouseArea { id: mouse anchors.fill: parent onClicked: debugDraw.visible = !debugDraw.visible; } // bounding fixtures Item { id: leftBorder width: 1 height: parent.height anchors.left: parent.left anchors.top: parent.top readonly property string collisionName: "leftBorder" Body { id: leftBody target: leftBorder bodyType: Body.Static sleepingAllowed: false fixedRotation: true linearDamping: 0 fixtures: Box { id: leftFixture categories: items.borderCategory collidesWith: items.rocketCategory density: 1 friction: 0 restitution: 0 width: leftBorder.width height: leftBorder.height } } } Item { id: rightBorder width: 1 height: parent.height anchors.right: parent.right anchors.rightMargin: 1 anchors.top: parent.top readonly property string collisionName: "rightBorder" Body { id: rightBody target: rightBorder bodyType: Body.Static sleepingAllowed: false fixedRotation: true linearDamping: 0 fixtures: Box { id: rightFixture categories: items.borderCategory collidesWith: items.rocketCategory density: 1 friction: 0 restitution: 0 width: rightBorder.width height: rightBorder.height } } } GCText { id: stats z: 0 property var velocity: rocket.body.linearVelocity property double fuel: 100.0 property double heigth: Activity.startingHeightReal anchors.left: background.left anchors.leftMargin: 20 anchors.top: background.top anchors.topMargin: 20 width: 100 height: 100 color: "gray" fontSize: tinySize text: qsTr("Planet: ") + Activity.levels[Activity.currentLevel].planet + "
" + qsTr("Velocity: ") + Math.round(velocity.y * 10) / 10 + "
" + qsTr("Fuel: ") + fuel + "
" + qsTr("Altitude: ") + height + "
" + qsTr("Gravity: ") + Math.round(Activity.gravity * 100)/100 } Item { id: rocket property double accel: 0.0 property double leftAccel: 0.0 property double rightAccel: 0.0 property alias body: rocketBody property alias leftEngine: leftEngine property alias rightEngine: rightEngine property alias explosion: explosion //property float rotate rotation: 0 width: background.width / 18// * ApplicationInfo.ratio height: width / 232 * 385 x: 300 y: 50 z: 3 Component.onCompleted: rocket.body.applyForceToCenter(Qt.point(0, 5)); onAccelChanged: applyForces(); onLeftAccelChanged: applyForces(); onRightAccelChanged: applyForces(); onRotationChanged: if (accel > 0) // should only happen in applyForces(); // "rotation" mode // map acceleration to box2d forces applying appropriate factors: function applyForces() { var totForce = (accel / 0.5 * 5) var xForce; var yForce; if (items.mode === "simple") { yForce = -totForce; xForce = (leftAccel-rightAccel) * 10 /* base of 10 m/s^2 */ * 5 /* factor to make movement smooth */; } else { // "rotation" yForce = -(totForce * Math.cos(Activity.degToRad(items.rocket.rotation))); xForce = (totForce * Math.sin(Activity.degToRad(items.rocket.rotation))); } var yFForce = yForce * items.rocket.body.getMass(); var xFForce = xForce * items.rocket.body.getMass(); var force = Qt.point(xFForce, yFForce); console.log("applying force " + force + " - " + " - mass=" + items.rocket.body.getMass() + " v=" + items.rocket.body.linearVelocity + " totForce=" + totForce + " - rotation=" + items.rocket.rotation + " - xForce=" + xForce + " xFForce=" + xFForce + " mass=" + items.rocket.body.getMass()); physicsWorld.clearForces(); items.rocket.body.applyForceToCenter(force); } Image { id: rocketImage width: parent.width height: parent.height sourceSize.width: 1024 source: Activity.baseUrl + "/rocket.svg"; anchors.centerIn: parent anchors.fill: parent } Image { id: explosion // width: parent.height // height: width/785*621 width: height/621 * 785 height: 2*parent.height sourceSize.width: 1024 source: Activity.baseUrl + "/explosion.svg"; anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter scale: 0 function show() { visible = true; scale = 1; } function hide() { visible = false; scale = 0; } Behavior on scale { NumberAnimation { duration: 150 easing.type: Easing.InExpo } } } Body { id: rocketBody target: rocket bodyType: Body.Dynamic sleepingAllowed: false fixedRotation: true linearDamping: 0 property double rotation: Activity.degToRad(rocket.rotation % 360) onLinearVelocityChanged: console.log("v = " + linearVelocity); fixtures: Box { id: rocketFixture categories: items.rocketCategory collidesWith: items.groundCategory | items.landingCategory | items.borderCategory density: 1 friction: 0 restitution: 0 width: rocket.width height: rocket.height rotation: rocketBody.rotation onBeginContact: { console.log("XXX beginning contact with " + other.getBody().target.collisionName + " abs v=" + Math.abs(items.lastVelocity) + + " maxV=" + Activity.maxLandingVelocity); if (other.getBody().target === leftBorder || other.getBody().target === rightBorder) ; //nothing to do else if (other.getBody().target === landing && Math.abs(items.lastVelocity) <= Activity.maxLandingVelocity && (items.mode === "simple" || rocket.rotation === 0)) Activity.finishLevel(true); else // ground Activity.finishLevel(false); // crash } onEndContact: console.log("XXX ending contact with " + other.getBody().target.collisionName); } } ParticleSystem { id: leftEngine anchors.left: parent.left anchors.leftMargin: 10 anchors.verticalCenter: parent.verticalCenter height: 30 ImageParticle { groups: ["flameLeft"] source: "qrc:///particleresources/glowdot.png" color: "#11ff400f" colorVariation: 0.1 } Emitter { anchors.centerIn: parent group: "flameLeft" emitRate: rocket.leftAccel > 0 ? 50 : 0 // 50 lifeSpan: rocket.leftAccel > 0 ? 600 : 0 // 600 size: rocket.leftAccel > 0 ? leftEngine.height : 0 endSize: 5 sizeVariation: 5 acceleration: PointDirection { x: -40 } velocity: PointDirection { x: -40 } } } ParticleSystem { id: rightEngine anchors.right: parent.right anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter height: 30 ImageParticle { groups: ["flameRight"] source: "qrc:///particleresources/glowdot.png" color: "#11ff400f" colorVariation: 0.1 } Emitter { anchors.centerIn: parent group: "flameRight" emitRate: rocket.rightAccel > 0 ? 50 : 0 // 50 lifeSpan: rocket.rightAccel > 0 ? 600 : 0 // 600 size: rocket.rightAccel > 0 ? rightEngine.height : 0 endSize: 5 sizeVariation: 5 acceleration: PointDirection { x: 40 } velocity: PointDirection { x: 40 } } } ParticleSystem { id: bottomEngine anchors.top: parent.bottom anchors.topMargin: 5 anchors.horizontalCenter: parent.horizontalCenter width: rocket.width ImageParticle { groups: ["flame"] source: "qrc:///particleresources/glowdot.png" color: "#11ff400f" colorVariation: 0.1 } Emitter { anchors.centerIn: parent group: "flame" emitRate: rocket.accel > 0 ? (75 + 75*rocket.accel) : 0 // 75-150 lifeSpan: (500 + 500 * rocket.accel) * background.height / 600 // 500 - 1000 size: rocket.width/2 + rocket.width/2*rocket.accel // width*-0.5 - width endSize: size/1.85 sizeVariation: 10 acceleration: PointDirection { y: 80 } velocity: PointDirection { y: 80 } } } } Image { id: ground z: 1 width: parent.width // height: parent.height source: Activity.baseUrl + "/land4.png"; anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom readonly property string collisionName: "ground" property int surfaceOffset: height/2 Body { id: groundBody target: ground bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: groundFixture categories: items.groundCategory collidesWith: items.rocketCategory density: 1 friction: 0 restitution: 0 width: ground.width height: ground.height - ground.surfaceOffset x: 0 y: ground.surfaceOffset } } } Image { id: landing readonly property string collisionName: "landing" property int surfaceOffset: landing.height - 1 z: 2 source: Activity.baseUrl + "/landing_red.png"; anchors.left: ground.left anchors.leftMargin: 270 anchors.top: ground.top anchors.topMargin: ground.surfaceOffset - height width: 116 * background.width / 900 Body { id: landingBody target: landing bodyType: Body.Static sleepingAllowed: true fixedRotation: true linearDamping: 0 fixtures: Box { id: landingFixture categories: items.landingCategory collidesWith: items.rocketCategory density: 1 friction: 0 restitution: 0 width: landing.width height: landing.height - landing.surfaceOffset y: landing.surfaceOffset } } } DebugDraw { id: debugDraw world: physicsWorld visible: false z: 1 } DialogHelp { id: dialogHelp onClose: home() } Bar { id: bar content: BarEnumContent { value: help | home | level } onHelpClicked: { displayDialog(dialogHelp) } onPreviousLevelClicked: Activity.previousLevel() onNextLevelClicked: Activity.nextLevel() onHomeClicked: activity.home() } Bonus { id: bonus Component.onCompleted: { loose.connect(Activity.initLevel); win.connect(Activity.nextLevel) } } + + IntroMessage { + id: intro + onIntroDone: { + items.world.running = true; + } + intro: [ + Activity.introText1 + ] + z: 20 + anchors { + top: parent.top + topMargin: 10 + right: parent.right + rightMargin: 5 + left: parent.left + leftMargin: 5 + } + } + + BarButton { + id: ok + source: "qrc:/gcompris/src/core/resource/bar_ok.svg"; + sourceSize.width: 75 * ApplicationInfo.ratio + visible: false + anchors.centerIn: background + onClicked: { + visible = false; + items.world.running = true; + } + } } } diff --git a/src/activities/land_safe/land_safe.js b/src/activities/land_safe/land_safe.js index 4e9ca1d3d..595345a7e 100644 --- a/src/activities/land_safe/land_safe.js +++ b/src/activities/land_safe/land_safe.js @@ -1,250 +1,275 @@ /* GCompris - land_safe.js * * Copyright (C) 2016 Holger Kaelberer * * Authors: * Matilda Bernard (GTK+ version) * Holger Kaelberer (Qt Quick port) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* ToDo: * - zoom out if too high! * - support android * - check for shader availability * - use polygon fixture for rocket * - succesful landing only if on platform * - red/green colors for crash/save landing * - Einleitung + Ueberleitung simple -> rotate * * Gravitational forces: * !- Pluto: 0,62 m/s² * - Titan: 1,352 m/s² * !- Moon: 1,622 m/s² * - Io: 1,796 m/s² * !- Mars: 3,711 m/s² * - Merkur: 3,7 m/s² * !- Venus: 8,87 m/s² * - Earth: 9,807 m/s² * - Jupiter: 24,79 m/s² */ .pragma library .import QtQuick 2.0 as Quick .import GCompris 1.0 as GCompris var levels = [ /** simple **/ { "planet": "Pluto", "gravity": 0.62, "maxAccel": 0.2, "accelSteps": 3, "alt": 100.0, "mode": "simple", "fuel" : 5 }, { "planet": "Moon", "gravity": 1.62, "maxAccel": 0.4, "accelSteps": 4, "alt": 150.0, "mode": "simple", "fuel" : 10 }, { "planet": "Mars", "gravity": 3.71, "maxAccel": 0.6, "accelSteps": 5, "alt": 200.0, "mode": "simple", "fuel" : 20 }, { "planet": "Vulc@n", "gravity": 5.55, "maxAccel": 1.0, "accelSteps": 5, "alt": 300.0, "mode": "simple", "fuel" : 30 }, { "planet": "Venus", "gravity": 8.87, "maxAccel": 1.2, "accelSteps": 5, "alt": 300.0, "mode": "simple", "fuel" : 70 }, /** rotation **/ { "planet": "Pluto", "gravity": 0.62, "maxAccel": 0.2, "accelSteps": 3, "alt": 100.0, "mode": "rotation", "fuel" : 5 }, { "planet": "Moon", "gravity": 1.62, "maxAccel": 0.4, "accelSteps": 4, "alt": 150.0, "mode": "rotation", "fuel" : 10 }, { "planet": "Mars", "gravity": 3.71, "maxAccel": 0.6, "accelSteps": 5, "alt": 200.0, "mode": "rotation", "fuel" : 20 }, { "planet": "Vulc@n", "gravity": 5.55, "maxAccel": 1.0, "accelSteps": 5, "alt": 300.0, "mode": "rotation", "fuel" : 30 }, { "planet": "Venus", "gravity": 8.87, "maxAccel": 1.2, "accelSteps": 5, "alt": 300.0, "mode": "rotation", "fuel" : 70 } ]; +var introTextSimple = qsTr("Use the up and down keys to control the thrust." + + "
Use the right and left keys to control direction." + + "
You must drive Tux's ship towards the landing platform." + + "
The landing platform turns green when the velocity is safe to land.") + +var introTextRotate = qsTr("The up and down keys control the thrust of the rear engine." + + "
The right and left keys now control the rotation of the ship." + + "
To move the ship in horizontal direction you must first rotate and then accelerate it.") + var currentLevel = 0; var numberOfLevel; var items; var baseUrl = "qrc:/gcompris/src/activities/land_safe/resource"; var startingHeightReal = 100.0; var startingOffsetPx = 10; // y-value for setting rocket initially var gravity = 1; var maxLandingVelocity = 10; var leftRightAccel = 0.1; // accel force set on horizontal accel //var minAccel = 0.1; var maxAccel = 0.15; var accelSteps = 3; var dAccel = maxAccel / accelSteps;//- minAccel; var barAtStart; var maxFuel = 100.0; var currentFuel = 0.0; +var lastLevel = -1; function start(items_) { items = items_; currentLevel = 0; numberOfLevel = levels.length; barAtStart = GCompris.ApplicationSettings.isBarHidden; GCompris.ApplicationSettings.isBarHidden = true; initLevel() } function stop() { GCompris.ApplicationSettings.isBarHidden = barAtStart; } function initLevel() { items.bar.level = currentLevel + 1 var max = items.background.width - items.landing.width-20; var min = 20; items.rocket.explosion.hide(); items.rocket.x = Math.random() * (max- min) + min; items.rocket.y = startingOffsetPx; items.rocket.rotation = 0; items.rocket.accel = 0; items.rocket.leftAccel = 0; items.rocket.rightAccel = 0; items.rocket.body.linearVelocity = Qt.point(0,0) items.landing.anchors.leftMargin = Math.random() * (max- min) + min; maxAccel = levels[currentLevel].maxAccel; accelSteps = levels[currentLevel].accelSteps; dAccel = maxAccel / accelSteps;//- minAccel; startingHeightReal = levels[currentLevel].alt; gravity = levels[currentLevel].gravity; items.mode = levels[currentLevel].mode; maxFuel = currentFuel = levels[currentLevel].fuel; items.world.pixelsPerMeter = getHeightPx() / startingHeightReal; items.world.gravity = Qt.point(0, gravity) + items.world.running = false; console.log("Starting level (surfaceOff=" + items.ground.surfaceOffset + ", ppm=" + items.world.pixelsPerMeter + ")"); - items.world.running = true; + + if (currentLevel === 0 && lastLevel !== 0) { + items.ok.visible = false; + items.intro.intro = [introTextSimple]; + items.intro.index = 0; + } else if (currentLevel === 5 && lastLevel !== 0) { + items.ok.visible = false; + items.intro.intro = [introTextRotate]; + items.intro.index = 0; + } else { + // go + items.intro.index = -1; + items.ok.visible = true; + } + lastLevel = currentLevel; } function getHeightPx() { var heightPx = items.background.height - items.ground.height + items.ground.surfaceOffset - items.rocket.y - items.rocket.height - 1; // landing is 1 pixel above ground surface return heightPx; } // calc real height of rocket in meters above surface function getRealHeight() { var heightPx = getHeightPx(); var heightReal = heightPx / items.world.pixelsPerMeter; return heightReal; } function nextLevel() { if(numberOfLevel <= ++currentLevel ) { currentLevel = 0 } initLevel(); } function previousLevel() { if(--currentLevel < 0) { currentLevel = numberOfLevel - 1 } initLevel(); } function processKeyPress(event) { var key = event.key; event.accepted = true; var newAccel = 0; if (key === Qt.Key_Up || key === Qt.Key_Down) { if (key === Qt.Key_Up) { if (items.rocket.accel === 0) newAccel = dAccel; else newAccel = items.rocket.accel + dAccel; } else if (key === Qt.Key_Down) newAccel = items.rocket.accel - dAccel; if (newAccel < dAccel) newAccel = 0; if (newAccel > maxAccel) newAccel = maxAccel; if (newAccel !== items.rocket.accel && currentFuel > 0) items.rocket.accel = newAccel; } else if (key === Qt.Key_Right || key === Qt.Key_Left) { if (items.mode === "simple") { if (key === Qt.Key_Right && !event.isAutoRepeat && currentFuel > 0) { items.rocket.leftAccel = leftRightAccel; items.rocket.rightAccel = 0.0; } else if (key === Qt.Key_Left && !event.isAutoRepeat && currentFuel > 0) { items.rocket.rightAccel = leftRightAccel; items.rocket.leftAccel = 0.0; } } else { // "rotation" if (key === Qt.Key_Right) items.rocket.rotation += 10; else if (key === Qt.Key_Left) items.rocket.rotation -= 10; //console.log("XXX rotation=" + items.rocket.rotation + " bodyRot=" + items.rocket.body.rotation); } } else event.accepted = false; } function processKeyRelease(event) { var key = event.key; event.accepted = true; //console.log("XXX release " + key + " = " + event.isAutoRepeat + " = " + Qt.Key_Right); if (key===Qt.Key_1) { items.rocket.explosion.show(); } if (key===Qt.Key_0) { items.rocket.explosion.hide(); } if (key === Qt.Key_Right && !event.isAutoRepeat) { items.rocket.leftAccel = 0; } else if (key === Qt.Key_Left && !event.isAutoRepeat) { items.rocket.rightAccel = 0; } else event.accepted = false; } function finishLevel(success) { items.rocket.accel = 0; items.rocket.leftAccel = 0; items.rocket.rightAccel = 0; items.rocket.body.linearVelocity = Qt.point(0,0) if (success) items.bonus.good("lion"); else { items.rocket.explosion.show(); items.bonus.bad("lion"); } } function degToRad(degrees) { return degrees * Math.PI / 180; }