diff --git a/.github/workflows/build-android.yaml b/.github/workflows/build-android.yaml new file mode 100644 index 0000000..585a0b8 --- /dev/null +++ b/.github/workflows/build-android.yaml @@ -0,0 +1,91 @@ +name: Android Build + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: "temurin" + + - uses: nttld/setup-ndk@v1 + id: setup-ndk + with: + ndk-version: r27 # 27.0.12077973, see: https://github.com/android/ndk/releases/tag/r27 + + - uses: krdlab/setup-haxe@v1 + with: + haxe-version: 4.3.6 + + - name: Set up Haxelib dependencies + run: | + haxelib install openfl 9.4.1 + haxelib install lime 8.2.2 + haxelib install flixel 6.0.0 + haxelib install flixel-addons 3.3.2 + haxelib install flixel-ui 2.6.4 + haxelib install hxcpp + + - name: Set up hxcpp 4.3.78 + run: | + echo ">>> Existing hxcpp <<<" + ls -l $(haxelib config)/hxcpp/ + + echo ">>> Updated hxcpp <<<" + set -ex + pushd $(mktemp -d) + wget -O hxcpp.zip https://github.com/HaxeFoundation/hxcpp/releases/download/v4.3.78/hxcpp-4.3.78.zip + unzip hxcpp.zip + mv hxcpp-4.3.78 '4,3,78' + mkdir -p $(haxelib config)/hxcpp/ || true + mv '4,3,78' $(haxelib config)/hxcpp/ + haxelib set hxcpp 4.3.78 + + - name: lime setup android + run: | + haxelib run lime config ANDROID_SDK $ANDROID_SDK_ROOT + haxelib run lime config ANDROID_NDK_ROOT ${{ steps.setup-ndk.outputs.ndk-path }} + haxelib run lime config JAVA_HOME ${{ env.JAVA_HOME }} + haxelib run lime config ANDROID_SETUP true + haxelib run lime config + + - name: Set up keystore + run: | + set -ex + KS_FILE="$(mktemp --suffix='.keystore')" + chmod 600 $KS_FILE + echo -En '${{ secrets.QUIK_DEV_KEYSTORE }}' | base64 -d > $KS_FILE + chmod 400 $KS_FILE + echo "KEYSTORE_FILE=$KS_FILE" >> $GITHUB_ENV + + - name: Build APK + run: | + haxelib run lime build android -final -Drelease \ + --certificate-path=$KEYSTORE_FILE --certificate-alias=dev --certificate-password=changeme + + - name: Build Bundle + run: | + export ANDROID_GRADLE_TASK=":app:bundleRelease" + haxelib run lime build android -final -Drelease \ + --certificate-path=$KEYSTORE_FILE --certificate-alias=dev --certificate-password=changeme + + - name: Upload APK (Release) + uses: actions/upload-artifact@main + with: + name: quik-release.apk + path: ./export/android/bin/app/build/outputs/apk/release/quik-release.apk + if-no-files-found: warn + + - name: Upload Bundle (Release) + uses: actions/upload-artifact@main + with: + name: quik-release.aab + path: ./export/android/bin/app/build/outputs/bundle/release/app-release.aab + if-no-files-found: warn diff --git a/Project.xml b/Project.xml index 66b2229..33bf5dc 100644 --- a/Project.xml +++ b/Project.xml @@ -2,9 +2,8 @@ - + -
@@ -31,11 +30,16 @@ - - - - - + +
+ + + + + + + +
@@ -60,13 +64,13 @@ - + - + - + @@ -83,8 +87,7 @@ - - + diff --git a/README.md b/README.md index 2aa8624..a734193 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Quik The source and assets for [Quik: Gravity Flip Platformer](https://irrationalidiom.com/quik). -**This is the develop branch, updated to use Flixel 4.** +**This is the develop branch, updated to use Flixel 6.** It is inspired by two games I love: _Cannabalt_ and _VVVVVV_. @@ -31,12 +31,22 @@ You will need Haxe, OpenFL/Lime and HaxeFlixel set up. ### Known Working Versions of Dependencies - * Haxe 4.3.4 - * HxCPP 4.3.2 - * Flixel 5.8.0 - * Flixel UI 2.6.1 - * Lime 8.1.2 - * OpenFL 9.3.3 + * Haxe 4.3.6 + * HxCPP 4.3.78 (Note this is a not published to Haxelib as yet, find it [here](https://github.com/HaxeFoundation/hxcpp/releases/tag/v4.3.78)) + * Flixel 6.0.0 + * Flixel UI 2.6.4 + * Lime 8.2.2 + * OpenFL 9.4.1 + +#### Android Dependencies + +The first two are highly specific, and line up with HXCPP and OpenFL/Lime. + + * NDK 27.0.12077973 + * Android 15 SDK (API level 35) + + * SDK Build Tools 36.0.0 + * SDK Command Line Tools 19.0 ### Then diff --git a/source/Game.hx b/source/Game.hx index ce20e35..cf6a723 100644 --- a/source/Game.hx +++ b/source/Game.hx @@ -1,15 +1,13 @@ package ; -import flixel.FlxGame; -import flixel.FlxG; -import flixel.FlxState; -import flixel.util.FlxSave; -import flixel.sound.FlxSound; - import flash.Lib; - import Reg.HighScore; import Reg.LevelStats; +import flixel.FlxG; +import flixel.FlxGame; +import flixel.FlxState; +import flixel.sound.FlxSound; +import flixel.util.FlxSave; import misc.GlobalHighscores; class Game extends FlxGame { @@ -36,6 +34,7 @@ class Game extends FlxGame { var framerate:Int = 60; // How many frames per second the game should run at. var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode. var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets + var devLevelIndex:Int = -1; // For Development purposes: Index of level to jump straight into var stageWidth:Int = Lib.current.stage.stageWidth; var stageHeight:Int = Lib.current.stage.stageHeight; @@ -50,14 +49,17 @@ class Game extends FlxGame { trace("zoom is now", zoom); } - #if mobile - framerate = 30; - #end - seenInstructions = false; attachAutoSave(); - if (Reg.autoSave.data.active != null && Reg.autoSave.data.active == true) + if (devLevelIndex >= 0) + { + Reg.level = devLevelIndex; + Reg.resumed = true; // Pause + initialState = PlayState; + trace('DEV starting at level: ${Reg.level}'); + } + else if (Reg.autoSave.data.active != null && Reg.autoSave.data.active == true) { Reg.score = Reg.autoSave.data.score; Reg.level = Reg.autoSave.data.level; @@ -146,7 +148,7 @@ class Game extends FlxGame { #if android // Default behavior is to end the current activity, instead // we can use this for our pause screen - FlxG.android.preventDefaultBackAction = true; + FlxG.android.preventDefaultKeys = [BACK]; #end loadGlobalSettings(); diff --git a/source/Input.hx b/source/Input.hx index a9d3365..c40e494 100644 --- a/source/Input.hx +++ b/source/Input.hx @@ -77,35 +77,35 @@ class Input { public static function skipPressed():Bool { #if (desktop || flash || html5) - return FlxG.keys.justPressed.SPACE || FlxG.mouse.pressed; + return FlxG.keys.justPressed.SPACE || FlxG.mouse.pressed || FlxG.gamepads.anyJustPressed(ANY); #end #if mobile - return isTouched(); + return isTouched() || FlxG.gamepads.anyJustPressed(ANY); #end } public static function flipPressed():Bool { #if (desktop || flash || html5) - return FlxG.keys.justPressed.SPACE; + return FlxG.keys.justPressed.SPACE || FlxG.gamepads.anyJustPressed(A) || FlxG.gamepads.anyJustPressed(B); #end #if mobile - return isTouched(); + return isTouched() || FlxG.gamepads.anyJustPressed(A) || FlxG.gamepads.anyJustPressed(B); #end } public static function bouncePressed():Bool { #if (desktop || flash || html5) - return FlxG.keys.justPressed.CONTROL; + return FlxG.keys.justPressed.CONTROL || FlxG.gamepads.anyJustPressed(X); #end #if mobile if (bounceFlag) { - return true; + return true || FlxG.gamepads.anyJustPressed(X); } #end @@ -115,20 +115,20 @@ class Input { public static function stopHeld():Bool { #if (desktop || flash || html5) - return FlxG.keys.pressed.SHIFT; + return FlxG.keys.pressed.SHIFT || FlxG.gamepads.anyPressed(Y); #end - return stopFlag; + return stopFlag || FlxG.gamepads.anyPressed(Y); } public static function escapePressed():Bool { #if (desktop || flash || html5) - return FlxG.keys.justPressed.ESCAPE; + return FlxG.keys.justPressed.ESCAPE || FlxG.gamepads.anyPressed(START); #end #if android - return FlxG.android.justPressed("BACK"); + return FlxG.android.justPressed.BACK || FlxG.gamepads.anyPressed(START); #end return false; diff --git a/source/Main.hx b/source/Main.hx index eac8c25..6cfe0ec 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -1,37 +1,12 @@ package; -import flash.display.Sprite; -import flash.events.Event; -import flash.Lib; +import openfl.display.Sprite; -class Main extends Sprite +class Main extends Sprite { - public static function main():Void - { - Lib.current.addChild(new Main()); - } - - public function new() + public function new() { super(); - - if (stage != null) - { - init(); - } - else - { - addEventListener(Event.ADDED_TO_STAGE, init); - } - } - - private function init(?E:Event):Void - { - if (hasEventListener(Event.ADDED_TO_STAGE)) - { - removeEventListener(Event.ADDED_TO_STAGE, init); - } - addChild(new Game()); } } diff --git a/source/MenuState.hx b/source/MenuState.hx index 21de3dc..b9c3c39 100644 --- a/source/MenuState.hx +++ b/source/MenuState.hx @@ -1,7 +1,11 @@ package; +import flixel.addons.effects.chainable.FlxRainbowEffect; +import flixel.addons.effects.chainable.FlxEffectSprite; import flixel.text.FlxText; import flixel.group.FlxSpriteGroup; +import flixel.tweens.FlxTween; +import flixel.tweens.FlxTween.FlxTweenType; import flixel.FlxG; import flixel.FlxState; @@ -83,7 +87,7 @@ class MenuState extends FlxState #end buttonGroup.screenCenter(); - buttonGroup.y += 30; + buttonGroup.y += 35; title.screenCenter(); title.y -= 60; @@ -100,10 +104,34 @@ class MenuState extends FlxState #end #end // demo + var edition = new FlxText(); + edition.borderStyle = FlxTextBorderStyle.SHADOW; + edition.borderSize = 1.0; + edition.text = "10th Anniversary Edition!"; + + edition.centerOrigin(); + edition.screenCenter(); + #if !mobile + edition.y -= 35; + #end + #if mobile + edition.y -= 15; + #end + + var editionEffect = new FlxEffectSprite(edition); + var rainbow = new FlxRainbowEffect(0.5); + editionEffect.effects = [rainbow]; + editionEffect.active = rainbow.active = true; + editionEffect.x = edition.x; + editionEffect.y = edition.y; + FlxTween.tween(editionEffect, { "scale.x": 1.1, "scale.y": 1.1 }, 1.0, { type: FlxTweenType.PINGPONG }); + add(backdrop); add(title); add(buttonGroup); + add(editionEffect); + var buildNote = new FlxText(); buildNote.size = 8; buildNote.alignment = "right"; diff --git a/source/PlayState.hx b/source/PlayState.hx index ca00655..c0fbd88 100644 --- a/source/PlayState.hx +++ b/source/PlayState.hx @@ -1,46 +1,38 @@ package; -import flixel.util.FlxDirectionFlags; -import flixel.math.FlxPoint; -import flixel.FlxG; +import Input; +import LevelLoader; +import PlayStateHUD; import flixel.FlxBasic; -import flixel.FlxSprite; -import flixel.FlxState; import flixel.FlxCamera; +import flixel.FlxG; import flixel.FlxObject; +import flixel.FlxSprite; +import flixel.FlxState; import flixel.FlxSubState; - -import flixel.sound.FlxSound; -import flixel.system.FlxQuadTree; - -import flixel.group.FlxGroup; -//import flixel.group.FlxTypedGroup; - -import flixel.text.FlxText; -import flixel.util.FlxColor; -import flixel.path.FlxPath; -import flixel.util.FlxDestroyUtil; - -import flixel.math.FlxPoint; - -import flixel.tweens.FlxTween; -import flixel.effects.FlxFlicker; -import flixel.addons.effects.FlxTrail; import flixel.addons.display.FlxBackdrop; -import flixel.addons.effects.chainable.IFlxEffect; +import flixel.addons.editors.tiled.TiledObject; +import flixel.addons.editors.tiled.TiledPropertySet; +import flixel.addons.effects.FlxTrail; import flixel.addons.effects.chainable.FlxEffectSprite; import flixel.addons.effects.chainable.FlxGlitchEffect; +import flixel.addons.effects.chainable.IFlxEffect; +import flixel.addons.tile.FlxTilemapExt; +import flixel.effects.FlxFlicker; import flixel.effects.particles.FlxEmitter; - -import flixel.tile.FlxTilemap; +import flixel.group.FlxGroup; +import flixel.math.FlxPoint; +import flixel.math.FlxPoint; +import flixel.path.FlxPath; +import flixel.sound.FlxSound; +import flixel.system.FlxQuadTree; +import flixel.text.FlxText; import flixel.tile.FlxBaseTilemap; -import flixel.addons.tile.FlxTilemapExt; -import flixel.addons.editors.tiled.TiledObject; -import flixel.addons.editors.tiled.TiledPropertySet; - -import Input; -import PlayStateHUD; -import LevelLoader; +import flixel.tile.FlxTilemap; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import flixel.util.FlxDestroyUtil; +import flixel.util.FlxDirectionFlags; /** * A FlxState which can be used for the actual gameplay. @@ -50,7 +42,7 @@ class PlayState extends FlxState private var wallMap:FlxTilemap; private var bounceMap:FlxTilemap; private var decorationMap:FlxTilemap; - private var overlayMap:FlxTilemapExt; + private var overlayMap:FlxTilemap; private var spikeGroup:FlxTypedGroup; private var objectGroup:FlxTypedGroup; private var powerupGroup:FlxTypedGroup; @@ -208,8 +200,10 @@ class PlayState extends FlxState add(playerTrail); add(player); - if (overlayMap != null) + if (overlayMap != null) { + trace('added overlayMap'); add(overlayMap); + } add(scanlines); @@ -291,7 +285,7 @@ class PlayState extends FlxState savePoint.velocity.set(player.velocity.x, player.velocity.y); savePoint.accelleration.set(player.acceleration.x, player.acceleration.y); savePoint.flipY = player.flipY; - savePoint.facing = player.facing; + savePoint.facing = player.facing.toInt(); savePoint.timeElapsed = stats.elapsedTime; } @@ -300,7 +294,7 @@ class PlayState extends FlxState if (savePoint == null) return false; - player.facing = savePoint.facing; + player.facing = FlxDirectionFlags.fromInt(savePoint.facing); player.flipY = savePoint.flipY; player.acceleration.set(savePoint.accelleration.x, savePoint.accelleration.y); player.velocity.set(savePoint.velocity.x, savePoint.velocity.y); @@ -458,7 +452,7 @@ class PlayState extends FlxState return overlayMap.overlapsWithCallback(player); }); - overlayMap.alpha = (under ? OVERLAY_ALPHA : 1.0); + overlayMap.visible = !under; } FlxG.collide(endArea, player, onEndAreaHit); @@ -607,7 +601,7 @@ class PlayState extends FlxState case "overlay": // Convert to a FlxTilemapExt since we need alpha support - overlayMap = new FlxTilemapExt(); + overlayMap = new FlxTilemap(); var tileWidth = Math.floor(obj.width / obj.widthInTiles); var tileHeight = Math.floor(obj.height / obj.heightInTiles); overlayMap.loadMapFromArray(obj.getData(), @@ -944,14 +938,17 @@ class Platform extends FlxSprite newPath.push(new FlxPoint(this.x + p.x, this.y + p.y)); } - if (points.length > 0) + if (newPath.length > 0) { - this.x = points[0].x; - this.y = points[0].y; + this.x = newPath[0].x; + this.y = newPath[0].y; } this.path = FlxDestroyUtil.destroy(this.path); this.path = new FlxPath(); + // IMPORTANT to ensure the platform is in the expected starting position + this.path.centerMode = FlxPathAnchorMode.TOP_LEFT; + this.path.start(newPath, speed, FlxPathType.YOYO); this.path.setNode(0); @@ -974,7 +971,11 @@ class Platform extends FlxSprite super(xPos, yPos, assetPath); - centerOrigin(); + // Due to changes in newer versions of Flixel, we need to adjust the position + // to keep platforms working the same as when the game was first coded (Flixel 3). + // See also: setPath() + this.x -= this.width / 2; + this.y -= this.height / 2; } override public function destroy():Void diff --git a/source/Reg.hx b/source/Reg.hx index 4e40415..96811d7 100644 --- a/source/Reg.hx +++ b/source/Reg.hx @@ -1,8 +1,7 @@ package; -import flixel.util.FlxSave; - import PlayState.SavePointState; +import flixel.util.FlxSave; /** * Handy, pre-built Registry class that can be used to store diff --git a/source/misc/GlobalHighscores.hx b/source/misc/GlobalHighscores.hx index a5366b0..037c3eb 100644 --- a/source/misc/GlobalHighscores.hx +++ b/source/misc/GlobalHighscores.hx @@ -1,20 +1,18 @@ package misc; import StringTools; +import flash.events.Event; +import flash.events.HTTPStatusEvent; import haxe.crypto.Base64; -import haxe.io.Bytes; import haxe.crypto.Hmac; - +import haxe.io.Bytes; +import Reg.HighScore; import openfl.events.IOErrorEvent; import openfl.events.SecurityErrorEvent; -import flash.events.HTTPStatusEvent; -import flash.events.Event; -import openfl.net.URLLoaderDataFormat; -import openfl.net.URLRequestMethod; import openfl.net.URLLoader; +import openfl.net.URLLoaderDataFormat; import openfl.net.URLRequest; - -import Reg.HighScore; +import openfl.net.URLRequestMethod; import misc.MacroStuff; class GlobalHighscores {