Platformer with Level Editor
import Felgo 3.0
import QtQuick 2.0
import "../editorElements"
PlatformerEntityBaseDraggable {
id: player
entityType: "player"
property int startX
property int startY
property alias horizontalVelocity: collider.linearVelocity.x
property alias verticalVelocity: collider.linearVelocity.y
property int score: 0
property bool doubleJumpEnabled: true
property bool isBig: false
property bool invincible: false
property int starInvincibilityTime: 4000
property int contacts: 0
property int normalJumpForce: 210
property int killJumpForce: 420
property int jumpForceLeft: 20
property int accelerationForce: 1000
property real decelerationFactor: 0.6
property int maxSpeed: 230
property int maxFallingSpeed: 800
signal finish
width: image.width
height: image.height
colliderComponent: collider
image.source: "../../assets/player/player.png"
scale: isBig ? 1 : 0.64
Behavior on scale { NumberAnimation { duration: 500 } }
transformOrigin: Item.Bottom
state: contacts > 0 ? "walking" : "jumping"
preventFromRemovalFromEntityManager: true
onVerticalVelocityChanged: {
if(verticalVelocity > maxFallingSpeed)
verticalVelocity = maxFallingSpeed
}
onFinish: audioManager.playSound("finish")
PolygonCollider {
id: collider
vertices: isBig ?
[
Qt.point(15, 24),
Qt.point(25, 14),
Qt.point(40, 14),
Qt.point(47, 24),
Qt.point(49, 62.8),
Qt.point(44, 63),
Qt.point(19, 63),
Qt.point(14, 62.8)
] :
[
Qt.point(21, 39),
Qt.point(28, 34),
Qt.point(39, 34),
Qt.point(43, 39),
Qt.point(43, 62.85),
Qt.point(40, 63),
Qt.point(23, 63),
Qt.point(20, 62.85)
]
bodyType: Body.Dynamic
active: !inLevelEditingMode
categories: Box.Category1
collidesWith: Box.Category3 | Box.Category5 | Box.Category6 | Box.Category7
friction: 0
sleepingAllowed: false
force: Qt.point(controller.xAxis * accelerationForce, 0)
onLinearVelocityChanged: {
if(linearVelocity.x > maxSpeed) linearVelocity.x = maxSpeed
if(linearVelocity.x < -maxSpeed) linearVelocity.x = -maxSpeed
}
fixture.onBeginContact: {
var otherEntity = other.getBody().target
if(otherEntity.entityType === "coin") {
otherEntity.collect()
score += 1
}
else if(otherEntity.entityType === "mushroom") {
otherEntity.collect()
isBig = true
}
else if(otherEntity.entityType === "star") {
otherEntity.collect()
startInvincibility(starInvincibilityTime)
}
}
fixture.onContactChanged: {
var otherEntity = other.getBody().target
if(otherEntity.entityType === "opponent" || otherEntity.entityType === "spikes") {
die(false)
}
}
}
BoxCollider {
id: feetSensor
width: 32 * parent.scale
height: 10 * parent.scale
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.bottom
bodyType: Body.Dynamic
active: gameScene.state === "edit" ? false : true
categories: Box.Category2
collidesWith: Box.Category3 | Box.Category5
collisionTestingOnlyMode: true
fixture.onBeginContact: {
var otherEntity = other.getBody().target
if(otherEntity.entityType === "opponent") {
var playerLowestY = player.y + player.height
var oppLowestY = otherEntity.y + otherEntity.height
if(playerLowestY < oppLowestY - 5) {
console.debug("kill opponent")
otherEntity.die()
startJump(false)
}
}
else if(otherEntity.entityType === "platform" || otherEntity.entityType === "ground") {
contacts++
if(verticalVelocity >= 0)
doubleJumpEnabled = true
}
}
fixture.onEndContact: {
var otherEntity = other.getBody().target
if(otherEntity.entityType === "platform" || otherEntity.entityType === "ground")
contacts--
}
}
BoxCollider {
id: editorCollider
anchors.fill: parent
collisionTestingOnlyMode: true
categories: Box.Category16
}
Timer {
id: updateTimer
interval: 60
running: true
repeat: true
onTriggered: {
var xAxis = controller.xAxis;
if(xAxis == 0) {
if(Math.abs(player.horizontalVelocity) > 10)
player.horizontalVelocity *= decelerationFactor
else
player.horizontalVelocity = 0
}
}
}
MultiResolutionImage {
id: invincibilityOverlayImage
source: "../../assets/player/player_rainbow.png"
opacity: 0
NumberAnimation on opacity {
id: invincibilityOverlayImageFadeOut
from: 1
to: 0
duration: 500
}
}
Timer {
id: invincibilityWarningTimer
onTriggered: warnInvincibility()
}
Timer {
id: invincibilityTimer
onTriggered: endInvincibility()
}
Timer {
id: ascentControl
interval: 15
repeat: true
onTriggered: {
if(jumpForceLeft > 0) {
var verticalImpulse = 0
if(jumpForceLeft == 20) {
verticalVelocity = 0
verticalImpulse = -normalJumpForce
}
else if(jumpForceLeft >= 14) {
verticalImpulse = -normalJumpForce / 5
}
else {
verticalImpulse = -normalJumpForce / 15
}
collider.applyLinearImpulse(Qt.point(0, verticalImpulse))
jumpForceLeft--
}
}
}
EditableComponent {
editableType: "Balance"
properties: {
"Player" : {
"normalJumpForce": {"min": 0, "max": 400, "stepSize": 5, "label": "Jump Force"},
"killJumpForce": {"min": 0, "max": 1000, "stepSize": 10, "label": "Kill Jump Force"},
"accelerationForce": {"min": 0, "max": 5000, "stepSize": 10, "label": "Acceleration"},
"maxSpeed": {"min": 0, "max": 400, "stepSize": 5, "label": "Speed"},
"maxFallingSpeed": {"min": 5, "max": 1000, "stepSize": 5, "label": "Max Falling Speed"}
},
"Power-Ups" : {
"starInvincibilityTime": {"min": 500, "max": 10000, "stepSize": 100, "label": "Star Duration (ms)"}
}
}
}
function startJump(isNormalJump) {
if(isNormalJump) {
if(player.state == "walking") {
ascentControl.start()
audioManager.playSound("playerJump")
}
else if(doubleJumpEnabled) {
ascentControl.start()
doubleJumpEnabled = false
audioManager.playSound("playerJump")
}
}
else {
verticalVelocity = -killJumpForce
}
}
function endJump() {
ascentControl.stop()
jumpForceLeft = 20
}
function die(dieImmediately) {
if(dieImmediately || (!isBig && !invincible))
{
audioManager.playSound("playerDie")
gameScene.resetLevel()
}
else if(invincible) {
}
else {
isBig = false
startInvincibility(0)
audioManager.playSound("playerHit")
}
}
function startInvincibility(interval) {
var warningTime = 500
if(interval < warningTime)
interval = warningTime
invincibilityOverlayImage.opacity = 1
invincibilityWarningTimer.interval = interval - warningTime
invincibilityWarningTimer.start()
invincibilityTimer.interval = interval
invincibilityTimer.start()
invincible = true
audioManager.playSound("playerInvincible")
console.debug("start invincibility; interval: "+interval)
}
function warnInvincibility() {
invincibilityOverlayImageFadeOut.start()
console.debug("warn invincibility")
}
function endInvincibility() {
invincible = false
audioManager.stopSound("playerInvincible")
console.debug("stop invincibility")
}
function changeSpriteOrientation() {
if(controller.xAxis == -1) {
image.mirrorX = true
invincibilityOverlayImage.mirrorX = true
}
else if (controller.xAxis == 1) {
image.mirrorX = false
invincibilityOverlayImage.mirrorX = false
}
}
onEntityReleased: updateStartPosition()
function updateStartPosition() {
startX = x
startY = y
}
function loadStartPosition() {
if(gameWindow.levelEditor && gameWindow.levelEditor.currentLevelData
&& gameWindow.levelEditor.currentLevelData["customData"]
&& gameWindow.levelEditor.currentLevelData["customData"]["playerX"]) {
startX = parseInt(gameWindow.levelEditor.currentLevelData["customData"]["playerX"])
}
else {
startX = 32
}
if(gameWindow.levelEditor && gameWindow.levelEditor.currentLevelData
&& gameWindow.levelEditor.currentLevelData["customData"]
&& gameWindow.levelEditor.currentLevelData["customData"]["playerY"]) {
startY = parseInt(gameWindow.levelEditor.currentLevelData["customData"]["playerY"])
}
else {
startY = -96
}
}
function initialize() {
loadStartPosition()
reset()
lastPosition = Qt.point(x, y)
}
function reset() {
x = startX
y = startY
collider.linearVelocity.x = 0
collider.linearVelocity.y = 0
score = 0
doubleJumpEnabled = true
isBig = false
invincible = false
invincibilityTimer.stop()
invincibilityWarningTimer.stop()
invincibilityOverlayImage.opacity = 0
audioManager.stopSound("playerInvincible")
}
function resetContacts() {