diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..24f8d9b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + //run love in current dir + { + "name": "love", + "type": "f5anything", + "request": "launch", + "command": "love --console .", + "stopAtEntry": false, + "cwd": "${workspaceRoot}", + "env": { + "PATH": "${env:PATH}" + } + } + ] +} \ No newline at end of file diff --git a/assets/images/backgrounds/bg0.png b/assets/images/backgrounds/bg0.png new file mode 100644 index 0000000..7801a39 Binary files /dev/null and b/assets/images/backgrounds/bg0.png differ diff --git a/assets/images/spritesheets/player.gif b/assets/images/spritesheets/player.gif new file mode 100644 index 0000000..eee4abb Binary files /dev/null and b/assets/images/spritesheets/player.gif differ diff --git a/assets/images/spritesheets/player.json b/assets/images/spritesheets/player.json new file mode 100644 index 0000000..aed33eb --- /dev/null +++ b/assets/images/spritesheets/player.json @@ -0,0 +1,519 @@ +{ "frames": [ + { + "filename": "Sprite-0002 0.aseprite", + "frame": { "x": 140, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 1.aseprite", + "frame": { "x": 0, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 2.aseprite", + "frame": { "x": 140, "y": 320, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 3.aseprite", + "frame": { "x": 0, "y": 320, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 4.aseprite", + "frame": { "x": 840, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 5.aseprite", + "frame": { "x": 700, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 6.aseprite", + "frame": { "x": 560, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 7.aseprite", + "frame": { "x": 420, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 8.aseprite", + "frame": { "x": 280, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 9.aseprite", + "frame": { "x": 140, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 10.aseprite", + "frame": { "x": 280, "y": 320, "w": 140, "h": 150 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 150 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 11.aseprite", + "frame": { "x": 420, "y": 320, "w": 140, "h": 150 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 150 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 12.aseprite", + "frame": { "x": 0, "y": 620, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 13.aseprite", + "frame": { "x": 840, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 14.aseprite", + "frame": { "x": 700, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 15.aseprite", + "frame": { "x": 560, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 16.aseprite", + "frame": { "x": 420, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 17.aseprite", + "frame": { "x": 420, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 18.aseprite", + "frame": { "x": 280, "y": 610, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 19.aseprite", + "frame": { "x": 140, "y": 480, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 20.aseprite", + "frame": { "x": 0, "y": 480, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 21.aseprite", + "frame": { "x": 840, "y": 470, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 22.aseprite", + "frame": { "x": 700, "y": 470, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 23.aseprite", + "frame": { "x": 560, "y": 470, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 24.aseprite", + "frame": { "x": 140, "y": 620, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 25.aseprite", + "frame": { "x": 420, "y": 470, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 26.aseprite", + "frame": { "x": 840, "y": 320, "w": 140, "h": 150 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 150 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 27.aseprite", + "frame": { "x": 700, "y": 320, "w": 140, "h": 150 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 150 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 28.aseprite", + "frame": { "x": 280, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 500 + }, + { + "filename": "Sprite-0002 29.aseprite", + "frame": { "x": 280, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 500 + }, + { + "filename": "Sprite-0002 30.aseprite", + "frame": { "x": 420, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 31.aseprite", + "frame": { "x": 560, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 32.aseprite", + "frame": { "x": 700, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 33.aseprite", + "frame": { "x": 420, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 34.aseprite", + "frame": { "x": 420, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 35.aseprite", + "frame": { "x": 840, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 36.aseprite", + "frame": { "x": 0, "y": 160, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 37.aseprite", + "frame": { "x": 420, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 200 + }, + { + "filename": "Sprite-0002 38.aseprite", + "frame": { "x": 280, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 65535 + }, + { + "filename": "Sprite-0002 39.aseprite", + "frame": { "x": 280, "y": 0, "w": 140, "h": 160 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 0, "w": 140, "h": 160 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 40.aseprite", + "frame": { "x": 560, "y": 320, "w": 140, "h": 150 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 10, "w": 140, "h": 150 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 41.aseprite", + "frame": { "x": 280, "y": 470, "w": 140, "h": 140 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 20, "w": 140, "h": 140 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 42.aseprite", + "frame": { "x": 280, "y": 750, "w": 140, "h": 130 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 30, "w": 140, "h": 130 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 43.aseprite", + "frame": { "x": 420, "y": 750, "w": 140, "h": 120 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 40, "w": 140, "h": 120 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 44.aseprite", + "frame": { "x": 560, "y": 750, "w": 140, "h": 110 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 50, "w": 140, "h": 110 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 45.aseprite", + "frame": { "x": 700, "y": 750, "w": 140, "h": 100 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 60, "w": 140, "h": 100 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 46.aseprite", + "frame": { "x": 840, "y": 750, "w": 140, "h": 90 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 70, "w": 140, "h": 90 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 47.aseprite", + "frame": { "x": 0, "y": 760, "w": 140, "h": 80 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 80, "w": 140, "h": 80 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 48.aseprite", + "frame": { "x": 140, "y": 760, "w": 140, "h": 70 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 10, "y": 90, "w": 140, "h": 70 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 49.aseprite", + "frame": { "x": 140, "y": 830, "w": 120, "h": 60 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 20, "y": 100, "w": 120, "h": 60 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 50.aseprite", + "frame": { "x": 0, "y": 840, "w": 100, "h": 50 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 110, "w": 100, "h": 50 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 51.aseprite", + "frame": { "x": 840, "y": 840, "w": 100, "h": 40 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 30, "y": 120, "w": 100, "h": 40 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 52.aseprite", + "frame": { "x": 700, "y": 850, "w": 60, "h": 30 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 50, "y": 130, "w": 60, "h": 30 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 53.aseprite", + "frame": { "x": 760, "y": 850, "w": 60, "h": 20 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 50, "y": 140, "w": 60, "h": 20 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + }, + { + "filename": "Sprite-0002 54.aseprite", + "frame": { "x": 560, "y": 860, "w": 60, "h": 10 }, + "rotated": false, + "trimmed": true, + "spriteSourceSize": { "x": 50, "y": 150, "w": 60, "h": 10 }, + "sourceSize": { "w": 160, "h": 160 }, + "duration": 1 + } + ], + "meta": { + "app": "http://www.aseprite.org/", + "version": "1.x-dev", + "image": "player.png", + "format": "RGBA8888", + "size": { "w": 980, "h": 890 }, + "scale": "1", + "frameTags": [ + { "name": "Jump", "from": 0, "to": 27, "direction": "forward" }, + { "name": "Idle", "from": 28, "to": 29, "direction": "forward" }, + { "name": "Walk_left", "from": 30, "to": 33, "direction": "forward" }, + { "name": "Walk_right", "from": 34, "to": 37, "direction": "forward" }, + { "name": "Default", "from": 38, "to": 38, "direction": "forward" }, + { "name": "Die", "from": 39, "to": 54, "direction": "forward" } + ], + "layers": [ + { "name": "Layer 1", "opacity": 255, "blendMode": "normal" } + ], + "slices": [ + ] + } +} diff --git a/assets/images/spritesheets/player.png b/assets/images/spritesheets/player.png new file mode 100644 index 0000000..01d796b Binary files /dev/null and b/assets/images/spritesheets/player.png differ diff --git a/assets/sounds/effects/coin.ogg b/assets/sounds/effects/coin.ogg new file mode 100644 index 0000000..933a67c Binary files /dev/null and b/assets/sounds/effects/coin.ogg differ diff --git a/assets/sounds/effects/die.ogg b/assets/sounds/effects/die.ogg new file mode 100644 index 0000000..284b792 Binary files /dev/null and b/assets/sounds/effects/die.ogg differ diff --git a/assets/sounds/effects/explosion.ogg b/assets/sounds/effects/explosion.ogg new file mode 100644 index 0000000..0234dc1 Binary files /dev/null and b/assets/sounds/effects/explosion.ogg differ diff --git a/assets/sounds/effects/jump.ogg b/assets/sounds/effects/jump.ogg new file mode 100644 index 0000000..7c55539 Binary files /dev/null and b/assets/sounds/effects/jump.ogg differ diff --git a/assets/sounds/effects/powerup.ogg b/assets/sounds/effects/powerup.ogg new file mode 100644 index 0000000..aa79bcd Binary files /dev/null and b/assets/sounds/effects/powerup.ogg differ diff --git a/assets/sounds/effects/revive.ogg b/assets/sounds/effects/revive.ogg new file mode 100644 index 0000000..7caf57f Binary files /dev/null and b/assets/sounds/effects/revive.ogg differ diff --git a/assets/sounds/effects/select.ogg b/assets/sounds/effects/select.ogg new file mode 100644 index 0000000..858a828 Binary files /dev/null and b/assets/sounds/effects/select.ogg differ diff --git a/assets/sounds/effects/shoot.ogg b/assets/sounds/effects/shoot.ogg new file mode 100644 index 0000000..c4a6b52 Binary files /dev/null and b/assets/sounds/effects/shoot.ogg differ diff --git a/assets/sounds/music/track0.ogg b/assets/sounds/music/track0.ogg new file mode 100644 index 0000000..58edade Binary files /dev/null and b/assets/sounds/music/track0.ogg differ diff --git a/assets/sounds/music/track1.ogg b/assets/sounds/music/track1.ogg new file mode 100644 index 0000000..65ed062 Binary files /dev/null and b/assets/sounds/music/track1.ogg differ diff --git a/assets/sounds/music/track10.ogg b/assets/sounds/music/track10.ogg new file mode 100644 index 0000000..a0a90ac Binary files /dev/null and b/assets/sounds/music/track10.ogg differ diff --git a/assets/sounds/music/track11.ogg b/assets/sounds/music/track11.ogg new file mode 100644 index 0000000..4c12035 Binary files /dev/null and b/assets/sounds/music/track11.ogg differ diff --git a/assets/sounds/music/track2.ogg b/assets/sounds/music/track2.ogg new file mode 100644 index 0000000..a877c8f Binary files /dev/null and b/assets/sounds/music/track2.ogg differ diff --git a/assets/sounds/music/track3.ogg b/assets/sounds/music/track3.ogg new file mode 100644 index 0000000..ab4052e Binary files /dev/null and b/assets/sounds/music/track3.ogg differ diff --git a/assets/sounds/music/track4.ogg b/assets/sounds/music/track4.ogg new file mode 100644 index 0000000..f09e899 Binary files /dev/null and b/assets/sounds/music/track4.ogg differ diff --git a/assets/sounds/music/track5.ogg b/assets/sounds/music/track5.ogg new file mode 100644 index 0000000..276cb70 Binary files /dev/null and b/assets/sounds/music/track5.ogg differ diff --git a/assets/sounds/music/track6.ogg b/assets/sounds/music/track6.ogg new file mode 100644 index 0000000..6946d61 Binary files /dev/null and b/assets/sounds/music/track6.ogg differ diff --git a/assets/sounds/music/track7.ogg b/assets/sounds/music/track7.ogg new file mode 100644 index 0000000..a76dd73 Binary files /dev/null and b/assets/sounds/music/track7.ogg differ diff --git a/assets/sounds/music/track8.ogg b/assets/sounds/music/track8.ogg new file mode 100644 index 0000000..8101dc3 Binary files /dev/null and b/assets/sounds/music/track8.ogg differ diff --git a/assets/sounds/music/track9.ogg b/assets/sounds/music/track9.ogg new file mode 100644 index 0000000..d3a3aba Binary files /dev/null and b/assets/sounds/music/track9.ogg differ diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..2cedf70 --- /dev/null +++ b/conf.lua @@ -0,0 +1,51 @@ +function love.conf(t) + t.identity = nil -- The name of the save directory (string) + t.appendidentity = false -- Search files in source directory before save directory (boolean) + t.version = "11.4" -- The LÖVE version this game was made for (string) + t.console = true -- Attach a console (boolean, Windows only) + t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) + t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) + t.gammacorrect = true -- Enable gamma-correct rendering, when supported by the system (boolean) + + t.audio.mic = false -- Request and use microphone capabilities in Android (boolean) + t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only) + + t.window.title = "Weeeeeeeee" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 1024 -- The window width (number) + t.window.height = 768 -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = false -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) + t.window.vsync = 1 -- Vertical sync mode (number) + t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.depth = nil -- The number of bits per sample in the depth buffer + t.window.stencil = nil -- The number of bits per sample in the stencil buffer + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.usedpiscale = true -- Enable automatic DPI scaling when highdpi is set to true as well (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = true -- Enable the audio module (boolean) + t.modules.data = true -- Enable the data module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.font = true -- Enable the font module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = true -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = true -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = true -- Enable the physics module (boolean) + t.modules.sound = true -- Enable the sound module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) + t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update + t.modules.touch = true -- Enable the touch module (boolean) + t.modules.video = true -- Enable the video module (boolean) + t.modules.window = true -- Enable the window module (boolean) +end diff --git a/libs/cron.lua b/libs/cron.lua new file mode 100644 index 0000000..6f3a8a5 --- /dev/null +++ b/libs/cron.lua @@ -0,0 +1,106 @@ +local cron = { + __VERSION = 'cron.lua 2.0.0', + __DESCRIPTION = 'Time-related functions for lua', + __URL = 'https://github.com/kikito/cron.lua', + __LICENSE = [[ + Copyright (c) 2011 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +-- Private functions + +local function isCallable(callback) + local tc = type(callback) + if tc == 'function' then return true end + if tc == 'table' then + local mt = getmetatable(callback) + return type(mt) == 'table' and type(mt.__call) == 'function' + end + return false +end + +local function checkPositiveInteger(name, value) + if type(value) ~= "number" or value < 0 then + error(name .. " must be a positive number") + end +end + +local Clock = {} +local Clock_mt = {__index = Clock} + +local function newClock(time, callback, update, ...) + checkPositiveInteger('time', time) + assert(isCallable(callback), "callback must be a function") + + return setmetatable({ + time = time, + callback = callback, + args = {...}, + running = 0, + update = update + }, Clock_mt) +end + +local function updateAfterClock(self, dt) -- returns true if expired + checkPositiveInteger('dt', dt) + + if self.running >= self.time then return true end + + self.running = self.running + dt + + if self.running >= self.time then + self.callback(unpack(self.args)) + return true + end + return false +end + +local function updateEveryClock(self, dt) + checkPositiveInteger('dt', dt) + + self.running = self.running + dt + + while self.running >= self.time do + self.callback(unpack(self.args)) + self.running = self.running - self.time + end + return false +end + +function Clock:reset(running) + running = running or 0 + checkPositiveInteger('running', running) + + self.running = running +end + + +function cron.after(time, callback, ...) + return newClock(time, callback, updateAfterClock, ...) +end + +function cron.every(time, callback, ...) + return newClock(time, callback, updateEveryClock, ...) +end + +return cron + diff --git a/libs/json.lua b/libs/json.lua new file mode 100644 index 0000000..a3d7530 --- /dev/null +++ b/libs/json.lua @@ -0,0 +1,400 @@ +-- +-- json.lua +-- +-- Copyright (c) 2018 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.1" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/libs/peachy.lua b/libs/peachy.lua new file mode 100644 index 0000000..7558981 --- /dev/null +++ b/libs/peachy.lua @@ -0,0 +1,351 @@ +--- A parser/renderer for Aseprite animations in LÖVE. +-- @classmod peachy + +local peachy = { + _VERSION = "1.0.0-alpha", + _DESCRIPTION = "A parser/renderer for Aseprite animations in LÖVE.", + _URL = "https://github.com/josh-perry/peachy", + _LICENSE = [[ + MIT License + + Copyright (c) 2018 Josh Perry + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ]] +} + +local PATH = select('1', ...):match(".+%.") or "" +local json = require(PATH.."libs/json") +local cron = require(PATH.."libs/cron") + +peachy.__index = peachy + +--- Creates a new Peachy animation object. + -- + -- If imageData isn't specified then Peachy will attempt to load it using the + -- filename from the JSON data. + -- + -- If no initial tag is set then the object will be paused (i.e. not displayed) with no tag. + -- The animation will start playing immediately once created. + -- + -- @usage + -- -- Load the image ourselves and set animation tag to "Spin". + -- -- Will start playing immediately. + -- spinner = peachy.new("spinner.json", love.graphics.newImage("spinner.png"), "Spin") + -- + -- @tparam string dataFile a path to an Aseprite JSON file. It is also possible to pass a predecoded table, + -- which is useful for performance when creating large amounts of the same animation. + -- @tparam Image imageData a LÖVE image to animate. + -- @tparam string initialTag the name of the animation tag to use initially. + -- @return the new Peachy object. + function peachy.new(dataFile, imageData, initialTag) + assert(dataFile ~= nil, "No JSON data!") + + local self = setmetatable({}, peachy) + + --store the path to the passed json file + self.json_path = dataFile + + -- check if datafile is a lua table (i.e. pre decoded) + if type(dataFile) == 'table' then + self._jsonData = dataFile + else + -- Read the data + self._jsonData = json.decode(love.filesystem.read(dataFile)) + end + + -- Load the image + self.image = imageData or love.graphics.newImage(self._jsonData.meta.image) + + self:_checkImageSize() + + self:_initializeFrames() + self:_initializeTags() + + self.paused = true + + self.tag = nil + self.tagName = nil + self.direction = nil + + if initialTag then + self:setTag(initialTag) + self.paused = false + end + + return self +end + +--- Switch to a different animation tag. +-- In the case that we're attempting to switch to the animation currently playing, +-- nothing will happen. +-- +-- @tparam string tag the animation tag name to switch to. +function peachy:setTag(tag) + assert(tag, "No animation tag specified!") + assert(self.frameTags[tag], "Tag "..tag.." not found in frametags!") + + if self.tag == self.frameTags[tag] then + return + end + + self.tagName = tag + self.tag = self.frameTags[self.tagName] + self.frameIndex = nil + self.direction = self.tag.direction + + if self.direction == "pingpong" then + self.direction = "forward" + end + + self:nextFrame() +end + +--- Jump to a particular frame index (1-based indexes) in the current animation. +-- +-- Errors if the frame is outside the tag's frame range. +-- +-- @usage +-- -- Go to the 4th frame +-- sound:setFrame(4) +-- +-- @tparam number frame the frame index to jump to. +function peachy:setFrame(frame) + if frame < 1 or frame > #self.tag.frames then + error("Frame "..frame.." is out of range of tag '"..self.tagName.."' (1.."..#self.tag.frames..")") + end + + self.frameIndex = frame + + self.frame = self.tag.frames[self.frameIndex] + self.frameTimer = cron.after(self.frame.duration, self.nextFrame, self) +end + +--- Get the current frame of the current animation +-- @usage +-- Get the 2nd frame +-- local f = sound:getFrame() +-- +function peachy:getFrame() + return self.frameIndex +end + +--- Get the json path passed in the object +-- @usage +-- Get the (string) JSON path +-- local str_json = obj:getJSON() +-- +function peachy:getJSON() + return self.json_path +end + +--- Draw the animation's current frame in a specified location. +-- @tparam number x the x position. +-- @tparam number y the y position. +-- @tparam number rot the rotation to draw at. +-- @tparam number sx the x scaling. +-- @tparam number sy the y scaling. +-- @tparam number ox the origin offset x. +-- @tparam number oy the origin offset y. +function peachy:draw(x, y, rot, sx, sy, ox, oy) + if not self.frame then + return + end + + love.graphics.draw(self.image, self.frame.quad, x, y, rot or 0, sx or 1, sy or 1, ox or 0, oy or 0) +end + +--- Update the animation. +-- @tparam number dt frame delta. Should be called from love.update and given the dt. +function peachy:update(dt) + assert(dt, "No dt passed into update!") + + if self.paused then + return + end + + -- If we're trying to play an animation and it's nil or hasn't been set up + -- properly then error + assert(self.tag, "No animation tag has been set!") + assert(self.frameTimer, "Frame timer hasn't been initialized!") + + -- Update timer in milliseconds since that's how Aseprite stores durations + self.frameTimer:update(dt * 1000) +end + +--- Move to the next frame. +-- Internal: unless you want to skip frames, this generally will not ever +-- need to be called manually. +function peachy:nextFrame() + local forward = self.direction == "forward" + + if forward then + self.frameIndex = (self.frameIndex or 0) + 1 + else + self.frameIndex = (self.frameIndex or #self.tag.frames + 1) - 1 + end + + -- Looping + if forward and self.frameIndex > #self.tag.frames then + if self.tag.direction == "pingpong" then + self:_pingpongBounce() + else + self.frameIndex = 1 + end + self:call_onLoop() + elseif not forward and self.frameIndex < 1 then + if self.tag.direction == "pingpong" then + self:_pingpongBounce() + else + self.frameIndex = #self.tag.frames + self:call_onLoop() + end + end + + -- Get next frame + self.frame = self.tag.frames[self.frameIndex] + + self.frameTimer = cron.after(self.frame.duration, self.nextFrame, self) +end + +--- Check for callbacks +function peachy:call_onLoop() + if self.callback_onLoop then self.callback_onLoop(unpack(self.args_onLoop)) end +end + +--- Pauses the animation. +function peachy:pause() + self.paused = true +end + +--- Unpauses the animation. +function peachy:play() + self.paused = false +end + +--- Stops the animation (pause it then return to first frame or last if specified) +function peachy:stop(onLast) + local index = 1 + self.paused = true + if onLast then index = #self.tag.frames end + self:setFrame(index) +end + +--- Adds a callback function that will be called when the animation loops +function peachy:onLoop(fn, ...) + self.callback_onLoop = fn + self.args_onLoop = {...} +end + +--- Toggle between playing/paused. +function peachy:togglePlay() + if self.paused then + self:play() + else + self:pause() + end +end + +--- Provides width stored in the metadata of a current frame +function peachy:getWidth() + return self._jsonData.frames[self.frameIndex].frame.w +end + +--- Provides height stored in the metadata of a current frame +function peachy:getHeight() + return self._jsonData.frames[self.frameIndex].frame.h +end + +--- Provides dimensions stored in the metadata of a current frame +function peachy:getDimensions() + return self:getWidth(), self:getHeight() +end + +--- Internal: handles the ping-pong animation type. +-- +-- Should only be called when we actually want to bounce. +-- Swaps the direction. +function peachy:_pingpongBounce() + -- We need to increment/decrement frame index by 2 because + -- at this point we've already gone to the next frame + if self.direction == "forward" then + self.direction = "reverse" + self.frameIndex = self.frameIndex - 2 + else + self.direction = "forward" + self.frameIndex = self.frameIndex + 2 + end +end + +--- Internal: loads all of the frames +-- +-- Loads quads and frame duration data from the JSON. +-- +-- Called from peachy.new +function peachy:_initializeFrames() + assert(self._jsonData ~= nil, "No JSON data!") + assert(self._jsonData.meta ~= nil, "No metadata in JSON!") + assert(self._jsonData.frames ~= nil, "No frame data in JSON!") + + -- Initialize all the quads + self.frames = {} + for _, frameData in ipairs(self._jsonData.frames) do + local frame = {} + + local fd = frameData.frame + frame.quad = love.graphics.newQuad(fd.x, fd.y, fd.w, fd.h, self._jsonData.meta.size.w, self._jsonData.meta.size.h) + frame.duration = frameData.duration + + table.insert(self.frames, frame) + end +end + +--- Internal: loads all of the animation tags +-- +-- Called from peachy.new +function peachy:_initializeTags() + assert(self._jsonData ~= nil, "No JSON data!") + assert(self._jsonData.meta ~= nil, "No metadata in JSON!") + assert(self._jsonData.meta.frameTags ~= nil, "No frame tags in JSON! Make sure you exported them in Aseprite!") + + self.frameTags = {} + + for _, frameTag in ipairs(self._jsonData.meta.frameTags) do + local ft = {} + ft.direction = frameTag.direction + ft.frames = {} + + for frame = frameTag.from + 1, frameTag.to + 1 do + table.insert(ft.frames, self.frames[frame]) + end + + self.frameTags[frameTag.name] = ft + end +end + +--- Internal: checks that the loaded image size matches the metadata +-- +-- Called from peachy.new +function peachy:_checkImageSize() + local imageWidth, imageHeight = self._jsonData.meta.size.w, self._jsonData.meta.size.h + assert(imageWidth == self.image:getWidth(), "Image width metadata doesn't match actual width of file") + assert(imageHeight == self.image:getHeight(), "Image height metadata doesn't match actual height of file") +end + +return peachy diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..616ae29 --- /dev/null +++ b/main.lua @@ -0,0 +1,38 @@ +--load peachy +peachy = require("libs/peachy") + +--load player +player = require("modules/player") + +--load content_loader +local content_loader = require("modules/content_loader") +spritesheets = content_loader.spritesheets +sound_effects = content_loader.sound_effects +backgrounds = content_loader.backgrounds +music = content_loader.music + +--load globals +local globals = require("modules/globals") +local game = globals.game + +--load draw_callback +draw_mod = require("modules/draw_callback") +local draw_callback = draw_mod.draw +local drawerinit = draw_mod.drawerinit + +--load update_callback +update_mod = require("modules/update_callback") +local update_callback = update_mod.update +local updateinit = update_mod.updateinit + +drawerinit(spritesheets, player, game) +updateinit(game) +player:init(sound_effects, spritesheets, game) + +function love.draw() + draw_callback() +end + +function love.update(dt) + update_callback(dt) +end \ No newline at end of file diff --git a/modules/content_loader.lua b/modules/content_loader.lua new file mode 100644 index 0000000..daa93c7 --- /dev/null +++ b/modules/content_loader.lua @@ -0,0 +1,64 @@ +--import media +--import spritesheets +local spritesheets = {} +--load jsons +--load player jumping +spritesheets["player_jump"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Jump") +--load player walking left +spritesheets["player_walk_left"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Walk_left") +--load player walking right +spritesheets["player_walk_right"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Walk_right") +--load player idle +spritesheets["player_idle"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Idle") +--load player dying +spritesheets["player_die"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Die") +--load player DEFAULT +spritesheets["player_default"] = peachy.new("assets/images/spritesheets/player.json", love.graphics.newImage("assets/images/spritesheets/player.png"), "Default") + +--load backgrounds +local backgrounds = {} +for i, file in ipairs(love.filesystem.getDirectoryItems("assets/images/backgrounds")) do + if file:find(".png") then + --load backgrounds + print(i) + backgrounds[i] = love.graphics.newImage("assets/images/backgrounds/"..file) + end +end +--load sound effects +local sound_effects = {} +for i, file in ipairs(love.filesystem.getDirectoryItems("assets/sounds/effects")) do + if file:find(".ogg") then + --load sound effects + local keyname = file:gsub(".ogg", "") + sound = love.audio.newSource("assets/sounds/effects/"..file, "static") + sound_effects[keyname] = {} + sound_effects[keyname].volume = 1 + sound_effects[keyname].timer = love.timer.getTime() + sound_effects[keyname].first_play = true + sound_effects[keyname].sound = sound + sound_effects[keyname].duration = sound:getDuration() + sound_effects[keyname].timer_min = sound:getDuration() * 2 + sound_effects[keyname].play = function(self) + if (love.timer.getTime() - self.timer) >= self.timer_min or self.first_play then + self.timer = love.timer.getTime() + self.first_play = false + self.sound:setVolume(self.volume) + self.sound:play() + end + end + end +end +--load music +local music = {} +for i, file in ipairs(love.filesystem.getDirectoryItems("assets/sounds/music")) do + if file:find(".ogg") then + --load music + music[file:sub(1, -5)] = love.audio.newSource("assets/sounds/music/"..file, "stream") + end +end +return { + spritesheets = spritesheets, + backgrounds = backgrounds, + sound_effects = sound_effects, + music = music +} \ No newline at end of file diff --git a/modules/draw_callback.lua b/modules/draw_callback.lua new file mode 100644 index 0000000..6205b1e --- /dev/null +++ b/modules/draw_callback.lua @@ -0,0 +1,33 @@ +local spritesheets = {} +local player = {} +local game = {} + +function drawerinit(spritesheetst, playert, gamex) + spritesheets = spritesheetst + player = playert + game = gamex +end + +local function draw() + --draw background + love.graphics.draw(backgrounds[game.level], 0, 0) + --draw player_animation + player.animation:draw(player.x, player.y) + if player.state == "idle" then + spritesheets["player_idle"]:draw(player.x, player.y) + elseif player.state == "walk_left" then + spritesheets["player_walk_left"]:draw(player.x, player.y) + elseif player.state == "walk_right" then + spritesheets["player_walk_right"]:draw(player.x, player.y) + elseif player.state == "jump" then + spritesheets["player_jump"]:draw(player.x, player.y) + elseif player.state == "die" then + spritesheets["player_die"]:draw(player.x, player.y) + else + spritesheets["player_default"]:draw(player.x, player.y) + end +end +return { + drawerinit = drawerinit, + draw = draw +} \ No newline at end of file diff --git a/modules/globals.lua b/modules/globals.lua new file mode 100644 index 0000000..3a9c5b6 --- /dev/null +++ b/modules/globals.lua @@ -0,0 +1,13 @@ +local game = {} +game.debug = true +game.width = 1024 +game.height = 768 +game.title = "Weeeeeeeee" +game.version = "0.0.1" +game.fullscreen = false +game.level = 1 +game.die_animation_elapsed = false + +return { + game = game +} \ No newline at end of file diff --git a/modules/player.lua b/modules/player.lua new file mode 100644 index 0000000..d1b704e --- /dev/null +++ b/modules/player.lua @@ -0,0 +1,80 @@ +local player = {} +player.x = 0 +player.y = 500 +player.width = 160 +player.height = 160 +player.speed = 200 +player.jump_height = 500 +player.state = "idle" +player.direction = "right" +player.alive = true +player.init = function(self, sound_effects, spritesheets, game) + self.sound_effects = sound_effects + self.spritesheets = spritesheets + self.game = game + player.animation = spritesheets["player_idle"] +end + +player.walk_right = function(self, dt) + if self.x < self.game.width - self.width and self.alive then + self.x = self.x + self.speed * dt + self.state = "walk_right" + self.direction = "right" + self.animation = self.spritesheets["player_walk_right"] + end +end +player.walk_left = function(self, dt) + if self.x > 0 and self.alive then + self.x = self.x - self.speed * dt + self.state = "walk_left" + self.direction = "left" + self.animation = self.spritesheets["player_walk_left"] + end +end +player.jump = function(self, dt) + if self.y > 0 and self.alive then + self.y = self.y - self.jump_height * dt + self.state = "jump" + self.animation = self.spritesheets["player_jump"] + self.sound_effects["jump"]:play() + end +end +player.down = function(self, dt) + if self.y < self.game.height - self.height and self.alive then + self.y = self.y + self.jump_height * dt + self.state = "jump" + self.animation = self.spritesheets["player_jump"] + self.sound_effects["jump"]:play() + end +end +player.die = function(self, dt) + if self.alive then + self.alive = false + self.state = "die" + self.animation = self.spritesheets["player_die"] + self.sound_effects["die"]:play() + die_animation_elapsed = false + end +end +player.revive = function(self, dt) + if not self.alive then + self.alive = true + self.state = "idle" + self.animation = self.spritesheets["player_default"] + self.sound_effects["revive"]:play() + die_animation_elapsed = false + end +end +player.idle = function(self, dt) + if self.alive then + self.state = "idle" + self.animation = self.spritesheets["player_idle"] + end +end +player.default = function(self, dt) + if self.alive then + self.state = "default" + self.animation = self.spritesheets["player_default"] + end +end +return player \ No newline at end of file diff --git a/modules/update_callback.lua b/modules/update_callback.lua new file mode 100644 index 0000000..cb3a95f --- /dev/null +++ b/modules/update_callback.lua @@ -0,0 +1,54 @@ +local game = {} + +local function updateinit(gamex) + game = gamex +end + +local function update(dt) + --update player_animation + if spritesheets["player_die"]:getFrame() ~= 16 and not game.die_animation_elapsed then + spritesheets["player_die"]:update(dt) + else + game.die_animation_elapsed = true + end + if player.alive then + spritesheets["player_jump"]:update(dt) + spritesheets["player_walk_left"]:update(dt) + spritesheets["player_walk_right"]:update(dt) + spritesheets["player_idle"]:update(dt) + spritesheets["player_default"]:update(dt) + end + + --get keyboard + local key = love.keyboard.isDown + --move player + if key("d") then + player:walk_right(dt) + elseif key("a") then + player:walk_left(dt) + else + player:idle(dt) + end + --jump player + if key("w") then + player:jump(dt) + end + if game.debug then + if key("s") then + player:down(dt) + end + if key("f") then + --die + player:die(dt) + game.die_animation_elapsed = false + elseif key("g") then + --revive player + player:revive(dt) + game.die_animation_elapsed = false + end + end +end +return { + update = update, + updateinit = updateinit +} \ No newline at end of file diff --git a/test.tldr b/test.tldr new file mode 100644 index 0000000..f3a64d9 --- /dev/null +++ b/test.tldr @@ -0,0 +1,97 @@ +{ + "document": { + "id": "doc", + "name": "New Document", + "version": 15.3, + "pages": { + "page": { + "id": "page", + "name": "Page 1", + "childIndex": 1, + "shapes": { + "feba193a-4518-4953-0fd0-31a3c7550237": { + "id": "feba193a-4518-4953-0fd0-31a3c7550237", + "type": "draw", + "name": "Draw", + "parentId": "page", + "childIndex": 1, + "point": [ + 786, + 360.5 + ], + "rotation": 0, + "style": { + "color": "black", + "size": "small", + "isFilled": false, + "dash": "draw", + "scale": 1 + }, + "points": [ + [ + 0, + 0, + 0.5 + ], + [ + 0, + 0, + 0.5 + ] + ], + "isComplete": true + }, + "c3e4d223-d03e-4cd0-278b-53b9ae1164f0": { + "id": "c3e4d223-d03e-4cd0-278b-53b9ae1164f0", + "type": "draw", + "name": "Draw", + "parentId": "page", + "childIndex": 2, + "point": [ + 2315.63, + 1240.9 + ], + "rotation": 0, + "style": { + "color": "black", + "size": "small", + "isFilled": false, + "dash": "draw", + "scale": 1 + }, + "points": [ + [ + 0, + 0, + 0.5 + ], + [ + 0, + 0, + 0.5 + ] + ], + "isComplete": true + } + }, + "bindings": {} + } + }, + "pageStates": { + "page": { + "id": "page", + "selectedIds": [], + "camera": { + "point": [ + 0, + -79.5 + ], + "zoom": 1 + }, + "editingId": null + } + }, + "assets": {} + }, + "assets": {} +} \ No newline at end of file