├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── gradle-build-push.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── android ├── AndroidManifest.xml ├── build.gradle ├── ic_launcher-web.png ├── proguard-rules.pro ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ ├── drawable-xxxhdpi │ │ └── ic_launcher.png │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── main │ └── java │ └── com │ └── bladecoder │ └── inkplayer │ └── android │ └── AndroidLauncher.java ├── assets ├── inkplayer.properties ├── story.ink.json └── ui │ ├── 1 │ ├── background.png │ ├── blade_logo.png │ ├── help.png │ ├── libgdx_logo.png │ ├── title.png │ ├── ui.atlas │ └── ui.png │ ├── credits.txt │ ├── fonts │ ├── Remington-Noiseless.ttf │ ├── Roboto-Black.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-Thin.ttf │ └── Ubuntu-M.ttf │ ├── ui.json │ ├── ui.properties │ ├── ui_de.properties │ └── ui_es.properties ├── build.gradle ├── core ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── bladecoder │ └── inkplayer │ ├── ExternalFunctions.java │ ├── InkApp.java │ ├── Line.java │ ├── StoryListener.java │ ├── StoryManager.java │ ├── assets │ ├── AssetConsumer.java │ ├── BasePathResolver.java │ ├── EngineAssetManager.java │ └── EngineResolutionFileResolver.java │ ├── common │ ├── Config.java │ ├── DPIUtils.java │ ├── FileUtils.java │ └── RectangleRenderer.java │ ├── i18n │ ├── I18N.java │ └── I18NControl.java │ └── ui │ ├── AppScreen.java │ ├── BladeSkin.java │ ├── ChoicesUI.java │ ├── CreditsScreen.java │ ├── DebugScreen.java │ ├── InitScreen.java │ ├── MenuScreen.java │ ├── StoryScreen.java │ ├── TextPanel.java │ └── UI.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── ios ├── Info.plist.xml ├── build.gradle ├── data │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Media.xcassets │ │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app-store-icon-1024@1x.png │ │ ├── ipad-app-icon-76@1x.png │ │ ├── ipad-app-icon-76@2x.png │ │ ├── ipad-notifications-icon-20@1x.png │ │ ├── ipad-notifications-icon-20@2x.png │ │ ├── ipad-pro-app-icon-83.5@2x.png │ │ ├── ipad-settings-icon-29@1x.png │ │ ├── ipad-settings-icon-29@2x.png │ │ ├── ipad-spotlight-icon-40@1x.png │ │ ├── ipad-spotlight-icon-40@2x.png │ │ ├── iphone-app-icon-60@2x.png │ │ ├── iphone-app-icon-60@3x.png │ │ ├── iphone-notification-icon-20@2x.png │ │ ├── iphone-notification-icon-20@3x.png │ │ ├── iphone-spotlight-icon-40@2x.png │ │ ├── iphone-spotlight-icon-40@3x.png │ │ ├── iphone-spotlight-settings-icon-29@2x.png │ │ └── iphone-spotlight-settings-icon-29@3x.png │ │ ├── Contents.json │ │ └── Logo.imageset │ │ ├── Contents.json │ │ ├── libgdx@1x.png │ │ ├── libgdx@2x.png │ │ └── libgdx@3x.png ├── robovm.properties ├── robovm.xml └── src │ └── main │ └── java │ └── com │ └── bladecoder │ └── inkplayer │ └── ios │ └── IOSLauncher.java ├── lwjgl3 ├── build.gradle ├── icons │ ├── logo.icns │ ├── logo.ico │ └── logo.png ├── nativeimage.gradle └── src │ └── main │ ├── java │ └── com │ │ └── bladecoder │ │ └── inkplayer │ │ └── desktop │ │ ├── Lwjgl3Launcher.java │ │ └── StartupHelper.java │ └── resources │ ├── icon128.png │ ├── icon16.png │ ├── icon32.png │ └── icon64.png ├── raw ├── icons │ ├── bladeinktemplate.desktop │ ├── genicons.sh │ └── icon.svg ├── ink-src │ └── TheIntercept.ink ├── ui-src │ ├── blade_logo.svg │ ├── icon.svg │ ├── ios-default-images.svg │ └── ui.svg └── ui │ ├── back.png │ ├── border_rect.9.png │ ├── credits.png │ ├── debug.png │ ├── delete.png │ ├── dialog_down.png │ ├── dialog_up.png │ ├── facebook.png │ ├── help.png │ ├── instagram.png │ ├── leave.png │ ├── menu.png │ ├── pack.json │ ├── plus.png │ ├── rect.9.png │ ├── twitter.png │ └── white_pixel.png ├── release.sh └── settings.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{java,scala,groovy,kt,kts}] 12 | indent_size = 4 13 | 14 | [*.gradle] 15 | indent_size = 2 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.bat text=auto eol=crlf 3 | -------------------------------------------------------------------------------- /.github/workflows/gradle-build-push.yml: -------------------------------------------------------------------------------- 1 | name: Run Gradle build on push 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Set up JDK 17 12 | uses: actions/setup-java@v4 13 | with: 14 | distribution: corretto 15 | java-version: 17 16 | - name: Build with Gradle 17 | run: ./gradlew build 18 | - name: Pass tests and checks 19 | run: ./gradlew check 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Upload Release Asset 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | create-release: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | build: [ linux, windows, macos ] 13 | include: 14 | - build: linux 15 | os: ubuntu-latest 16 | exec: theintercept 17 | platform: x86_64-linux 18 | - build: macos 19 | os: macos-latest 20 | exec: theintercept.app 21 | platform: x86_64-macos 22 | - build: windows 23 | os: windows-latest 24 | exec: theintercept 25 | platform: x86_64-windows 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Set up JDK 17 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: corretto 33 | java-version: 17 34 | - name: Build package 35 | run: | 36 | VERSION=${{ github.event.release.tag_name}} 37 | VERSION=${VERSION:1 } 38 | # replace version property in inkplayer.properties 39 | if [[ "$OSTYPE" == "darwin"* ]]; then 40 | sed -i '' -e "s/version=.*/version=$VERSION/" assets/inkplayer.properties 41 | else 42 | sed -i -e "s/version=.*/version=$VERSION/" assets/inkplayer.properties 43 | fi 44 | ./gradlew lwjgl3:jpackageImage 45 | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then 46 | echo "${{ secrets.KEYSTORE }}" | base64 --decode > $HOME/keystore.jks 47 | VERSION_CODE=$((`echo $VERSION | cut -d. -f1` * 100 + `echo $VERSION | cut -d. -f2`)) 48 | ./gradlew -Pkeystore=$HOME/keystore.jks -PstorePassword=${{ secrets.KEYSTORE_PASSWORD }} -Palias=${{ secrets.KEYALIAS }} -PkeyPassword=${{ secrets.KEY_PASSWORD }} android:assembleRelease -Pversion=$VERSION -PversionCode=$VERSION_CODE 49 | fi 50 | shell: bash 51 | - name: Upload app image to Github release 52 | run: | 53 | staging="theintercept-${{github.event.release.tag_name}}-${{ matrix.platform }}" 54 | mkdir "$staging" 55 | cp -r lwjgl3/build/jpackage/${{ matrix.exec }} "$staging/" 56 | if [ "${{ matrix.os }}" = "windows-latest" ]; then 57 | 7z a "$staging.zip" "$staging" 58 | gh release upload ${{github.event.release.tag_name}} "$staging.zip" 59 | else 60 | tar czf "$staging.tar.gz" "$staging" 61 | gh release upload ${{github.event.release.tag_name}} "$staging.tar.gz" 62 | 63 | # Upload Android app if ubuntu-latest 64 | if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then 65 | staging="theintercept-${{github.event.release.tag_name}}" 66 | mv android/build/outputs/apk/release/android-release.apk "android/build/outputs/apk/release/theintercept-${{github.event.release.tag_name}}.apk" 67 | gh release upload ${{github.event.release.tag_name}} "android/build/outputs/apk/release/theintercept-${{github.event.release.tag_name}}.apk" 68 | fi 69 | fi 70 | env: 71 | GITHUB_TOKEN: ${{ github.TOKEN }} 72 | shell: bash 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Gradle: 2 | .gradle/ 3 | gradle-app.setting 4 | /build/ 5 | /android/build/ 6 | /core/build/ 7 | /lwjgl2/build/ 8 | /lwjgl3/build/ 9 | /html/build/ 10 | /teavm/build/ 11 | /ios/build/ 12 | /ios-moe/build/ 13 | /headless/build/ 14 | /server/build/ 15 | /shared/build/ 16 | 17 | ## Java: 18 | *.class 19 | *.war 20 | *.ear 21 | hs_err_pid* 22 | .attach_pid* 23 | 24 | ## Android: 25 | /android/libs/armeabi-v7a/ 26 | /android/libs/arm64-v8a/ 27 | /android/libs/x86/ 28 | /android/libs/x86_64/ 29 | /android/gen/ 30 | /android/out/ 31 | local.properties 32 | com_crashlytics_export_strings.xml 33 | 34 | ## Robovm: 35 | /ios/robovm-build/ 36 | 37 | ## iOS: 38 | /ios/xcode/*.xcodeproj/* 39 | !/ios/xcode/*.xcodeproj/xcshareddata 40 | !/ios/xcode/*.xcodeproj/project.pbxproj 41 | /ios/xcode/native/ 42 | /ios/IOSLauncher.app 43 | /ios/IOSLauncher.app.dSYM 44 | 45 | ## GWT: 46 | /html/war/ 47 | /html/gwt-unitCache/ 48 | .apt_generated/ 49 | /html/war/WEB-INF/deploy/ 50 | /html/war/WEB-INF/classes/ 51 | .gwt/ 52 | gwt-unitCache/ 53 | www-test/ 54 | .gwt-tmp/ 55 | 56 | ## TeaVM: 57 | # Not sure yet... 58 | 59 | ## IntelliJ, Android Studio: 60 | .idea/ 61 | *.ipr 62 | *.iws 63 | *.iml 64 | 65 | ## Eclipse: 66 | .classpath 67 | .project 68 | .metadata/ 69 | /android/bin/ 70 | /core/bin/ 71 | /lwjgl2/bin/ 72 | /lwjgl3/bin/ 73 | /html/bin/ 74 | /teavm/bin/ 75 | /ios/bin/ 76 | /ios-moe/bin/ 77 | /headless/bin/ 78 | /server/bin/ 79 | /shared/bin/ 80 | *.tmp 81 | *.bak 82 | *.swp 83 | *~.nib 84 | .settings/ 85 | .loadpath 86 | .externalToolBuilders/ 87 | *.launch 88 | 89 | 90 | ## NetBeans: 91 | 92 | /nbproject/private/ 93 | /android/nbproject/private/ 94 | /core/nbproject/private/ 95 | /lwjgl2/nbproject/private/ 96 | /lwjgl3/nbproject/private/ 97 | /html/nbproject/private/ 98 | /teavm/nbproject/private/ 99 | /ios/nbproject/private/ 100 | /ios-moe/nbproject/private/ 101 | /headless/nbproject/private/ 102 | /server/nbproject/private/ 103 | /shared/nbproject/private/ 104 | 105 | /nbbuild/ 106 | /android/nbbuild/ 107 | /core/nbbuild/ 108 | /lwjgl2/nbbuild/ 109 | /lwjgl3/nbbuild/ 110 | /html/nbbuild/ 111 | /teavm/nbbuild/ 112 | /ios/nbbuild/ 113 | /ios-moe/nbbuild/ 114 | /headless/nbbuild/ 115 | /server/nbbuild/ 116 | /shared/nbbuild/ 117 | 118 | /dist/ 119 | /android/dist/ 120 | /core/dist/ 121 | /lwjgl2/dist/ 122 | /lwjgl3/dist/ 123 | /html/dist/ 124 | /teavm/dist/ 125 | /ios/dist/ 126 | /ios-moe/dist/ 127 | /headless/dist/ 128 | /server/dist/ 129 | /shared/dist/ 130 | 131 | /nbdist/ 132 | /android/nbdist/ 133 | /core/nbdist/ 134 | /lwjgl2/nbdist/ 135 | /lwjgl3/nbdist/ 136 | /html/nbdist/ 137 | /teavm/nbdist/ 138 | /ios/nbdist/ 139 | /ios-moe/nbdist/ 140 | /headless/nbdist/ 141 | /server/nbdist/ 142 | /shared/nbdist/ 143 | 144 | nbactions.xml 145 | nb-configuration.xml 146 | 147 | ## OS-Specific: 148 | .DS_Store 149 | Thumbs.db 150 | 151 | ## Miscellaneous: 152 | *~ 153 | *.*# 154 | *#*# 155 | /assets/assets.txt 156 | 157 | ## Special cases: 158 | 159 | ## There is a resource-config.json file generated by nativeimage.gradle if you use Graal Native Image. 160 | ## Some usage may need extra resource configuration in a different file with the same name. 161 | ## You could also add that configuration to the text in nativeimage.gradle . 162 | ## You should delete or comment out the next line if you have configuration in a different resource-config.json . 163 | **/resource-config.json 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blade Ink Template 2 | 3 | This is a template to create interactive fiction games using the [Ink](https://www.inklestudios.com/ink/) language. 4 | 5 | Features: 6 | 7 | - Publish your games on Android, iOS and Desktop (Windows, Mac and Linux) with no effort. 8 | - No coding is necessary. 9 | - Fully customizable fonts, colors and images. 10 | 11 | # How to generate your own interactive fiction game with the Blade Ink Template 12 | 13 | ## 1. Write your story 14 | 15 | - Download [Inky](https://github.com/inkle/inky/releases) and write your story. 16 | - If you don't know Ink, you can read the tutorial [Writing with Ink](https://github.com/inkle/ink/blob/master/Documentation/WritingWithInk.md). 17 | 18 | ## 2. Export your story to Blade Ink Template 19 | 20 | - Make sure you have installed the [JDK 17](https://adoptium.net/es/) 21 | - Download the [Blade Ink Template](https://github.com/bladecoder/blade-ink-template). 22 | - Export your story to JSON from Inky as `story.ink.json` and copy it to the `assets` folder in the Blade Ink Template. 23 | - You can now test your story with `./gradlew desktop:run` in Mac and Linux, or `gradlew.bat desktop:run` in Windows. 24 | 25 | ## 3. Customize your fonts, colors, images and titles. 26 | 27 | - Edit the `ui/ui.json` with your text editor and customize the skin of your game. 28 | - Overwrite the images in `ui/1` with yours. 29 | - Change the `title` property in the `inkplayer.properties` with your app title. 30 | 31 | ## 4. Prepare for Android 32 | 33 | - Overwrite all icons in `android/res/drawable-*` with your app icons. 34 | - Put your app. name in the `android/res/values/strings.xml` file. 35 | - Change the `applicationId` property in the `android/build.gradle` file with yours. 36 | 37 | 38 | ## 3. Prepare for Desktop 39 | 40 | - Overwrite all icons in `desktop/src/main/resources/icons` with your app icons. 41 | 42 | ## 4. Prepare for iOS 43 | 44 | - Overwrite all icons in `ios/data/Media.xcassets/AppIcon.appiconset` with your app icons. 45 | - Overwrite all launch images in `ios/data` with your app launch images. 46 | - Change `app.name` and `app.id`properties in the `ios/robovm.properties` file with yours. 47 | 48 | ## 5. Releasing your game 49 | 50 | Releasing your game is very easy with the `release.sh` script. 51 | 52 | Unfortunately this is an Unix script and only works on Macos and Linux. Windows users can install a bash shell to execute the script or use the `gradlew.bat` directly to generate the packages. More info about using `gradlew` [here](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline). 53 | 54 | The first step is to edit the `release.sh` script and set the next parameters: 55 | 56 | ```sh 57 | # The destination folder for the packaged application 58 | DIST_DIR=$HOME"/PACKAGES" 59 | 60 | # IOS signing 61 | IOS_SIGN_IDENTITY="iPhone Distribution" 62 | IOS_PROVISIONING_PROFILE="your app provisioning profile" 63 | 64 | # Android signing 65 | ANDROID_KEYSTORE=$HOME/folder/to/the/android.keystore 66 | ANDROID_KEY_ALIAS=androidkeyalias 67 | ``` 68 | 69 | And now you can: 70 | 71 | Generate a `.ipa` for iPhone and iPad: 72 | 73 | ```sh 74 | ./release.sh ios 75 | ``` 76 | 77 | Generate a `.apk` for Android: 78 | 79 | ```sh 80 | ./release.sh android 81 | ``` 82 | 83 | Generate a runnable `.jar` for desktop: 84 | 85 | ```sh 86 | ./release.sh desktop 87 | ``` 88 | 89 | ### Create native executables for desktop platforms 90 | 91 | The normal way to distribute desktop applications is to embed the Java VM inside the package to not force the user to install Java. 92 | 93 | There are several tools to create native executables for Windows, Macos and Linux with your runnable .jar. Some examples: 94 | 95 | - [packr](https://github.com/libgdx/packr): Generates Windows, Macos and Linux packages. 96 | - [launch4j](https://sourceforge.net/projects/launch4j/): Generates packages for Windows. 97 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | namespace "com.bladecoder.inkplayer" 6 | compileSdk 34 7 | sourceSets { 8 | main { 9 | manifest.srcFile 'AndroidManifest.xml' 10 | java.setSrcDirs(['src/main/java']) 11 | aidl.setSrcDirs(['src/main/java']) 12 | renderscript.setSrcDirs(['src/main/java']) 13 | res.setSrcDirs(['res']) 14 | assets.setSrcDirs(['../assets']) 15 | jniLibs.setSrcDirs(['libs']) 16 | } 17 | } 18 | packagingOptions { 19 | resources { 20 | excludes += ['META-INF/robovm/ios/robovm.xml', 'META-INF/DEPENDENCIES.txt', 'META-INF/DEPENDENCIES', 21 | 'META-INF/dependencies.txt', '**/*.gwt.xml'] 22 | pickFirsts += ['META-INF/LICENSE.txt', 'META-INF/LICENSE', 'META-INF/license.txt', 'META-INF/LGPL2.1', 23 | 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/notice.txt'] 24 | } 25 | } 26 | defaultConfig { 27 | applicationId 'com.bladecoder.inkplayer' 28 | minSdkVersion 19 29 | targetSdkVersion 34 30 | 31 | if (project.hasProperty('versionCode')) 32 | versionCode project.versionCode.toInteger() 33 | else 34 | versionCode 1 35 | 36 | versionName version 37 | multiDexEnabled true 38 | } 39 | compileOptions { 40 | sourceCompatibility "11" 41 | targetCompatibility "11" 42 | coreLibraryDesugaringEnabled true 43 | } 44 | 45 | if (project.hasProperty('keystore')) { 46 | signingConfigs { 47 | release { 48 | storeFile file(project.keystore) 49 | storePassword project.storePassword 50 | keyAlias project.alias 51 | keyPassword project.keyPassword 52 | } 53 | } 54 | 55 | buildTypes { 56 | release { 57 | signingConfig signingConfigs.release 58 | } 59 | } 60 | } 61 | 62 | buildTypes { 63 | release { 64 | minifyEnabled true 65 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 66 | } 67 | } 68 | 69 | java { 70 | toolchain { 71 | languageVersion = JavaLanguageVersion.of(17) 72 | } 73 | } 74 | } 75 | 76 | repositories { 77 | // needed for AAPT2, may be needed for other tools 78 | google() 79 | } 80 | 81 | configurations { natives } 82 | 83 | dependencies { 84 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' 85 | implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" 86 | implementation project(':core') 87 | 88 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a" 89 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a" 90 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86" 91 | natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64" 92 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" 93 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" 94 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" 95 | natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" 96 | 97 | } 98 | 99 | // Called every time gradle gets executed, takes the native dependencies of 100 | // the natives configuration, and extracts them to the proper libs/ folders 101 | // so they get packed with the APK. 102 | tasks.register('copyAndroidNatives') { 103 | doFirst { 104 | file("libs/armeabi-v7a/").mkdirs() 105 | file("libs/arm64-v8a/").mkdirs() 106 | file("libs/x86_64/").mkdirs() 107 | file("libs/x86/").mkdirs() 108 | 109 | configurations.natives.copy().files.each { jar -> 110 | def outputDir = null 111 | if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") 112 | if(jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a") 113 | if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64") 114 | if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") 115 | if(outputDir != null) { 116 | copy { 117 | from zipTree(jar) 118 | into outputDir 119 | include "*.so" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { packageTask -> 127 | packageTask.dependsOn 'copyAndroidNatives' 128 | } 129 | 130 | tasks.register('run', Exec) { 131 | def path 132 | def localProperties = project.file("../local.properties") 133 | if (localProperties.exists()) { 134 | Properties properties = new Properties() 135 | localProperties.withInputStream { instr -> 136 | properties.load(instr) 137 | } 138 | def sdkDir = properties.getProperty('sdk.dir') 139 | if (sdkDir) { 140 | path = sdkDir 141 | } else { 142 | path = "$System.env.ANDROID_SDK_ROOT" 143 | } 144 | } else { 145 | path = "$System.env.ANDROID_SDK_ROOT" 146 | } 147 | 148 | def adb = path + "/platform-tools/adb" 149 | commandLine "$adb", 'shell', 'am', 'start', '-n', 'com.bladecoder.inkplayer/com.bladecoder.inkplayer.android.AndroidLauncher' 150 | } 151 | 152 | eclipse.project.name = appName + "-android" 153 | -------------------------------------------------------------------------------- /android/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/ic_launcher-web.png -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | 22 | -verbose 23 | 24 | -dontwarn android.support.** 25 | -dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication 26 | -dontwarn com.badlogic.gdx.utils.GdxBuild 27 | -dontwarn com.badlogic.gdx.physics.box2d.utils.Box2DBuild 28 | -dontwarn com.badlogic.gdx.jnigen.BuildTarget* 29 | -dontwarn com.badlogic.gdx.graphics.g2d.freetype.FreetypeBuild 30 | 31 | # If you're encountering ProGuard issues and use gdx-controllers, THIS MIGHT BE WHY!!! 32 | 33 | # Uncomment the following line if you use the gdx-controllers official extension. 34 | #-keep class com.badlogic.gdx.controllers.android.AndroidControllers 35 | 36 | -keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* { 37 | (com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration); 38 | } 39 | 40 | -keepclassmembers class com.badlogic.gdx.physics.box2d.World { 41 | boolean contactFilter(long, long); 42 | void beginContact(long); 43 | void endContact(long); 44 | void preSolve(long, long); 45 | void postSolve(long, long); 46 | boolean reportFixture(long); 47 | float reportRayFixture(long, float, float, float, float, float); 48 | } 49 | 50 | # You will need the next three lines if you use scene2d for UI or gameplay 51 | # If you don't use scene2d at all, you can remove or comment out the next line 52 | -keep public class com.badlogic.gdx.scenes.scene2d.** { *; } 53 | # You will need the next two lines if you use BitmapFont or any scene2d.ui text 54 | -keep public class com.badlogic.gdx.graphics.g2d.BitmapFont { *; } 55 | # You will probably need this line in most cases 56 | -keep public class com.badlogic.gdx.graphics.Color { *; } 57 | 58 | # These two lines are used with mapping files; see https://developer.android.com/build/shrink-code#retracing 59 | -keepattributes LineNumberTable,SourceFile 60 | -renamesourcefileattribute SourceFile 61 | -------------------------------------------------------------------------------- /android/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /android/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/android/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | The Intercept 4 | 5 | -------------------------------------------------------------------------------- /android/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | -------------------------------------------------------------------------------- /android/src/main/java/com/bladecoder/inkplayer/android/AndroidLauncher.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.android; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.badlogic.gdx.backends.android.AndroidApplication; 6 | import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; 7 | import com.bladecoder.inkplayer.InkApp; 8 | 9 | /** Launches the Android application. */ 10 | public class AndroidLauncher extends AndroidApplication { 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | AndroidApplicationConfiguration configuration = new AndroidApplicationConfiguration(); 15 | configuration.useImmersiveMode = true; // Recommended, but not required. 16 | initialize(new InkApp(), configuration); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/inkplayer.properties: -------------------------------------------------------------------------------- 1 | bladeInkVersion=1.0.0 2 | debug=false 3 | fullscreen=false 4 | gdxVersion=1.10.0 5 | languages=en,es,ru,it 6 | resolutions=1 7 | roboVMVersion=2.3.12 8 | story=story.ink.json 9 | title=The Intercept 10 | version=0.07 11 | -------------------------------------------------------------------------------- /assets/ui/1/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/background.png -------------------------------------------------------------------------------- /assets/ui/1/blade_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/blade_logo.png -------------------------------------------------------------------------------- /assets/ui/1/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/help.png -------------------------------------------------------------------------------- /assets/ui/1/libgdx_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/libgdx_logo.png -------------------------------------------------------------------------------- /assets/ui/1/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/title.png -------------------------------------------------------------------------------- /assets/ui/1/ui.atlas: -------------------------------------------------------------------------------- 1 | 2 | ui.png 3 | size: 1158,516 4 | format: RGBA8888 5 | filter: Linear,Linear 6 | repeat: none 7 | back 8 | rotate: false 9 | xy: 981, 1 10 | size: 90, 92 11 | orig: 120, 120 12 | offset: 12, 14 13 | index: -1 14 | border_rect 15 | rotate: false 16 | xy: 1036, 100 17 | size: 29, 29 18 | split: 5, 5, 5, 5 19 | orig: 29, 29 20 | offset: 0, 0 21 | index: -1 22 | credits 23 | rotate: false 24 | xy: 792, 367 25 | size: 148, 148 26 | orig: 148, 148 27 | offset: 0, 0 28 | index: -1 29 | debug 30 | rotate: false 31 | xy: 792, 217 32 | size: 148, 148 33 | orig: 148, 148 34 | offset: 0, 0 35 | index: -1 36 | delete 37 | rotate: false 38 | xy: 1036, 131 39 | size: 112, 112 40 | orig: 112, 112 41 | offset: 0, 0 42 | index: -1 43 | dialog_down 44 | rotate: false 45 | xy: 1073, 54 46 | size: 75, 75 47 | orig: 93, 93 48 | offset: 9, 8 49 | index: -1 50 | dialog_up 51 | rotate: false 52 | xy: 1064, 290 53 | size: 75, 75 54 | orig: 93, 93 55 | offset: 9, 10 56 | index: -1 57 | facebook 58 | rotate: false 59 | xy: 792, 95 60 | size: 120, 120 61 | orig: 120, 120 62 | offset: 0, 0 63 | index: -1 64 | help 65 | rotate: false 66 | xy: 942, 367 67 | size: 148, 148 68 | orig: 148, 148 69 | offset: 0, 0 70 | index: -1 71 | instagram 72 | rotate: false 73 | xy: 942, 245 74 | size: 120, 120 75 | orig: 120, 120 76 | offset: 0, 0 77 | index: -1 78 | leave 79 | rotate: false 80 | xy: 792, 3 81 | size: 94, 90 82 | orig: 94, 90 83 | offset: 0, 0 84 | index: -1 85 | menu 86 | rotate: false 87 | xy: 1092, 430 88 | size: 65, 85 89 | orig: 123, 123 90 | offset: 29, 18 91 | index: -1 92 | paper 93 | rotate: false 94 | xy: 1, 7 95 | size: 789, 508 96 | orig: 789, 508 97 | offset: 0, 0 98 | index: -1 99 | plus 100 | rotate: false 101 | xy: 888, 2 102 | size: 91, 91 103 | orig: 91, 91 104 | offset: 0, 0 105 | index: -1 106 | rect 107 | rotate: false 108 | xy: 1073, 21 109 | size: 31, 31 110 | split: 5, 5, 5, 4 111 | orig: 31, 31 112 | offset: 0, 0 113 | index: -1 114 | twitter 115 | rotate: false 116 | xy: 914, 95 117 | size: 120, 120 118 | orig: 120, 120 119 | offset: 0, 0 120 | index: -1 121 | white_pixel 122 | rotate: false 123 | xy: 1036, 95 124 | size: 3, 3 125 | orig: 3, 3 126 | offset: 0, 0 127 | index: -1 128 | -------------------------------------------------------------------------------- /assets/ui/1/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/1/ui.png -------------------------------------------------------------------------------- /assets/ui/credits.txt: -------------------------------------------------------------------------------- 1 | t#STORY BY 2 | TOM KAIL 3 | JOSEPH HUMPHREY 4 | JON INGOLD 5 | 6 | t#LIBGDX PORT BY 7 | RAFAEL GARCÍA 8 | 9 | t#WRITTEN IN INK 10 | A LANGUAGE BY 11 | INKLE STUDIOS 12 | 13 | s#120 14 | i#libgdx_logo.png 15 | s#250 16 | i#blade_logo.png 17 | -------------------------------------------------------------------------------- /assets/ui/fonts/Remington-Noiseless.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/fonts/Remington-Noiseless.ttf -------------------------------------------------------------------------------- /assets/ui/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /assets/ui/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /assets/ui/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /assets/ui/fonts/Ubuntu-M.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bladecoder/blade-ink-template/03b7f65b31bbfe5cc1b0b1bb70e46fd730ddbfac/assets/ui/fonts/Ubuntu-M.ttf -------------------------------------------------------------------------------- /assets/ui/ui.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | BitmapFont: { 4 | default: {file: fonts/Roboto-Regular.ttf, size: 17}, 5 | debug: {file: fonts/Roboto-Regular.ttf, size: 14}, 6 | big-font: {file: fonts/Roboto-Black.ttf, size: 30}, 7 | message-font: {file: fonts/Roboto-Black.ttf, size: 18}, 8 | choices-font: {file: fonts/Remington-Noiseless.ttf, size: 22}, 9 | credits-title: {file: fonts/Ubuntu-M.ttf, size: 20}, 10 | credits: {file: fonts/Ubuntu-M.ttf, size: 30}, 11 | story-font: {file: fonts/Remington-Noiseless.ttf, size: 20}, 12 | }, 13 | 14 | 15 | Color: { 16 | white: { a: 1, b: 1, g: 1, r: 1 }, 17 | black: { a: 1, b: 0, g: 0, r: 0 }, 18 | dark-grey: { a: 1, b: 0.25, g: 0.25, r: 0.25 }, 19 | light-grey: { a: 1, b: 0.85, g: 0.85, r: 0.85 }, 20 | grey: { a: 1, b: 0.5, g: 0.5, r: 0.5 }, 21 | text-dark: { a: 1, b: 1, g: 1, r: 1 }, 22 | black-trans: { r: 0.6, g: 0.6, b: 0.6, a: 1 }, 23 | white-trans: { r: 1, g: 1, b: 1, a: 0.8 }, 24 | white-trans2: { r: 1, g: 1, b: 1, a: 0.4 }, 25 | yellow: { r: 1, g: 1, b: 0.13, a: 1 } 26 | }, 27 | 28 | 29 | TintedDrawable: { 30 | black: { name: white_pixel, color: { r: 0, g: 0, b: 0, a: 1.0 } }, 31 | black08: { name: white_pixel, color: { r: 0, g: 0, b: 0, a: 0.5 } }, 32 | white: { name: white_pixel, color: white }, 33 | grey: { name: white_pixel, color: grey}, 34 | dark-grey: { name: white_pixel, color: dark-grey}, 35 | light-grey: { name: white_pixel, color: light-grey}, 36 | 37 | menu-up: { name: menu, color: white-trans2}, 38 | 39 | border-rect-over: { name: border_rect, color: white-trans}, 40 | border-rect-down: { name: border_rect, color: black}, 41 | 42 | help-down: {name:help, color: black}, 43 | help-over: {name:help, color: white-trans}, 44 | credits-down: {name:credits, color: black}, 45 | credits-over: {name:credits, color: white-trans}, 46 | debug-down: {name:debug, color: black}, 47 | debug-over: {name:debug, color: white-trans}, 48 | 49 | back-down: {name:back, color: black}, 50 | back-over: {name:back, color: white-trans}, 51 | 52 | delete-over: {name:delete, color: white-trans}, 53 | 54 | dialog-up-down: {name:dialog_up, color: black}, 55 | dialog-up-over: {name:dialog_up, color: white-trans}, 56 | dialog-down-down: {name:dialog_down, color: black}, 57 | dialog-down-over: {name:dialog_down, color: white-trans}, 58 | }, 59 | 60 | ButtonStyle: { 61 | menu: {up:menu-up, over: menu, down: menu}, 62 | 63 | credits: {up:credits, down: credits-down, over: credits-over}, 64 | debug: {up:debug, down: debug-down, over: debug-over}, 65 | 66 | back: {up:back, down: back-down, over: back-over}, 67 | 68 | dialog-up: {up:dialog_up, down: dialog-up-down, over: dialog-up-over}, 69 | dialog-down: {up:dialog_down, down: dialog-down-down, over: dialog-down-over}, 70 | }, 71 | 72 | 73 | TextButtonStyle: { 74 | default: { 75 | font: debug, 76 | up: grey, 77 | fontColor: white, 78 | overFontColor: yellow, 79 | downFontColor: grey, 80 | disabledFontColor: dark-grey 81 | }, 82 | 83 | menu: { 84 | font: big-font, 85 | fontColor: white, 86 | downFontColor: black, 87 | overFontColor: yellow, 88 | disabledFontColor: dark-grey 89 | }, 90 | 91 | toggle: { 92 | down: border-rect-down, 93 | checked: border_rect, 94 | font: debug, 95 | fontColor: white, 96 | overFontColor: yellow, 97 | downFontColor: black-trans, 98 | disabledFontColor: dark-grey 99 | } 100 | 101 | ui-dialog: { 102 | font: message-font, 103 | fontColor: white, 104 | overFontColor: yellow, 105 | downFontColor: grey, 106 | } 107 | }, 108 | 109 | 110 | LabelStyle: { 111 | default: { font: default, fontColor: white}, 112 | debug: { font: debug, fontColor: white}, 113 | title: { font: big-font, fontColor: white}, 114 | ui-dialog: { font: message-font, fontColor: white}, 115 | }, 116 | 117 | 118 | MenuScreenStyle: { 119 | default: {bgFile: ui/background.png, textButtonStyle: menu, showTitle: false, titleFile: ui/title.png, align: center} 120 | }, 121 | 122 | 123 | CreditScreenStyle: { 124 | default: { 125 | titleFont: credits-title, font: credits 126 | } 127 | }, 128 | 129 | 130 | ChoicesUIStyle: { 131 | default: {background: black08, textButtonStyle: {font: choices-font, fontColor: white, overFontColor: yellow}, autoselect: false} 132 | }, 133 | 134 | 135 | TextFieldStyle: { 136 | default: { 137 | selection: grey, 138 | background: dark-grey, 139 | focusedBackground: dark-grey, 140 | disabledBackground: grey, 141 | font: debug, 142 | fontColor: white, 143 | focusedFontColor: text-dark, 144 | disabledFontColor: text-dark, 145 | cursor: white, messageFont: debug, 146 | messageFontColor: text-dark 147 | } 148 | }, 149 | 150 | 151 | ScrollPaneStyle: { 152 | default: { 153 | vScroll: dark-grey, vScrollKnob: grey, hScrollKnob: dark-grey, hScroll: dark-grey, corner: white 154 | }, 155 | 156 | opaque: { 157 | vScroll: dark-grey, 158 | hScrollKnob: dark-grey, 159 | background: grey, hScroll: dark-grey, vScrollKnob: dark-grey, corner: grey 160 | } 161 | }, 162 | 163 | ListStyle: { 164 | default: { 165 | fontColorUnselected: text-dark, 166 | selection: dark-grey, 167 | fontColorSelected: text-dark, 168 | font: debug 169 | }, 170 | 171 | opaque: { 172 | fontColorUnselected: text-dark, 173 | selection: grey, 174 | fontColorSelected: text-dark, 175 | font: debug 176 | } 177 | }, 178 | 179 | 180 | SelectBoxStyle: { 181 | default: { 182 | background: dark-grey, 183 | backgroundOver: grey, 184 | backgroundOpen: dark-grey, 185 | listStyle: default, 186 | scrollStyle: opaque, 187 | font: debug, 188 | fontColor: white 189 | }, 190 | }, 191 | 192 | WindowStyle: { 193 | default: { titleFont: big-font, background: black08, titleFontColor: white }, 194 | }, 195 | 196 | TextPanelStyle: { 197 | default: { 198 | background: light-grey, labelStyle: {font: story-font, fontColor: {r:0.3, g:0.35, b:0.3, a:1}} 199 | }, 200 | } 201 | 202 | StoryScreenStyle: { 203 | default: {bgFile: ui/background.png} 204 | }, 205 | } 206 | -------------------------------------------------------------------------------- /assets/ui/ui.properties: -------------------------------------------------------------------------------- 1 | 2 | ui.back=Back 3 | ui.continue=Continue 4 | ui.credits=Credits 5 | ui.help=Help 6 | ui.load=Load/Save 7 | ui.new=New Game 8 | ui.newSlot=Save Game 9 | ui.no=NO 10 | ui.noSavedGames=No saved games found 11 | ui.override=The current game progress will be lost. Are you sure you want to start a new game? 12 | ui.override_load=The current game progress will be lost. Are you sure you want to load this game? 13 | ui.quit=Exit 14 | ui.remove=Are you sure you want to remove this saved game? 15 | ui.save=Save Game 16 | ui.yes=YES 17 | ui.credits=Credits 18 | -------------------------------------------------------------------------------- /assets/ui/ui_de.properties: -------------------------------------------------------------------------------- 1 | ui.back=Zurück 2 | ui.continue=Fortsetzen 3 | ui.credits=Danksagungen 4 | ui.help=Hilfe 5 | ui.load=Laden/Speichern 6 | ui.new=Neues Spiel 7 | ui.newSlot=Spiel speichern 8 | ui.no=NEIN 9 | ui.noSavedGames=Keine gespeicherten Spielstände gefunden 10 | ui.override=Der aktuelle Spielfortschritt wird verloren gehen. Möchtest du wirklich ein neues Spiel starten? 11 | ui.override_load=Der aktuelle Spielfortschritt wird verloren gehen. Möchtest du diesen Spielstand wirklich laden? 12 | ui.quit=Beenden 13 | ui.remove=Möchtest du diesen gespeicherten Spielstand wirklich entfernen? 14 | ui.save=Spiel speichern 15 | ui.yes=JA 16 | ui.credits=Mitwirkende 17 | -------------------------------------------------------------------------------- /assets/ui/ui_es.properties: -------------------------------------------------------------------------------- 1 | 2 | ui.back=Atrás 3 | ui.continue=Continuar 4 | ui.credits=Créditos 5 | ui.help=Ayuda 6 | ui.load=Guardar/Cargar 7 | ui.new=Nuevo Juego 8 | ui.newSlot=Guardar Juego 9 | ui.no=NO 10 | ui.noSavedGames=No se han encontrado juegos salvados 11 | ui.override=El progreso actual del juego se perderá. ¿Estás seguro de que quieres empezar un nuevo juego? 12 | ui.override_load=El progreso actual del juego se perderá. ¿Estás seguro de que quieres cargar la partida seleccionada? 13 | ui.quit=Salir 14 | ui.remove=¿Estás seguro de que quieres borrar la partida seleccionada? 15 | ui.save=Guardar Juego 16 | ui.yes=SI 17 | ui.credits=Créditos 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | google() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:$androidPluginVersion" 9 | classpath "com.badlogicgames.gdx:gdx-tools:$gdxVersion" 10 | } 11 | } 12 | 13 | allprojects { 14 | apply plugin: 'eclipse' 15 | apply plugin: 'idea' 16 | 17 | // This allows you to "Build and run using IntelliJ IDEA", an option in IDEA's Settings. 18 | idea { 19 | module { 20 | outputDir file('build/classes/java/main') 21 | testOutputDir file('build/classes/java/test') 22 | } 23 | } 24 | } 25 | 26 | configure(subprojects - project(':android')) { 27 | apply plugin: 'java-library' 28 | sourceCompatibility = 11 29 | compileJava { 30 | options.incremental = true 31 | } 32 | // From https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/ 33 | // The article can be helpful when using assets.txt in your project. 34 | compileJava.doLast { 35 | // projectFolder/assets 36 | def assetsFolder = new File("${project.rootDir}/assets/") 37 | // projectFolder/assets/assets.txt 38 | def assetsFile = new File(assetsFolder, "assets.txt") 39 | // delete that file in case we've already created it 40 | assetsFile.delete() 41 | 42 | // iterate through all files inside that folder 43 | // convert it to a relative path 44 | // and append it to the file assets.txt 45 | fileTree(assetsFolder).collect { assetsFolder.relativePath(it) }.each { 46 | assetsFile.append(it + "\n") 47 | } 48 | } 49 | 50 | java { 51 | toolchain { 52 | languageVersion = JavaLanguageVersion.of(17) 53 | } 54 | } 55 | } 56 | 57 | 58 | subprojects { 59 | repositories { 60 | mavenCentral() 61 | } 62 | } 63 | 64 | eclipse.project.name = 'blade-ink-template' + '-parent' 65 | 66 | // Run `gradle pack` task to generate skin.atlas file at assets/ui. 67 | import com.badlogic.gdx.tools.texturepacker.TexturePacker 68 | 69 | tasks.register('pack') { 70 | 71 | doLast { 72 | 73 | // Note that if you need multiple atlases, you can duplicate the 74 | // TexturePacker.process invocation and change paths to generate 75 | // additional atlases with this task. 76 | TexturePacker.process( 77 | 'raw/ui', // Raw assets path. 78 | 'assets/ui/1/', // Output directory. 79 | 'ui' // Name of the generated atlas (without extension). 80 | ) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 2 | eclipse.project.name = appName + '-core' 3 | 4 | dependencies { 5 | api "com.badlogicgames.gdx:gdx:$gdxVersion" 6 | api "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" 7 | api "com.bladecoder.ink:blade-ink:$bladeInkVersion" 8 | } 9 | 10 | 11 | tasks.register('setVersion', WriteProperties) { 12 | def props = new Properties() 13 | def propFile = project.file("../assets/inkplayer.properties"); 14 | props.load(new FileReader(propFile)) 15 | 16 | props."version" = version 17 | props."bladeInkVersion" = bladeInkVersion 18 | props."gdxVersion" = gdxVersion 19 | props."roboVMVersion" = robovmVersion 20 | 21 | setProperties(props); 22 | setOutputFile(propFile); 23 | 24 | doLast { 25 | println "Set version info in inkplayer.properties" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ExternalFunctions.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.bladecoder.ink.runtime.Story.ExternalFunction; 5 | 6 | public class ExternalFunctions { 7 | private static final String TAG="ExternalFunctions"; 8 | 9 | private StoryManager inkManager; 10 | 11 | public ExternalFunctions() { 12 | } 13 | 14 | public void bindExternalFunctions(StoryManager ink) throws Exception { 15 | 16 | this.inkManager = ink; 17 | 18 | inkManager.getStory().bindExternalFunction("getModelProp", new ExternalFunction() { 19 | 20 | @Override 21 | public Object call(Object[] args) throws Exception { 22 | try { 23 | String p = args[0].toString(); 24 | 25 | return p; 26 | } catch (Exception e) { 27 | Gdx.app.error( TAG, "Ink getModelProp: " + e.getMessage(), e); 28 | } 29 | 30 | return null; 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/InkApp.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer; 17 | 18 | import java.io.IOException; 19 | import java.text.MessageFormat; 20 | 21 | import com.badlogic.gdx.Application; 22 | import com.badlogic.gdx.ApplicationListener; 23 | import com.badlogic.gdx.Gdx; 24 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 25 | import com.bladecoder.inkplayer.common.Config; 26 | import com.bladecoder.inkplayer.ui.UI; 27 | 28 | public class InkApp implements ApplicationListener { 29 | private static final String TAG = "InkApp"; 30 | 31 | /* Run the specified path at init */ 32 | private String initPath; 33 | 34 | private String forceRes; 35 | private boolean debug = false; 36 | private boolean restart = false; 37 | private String gameState; 38 | private String recordName; 39 | 40 | private StoryManager storyManager; 41 | private UI ui; 42 | 43 | public void setInitPath(String s) { 44 | initPath = s; 45 | } 46 | 47 | public void loadGameState(String s) { 48 | gameState = s; 49 | } 50 | 51 | public void setPlayMode(String recordName) { 52 | this.recordName = recordName; 53 | } 54 | 55 | public void setDebugMode() { 56 | debug = true; 57 | } 58 | 59 | public void setRestart() { 60 | restart = true; 61 | } 62 | 63 | public void forceResolution(String forceRes) { 64 | this.forceRes = forceRes; 65 | } 66 | 67 | public UI getUI() { 68 | return ui; 69 | } 70 | 71 | @Override 72 | public void create() { 73 | EngineAssetManager.getInstance().setScale(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 74 | 75 | storyManager = new StoryManager(); 76 | ui = new UI(storyManager); 77 | 78 | if (!debug) 79 | debug = Config.getProperty(Config.DEBUG_PROP, debug); 80 | 81 | if (debug) 82 | Gdx.app.setLogLevel(Application.LOG_DEBUG); 83 | 84 | Gdx.app.debug(TAG, "GAME CREATE"); 85 | 86 | if (forceRes == null) 87 | forceRes = Config.getProperty(Config.FORCE_RES_PROP, forceRes); 88 | 89 | if (forceRes != null) { 90 | EngineAssetManager.getInstance().forceResolution(forceRes); 91 | } 92 | 93 | if (Gdx.app.getLogLevel() == Application.LOG_DEBUG) { 94 | 95 | if (initPath == null) { 96 | initPath = Config.getProperty(Config.INITPATH_PROP, initPath); 97 | } 98 | 99 | if (initPath != null) { 100 | try { 101 | StoryListener sl = storyManager.getStoryListener(); 102 | storyManager.setStoryListener(null); 103 | storyManager.newStory(Config.getProperty(Config.STORY, "story.ink.json")); 104 | storyManager.run(initPath); 105 | storyManager.setStoryListener(sl); 106 | storyManager.next(); 107 | } catch (Exception e) { 108 | dispose(); 109 | Gdx.app.error(TAG, "EXITING: " + e.getMessage()); 110 | Gdx.app.exit(); 111 | } 112 | 113 | ui.setCurrentScreen(UI.Screens.STORY_SCREEN); 114 | } 115 | 116 | if(restart) { 117 | // TODO 118 | } 119 | 120 | if(gameState != null) { 121 | // TODO 122 | } 123 | 124 | if(recordName != null) { 125 | // TODO 126 | } 127 | } 128 | 129 | // Capture back key 130 | Gdx.input.setCatchBackKey(true); 131 | 132 | Gdx.graphics.setContinuousRendering(false); 133 | Gdx.graphics.requestRendering(); 134 | } 135 | 136 | @Override 137 | public void dispose() { 138 | Gdx.app.debug(TAG, "GAME DISPOSE"); 139 | ui.dispose(); 140 | } 141 | 142 | @Override 143 | public void render() { 144 | ui.render(); 145 | } 146 | 147 | @Override 148 | public void resize(int width, int height) { 149 | Gdx.app.debug(TAG, MessageFormat.format("GAME RESIZE {0}x{1}", width, height)); 150 | 151 | if (ui != null) 152 | ui.resize(width, height); 153 | } 154 | 155 | @Override 156 | public void pause() { 157 | Gdx.app.debug(TAG, "GAME PAUSE"); 158 | ui.pause(); 159 | 160 | if (ui.getStoryManager().getStory() != null) { 161 | try { 162 | ui.getStoryManager().saveGameState(); 163 | } catch (IOException e) { 164 | Gdx.app.error(TAG, e.getMessage()); 165 | } 166 | } 167 | } 168 | 169 | @Override 170 | public void resume() { 171 | Gdx.app.debug(TAG, "GAME RESUME"); 172 | resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 173 | ui.resume(); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/Line.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer; 17 | 18 | import java.util.HashMap; 19 | 20 | public class Line { 21 | public Line(String t, HashMap p) { 22 | this.text = t; 23 | this.params = p; 24 | } 25 | 26 | public Line() { 27 | 28 | } 29 | 30 | public String text; 31 | public HashMap params; 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/StoryListener.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | 6 | public interface StoryListener { 7 | public void line(Line l); 8 | public void command(String name, HashMap params); 9 | public void choices(List choices); 10 | public void end(); 11 | public void newGame(); 12 | public void loadGame(); 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/assets/AssetConsumer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.assets; 17 | 18 | import com.badlogic.gdx.utils.Disposable; 19 | 20 | public interface AssetConsumer extends Disposable { 21 | /** 22 | * Send the assets to the AssetManager queue to load asynchronous 23 | */ 24 | public void loadAssets(); 25 | 26 | /** 27 | * Called when the AssetManager has loaded all the assets and can be retrieved. 28 | */ 29 | public void retrieveAssets(); 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/assets/BasePathResolver.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.assets; 17 | 18 | import java.io.File; 19 | 20 | import com.badlogic.gdx.Gdx; 21 | import com.badlogic.gdx.assets.loaders.FileHandleResolver; 22 | import com.badlogic.gdx.files.FileHandle; 23 | 24 | public class BasePathResolver implements FileHandleResolver { 25 | String basePath; 26 | 27 | @Override 28 | public FileHandle resolve(String fileName) { 29 | 30 | String fullName; 31 | 32 | if (new File(fileName).isAbsolute()) { 33 | fullName = fileName; 34 | } else { 35 | StringBuilder sb = new StringBuilder(); 36 | sb.append(basePath); 37 | sb.append("/"); 38 | sb.append(fileName); 39 | fullName = sb.toString(); 40 | } 41 | 42 | //Gdx.app.debug( InkApp.LOG_TAG, fullName); 43 | 44 | return Gdx.files.absolute(fullName); 45 | } 46 | 47 | public BasePathResolver(String base) { 48 | basePath = base; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/assets/EngineAssetManager.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.assets; 17 | 18 | import java.io.IOException; 19 | import java.io.UnsupportedEncodingException; 20 | import java.net.URL; 21 | import java.net.URLDecoder; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.Comparator; 25 | import java.util.Enumeration; 26 | import java.util.HashSet; 27 | import java.util.Set; 28 | import java.util.jar.JarEntry; 29 | import java.util.jar.JarFile; 30 | 31 | import com.badlogic.gdx.Application.ApplicationType; 32 | import com.badlogic.gdx.Gdx; 33 | import com.badlogic.gdx.assets.AssetManager; 34 | import com.badlogic.gdx.assets.loaders.FileHandleResolver; 35 | import com.badlogic.gdx.assets.loaders.TextureAtlasLoader; 36 | import com.badlogic.gdx.assets.loaders.TextureLoader; 37 | import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; 38 | import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution; 39 | import com.badlogic.gdx.files.FileHandle; 40 | import com.badlogic.gdx.graphics.Texture; 41 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 42 | import com.badlogic.gdx.graphics.g2d.TextureAtlas; 43 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; 44 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGeneratorLoader; 45 | import com.badlogic.gdx.graphics.g2d.freetype.FreetypeFontLoader; 46 | import com.bladecoder.inkplayer.common.Config; 47 | 48 | 49 | public class EngineAssetManager extends AssetManager { 50 | private static final String TAG="EngineAssetManager"; 51 | 52 | public static final String DESKTOP_PREFS_DIR = "InkPlayer"; 53 | public static final String NOT_DESKTOP_PREFS_DIR = "data/"; 54 | 55 | public static final String FONT_DIR = "ui/fonts/"; 56 | public static final String FONT_EXT = ".ttf"; 57 | 58 | public static final String INK_EXT = ".ink.json"; 59 | 60 | private static EngineAssetManager instance = null; 61 | 62 | private float scale = 1; 63 | 64 | private EngineResolutionFileResolver resResolver; 65 | 66 | protected EngineAssetManager() { 67 | this(new InternalFileHandleResolver()); 68 | // getLogger().setLevel(Application.LOG_DEBUG); 69 | } 70 | 71 | protected EngineAssetManager(FileHandleResolver resolver) { 72 | super(resolver); 73 | 74 | resResolver = new EngineResolutionFileResolver(resolver); 75 | setLoader(Texture.class, new TextureLoader(resResolver)); 76 | setLoader(TextureAtlas.class, new TextureAtlasLoader(resResolver)); 77 | setLoader(FreeTypeFontGenerator.class, new FreeTypeFontGeneratorLoader(resolver)); 78 | setLoader(BitmapFont.class, ".ttf", new FreetypeFontLoader(resolver)); 79 | 80 | Texture.setAssetManager(this); 81 | } 82 | 83 | public float getScale() { 84 | return scale; 85 | } 86 | 87 | public void setScale(int worldWidth, int worldHeight) { 88 | Resolution r[] = getResolutions(resResolver.getBaseResolver(), worldWidth, worldHeight); 89 | 90 | if (r == null || r.length == 0) { 91 | Gdx.app.error( TAG, "No resolutions defined. Maybe your 'assets' folder doesn't exists or it's empty"); 92 | return; 93 | } 94 | 95 | resResolver.setResolutions(r); 96 | resResolver.selectResolution(); 97 | scale = resResolver.getResolution().portraitWidth / (float) worldWidth; 98 | 99 | Gdx.app.debug( TAG, "Setting ASSETS SCALE: " + scale); 100 | } 101 | 102 | public static EngineAssetManager getInstance() { 103 | if (instance == null) { 104 | instance = new EngineAssetManager(); 105 | } 106 | 107 | return instance; 108 | } 109 | 110 | public void forceResolution(String suffix) { 111 | resResolver.setFixedResolution(suffix); 112 | 113 | Gdx.app.debug( TAG, "FORCING ASSETS RESOLUTION SCALE: " + suffix); 114 | } 115 | 116 | public Resolution getResolution() { 117 | return resResolver.getResolution(); 118 | } 119 | 120 | public boolean isLoading() { 121 | return !update(); 122 | } 123 | 124 | /** 125 | * Returns a file in the asset directory SEARCHING in the resolution 126 | * directories 127 | */ 128 | public FileHandle getResAsset(String filename) { 129 | return resResolver.resolve(filename); 130 | } 131 | 132 | /** 133 | * Returns a file in the asset directory without searching in the resolution 134 | * directories 135 | */ 136 | public FileHandle getAsset(String filename) { 137 | return resResolver.baseResolve(filename); 138 | } 139 | 140 | public void dispose() { 141 | super.dispose(); 142 | instance = null; 143 | } 144 | 145 | public boolean assetExists(String filename) { 146 | return resResolver.exists(filename); 147 | } 148 | 149 | private Resolution[] getResolutions(FileHandleResolver resolver, int worldWidth, int worldHeight) { 150 | ArrayList rl = new ArrayList(); 151 | 152 | String list[] = null; 153 | 154 | String configRes = Config.getProperty(Config.RESOLUTIONS, null); 155 | 156 | if(configRes != null) { 157 | list = configRes.split(","); 158 | } else { 159 | list = listAssetFiles("ui"); 160 | } 161 | 162 | if(list == null) 163 | list = new String[]{"1"}; 164 | 165 | for (String name : list) { 166 | 167 | try { 168 | float scale = Float.parseFloat(name); 169 | 170 | Gdx.app.debug( TAG, "RES FOUND: " + scale); 171 | 172 | Resolution r = new Resolution((int) (worldWidth * scale), (int) (worldHeight * scale), name); 173 | 174 | rl.add(r); 175 | } catch (NumberFormatException e) { 176 | 177 | } 178 | } 179 | 180 | Collections.sort(rl, new Comparator() { 181 | public int compare(Resolution a, Resolution b) { 182 | return a.portraitWidth - b.portraitWidth; 183 | } 184 | }); 185 | 186 | return rl.toArray(new Resolution[rl.size()]); 187 | } 188 | 189 | public String[] listAssetFiles(String base) { 190 | FileHandleResolver resolver = resResolver.getBaseResolver(); 191 | 192 | String list[] = null; 193 | 194 | if (Gdx.app.getType() != ApplicationType.Desktop) { 195 | 196 | FileHandle[] l = resolver.resolve(base).list(); 197 | list = new String[l.length]; 198 | 199 | for (int i = 0; i < l.length; i++) 200 | list[i] = l[i].name(); 201 | 202 | } else { 203 | // FOR DESKTOP 204 | URL u = EngineAssetManager.class.getResource("/" + resolver.resolve(base).path()); 205 | 206 | if (u != null && u.getProtocol().equals("jar")) { 207 | list = getFilesFromJar("/" + base); 208 | } else { 209 | String n = resolver.resolve(base).path(); 210 | 211 | if (u != null) 212 | n = u.getFile(); 213 | 214 | FileHandle f = null; 215 | 216 | try { 217 | f = Gdx.files.absolute(URLDecoder.decode(n, "UTF-8")); 218 | } catch (UnsupportedEncodingException e) { 219 | Gdx.app.error( TAG, "Error decoding URL", e); 220 | return new String[0]; 221 | } 222 | 223 | FileHandle[] l = f.list(); 224 | list = new String[l.length]; 225 | 226 | for (int i = 0; i < l.length; i++) 227 | list[i] = l[i].name(); 228 | } 229 | } 230 | 231 | return list; 232 | } 233 | 234 | /** 235 | * Returns the resolutions from a jar file. 236 | */ 237 | private String[] getFilesFromJar(String base) { 238 | URL dirURL = EngineAssetManager.class.getResource(base); 239 | 240 | Set result = new HashSet(); // avoid duplicates in case 241 | // it is a subdirectory 242 | 243 | if (dirURL.getProtocol().equals("jar")) { 244 | /* A JAR path */ 245 | String jarPath = dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")); 246 | JarFile jar; 247 | 248 | try { 249 | jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); 250 | } catch (Exception e) { 251 | Gdx.app.error( TAG, "Locating jar file", e); 252 | return result.toArray(new String[result.size()]); 253 | } 254 | 255 | Enumeration entries = jar.entries(); // gives ALL entries 256 | // in jar 257 | 258 | while (entries.hasMoreElements()) { 259 | String name = entries.nextElement().getName(); 260 | 261 | int start = name.indexOf('/'); 262 | int end = name.lastIndexOf('/'); 263 | 264 | if (start == end) 265 | continue; 266 | 267 | String entry = name.substring(start + 1, end); 268 | 269 | result.add(entry); 270 | } 271 | 272 | try { 273 | jar.close(); 274 | } catch (IOException e) { 275 | Gdx.app.error( TAG, "Closing jar file", e); 276 | return result.toArray(new String[result.size()]); 277 | } 278 | 279 | } 280 | 281 | return result.toArray(new String[result.size()]); 282 | } 283 | 284 | public FileHandle getUserFile(String filename) { 285 | FileHandle file = null; 286 | 287 | if (Gdx.app.getType() == ApplicationType.Desktop || Gdx.app.getType() == ApplicationType.Applet) { 288 | String dir = Config.getProperty(Config.TITLE_PROP, DESKTOP_PREFS_DIR); 289 | dir.replace(" ", ""); 290 | 291 | StringBuilder sb = new StringBuilder(); 292 | sb.append(".").append(dir).append("/").append(filename); 293 | 294 | if (System.getProperty("os.name").toLowerCase().contains("mac") 295 | && System.getenv("HOME").contains("Containers")) { 296 | 297 | file = Gdx.files.absolute(System.getenv("HOME") + "/" + sb.toString()); 298 | } else { 299 | 300 | file = Gdx.files.external(sb.toString()); 301 | } 302 | } else { 303 | file = Gdx.files.local(NOT_DESKTOP_PREFS_DIR + filename); 304 | } 305 | 306 | return file; 307 | } 308 | 309 | public FileHandle getUserFolder() { 310 | FileHandle file = null; 311 | 312 | if (Gdx.app.getType() == ApplicationType.Desktop || Gdx.app.getType() == ApplicationType.Applet) { 313 | String dir = Config.getProperty(Config.TITLE_PROP, DESKTOP_PREFS_DIR); 314 | dir.replace(" ", ""); 315 | 316 | StringBuilder sb = new StringBuilder("."); 317 | 318 | if (System.getProperty("os.name").toLowerCase().contains("mac") 319 | && System.getenv("HOME").contains("Containers")) { 320 | 321 | file = Gdx.files.absolute(System.getenv("HOME") + "/" + sb.append(dir).toString()); 322 | } else { 323 | 324 | file = Gdx.files.external(sb.append(dir).toString()); 325 | } 326 | 327 | } else { 328 | file = Gdx.files.local(NOT_DESKTOP_PREFS_DIR); 329 | } 330 | 331 | return file; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/assets/EngineResolutionFileResolver.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.assets; 17 | 18 | import com.badlogic.gdx.Gdx; 19 | import com.badlogic.gdx.assets.loaders.FileHandleResolver; 20 | import com.badlogic.gdx.assets.loaders.resolvers.ResolutionFileResolver.Resolution; 21 | import com.badlogic.gdx.files.FileHandle; 22 | import com.bladecoder.inkplayer.common.FileUtils; 23 | 24 | 25 | public class EngineResolutionFileResolver implements FileHandleResolver { 26 | private static final String TAG="EngineResolutionFileResolver"; 27 | 28 | private final FileHandleResolver baseResolver; 29 | private Resolution[] descriptors; 30 | 31 | private Resolution bestDesc; 32 | private String fixResolution; 33 | 34 | public EngineResolutionFileResolver(FileHandleResolver baseResolver) { 35 | this.baseResolver = baseResolver; 36 | } 37 | 38 | @Override 39 | public FileHandle resolve(String fileName) { 40 | FileHandle originalHandle = new FileHandle(fileName); 41 | FileHandle handle = baseResolver.resolve(resolve(originalHandle, bestDesc.folder)); 42 | 43 | if (!FileUtils.exists(handle)) 44 | handle = baseResolver.resolve(fileName); 45 | 46 | return handle; 47 | } 48 | 49 | protected String resolve(FileHandle originalHandle, String suffix) { 50 | StringBuilder stringBuilder = new StringBuilder(); 51 | 52 | stringBuilder.append(originalHandle.parent()); 53 | stringBuilder.append("/"); 54 | stringBuilder.append(suffix); 55 | stringBuilder.append("/"); 56 | stringBuilder.append(originalHandle.name()); 57 | 58 | return stringBuilder.toString(); 59 | } 60 | 61 | 62 | public boolean exists(String fileName) { 63 | FileHandle originalHandle = new FileHandle(fileName); 64 | FileHandle handle = baseResolver.resolve(resolve(originalHandle, bestDesc.folder)); 65 | 66 | if (FileUtils.exists(handle)) 67 | return true; 68 | 69 | handle = baseResolver.resolve(fileName); 70 | 71 | if (FileUtils.exists(handle)) 72 | return true; 73 | 74 | return false; 75 | } 76 | 77 | public FileHandleResolver getBaseResolver() { 78 | return baseResolver; 79 | } 80 | 81 | /** 82 | * Skip the resolution resolver. In Android the exists() method is expensive, so this 83 | * method save a exists call. 84 | */ 85 | public FileHandle baseResolve(String fileName) { 86 | return baseResolver.resolve(fileName); 87 | } 88 | 89 | public Resolution[] getResolutions() { 90 | return descriptors; 91 | } 92 | 93 | public Resolution getResolution() { 94 | return bestDesc; 95 | } 96 | 97 | public void selectResolution() { 98 | if(fixResolution != null) 99 | selectFixedResolution(); 100 | else 101 | selectBestResolution(); 102 | } 103 | 104 | private void selectBestResolution() { 105 | bestDesc = descriptors[0]; 106 | 107 | int width = Gdx.graphics.getWidth() > Gdx.graphics.getHeight() ?Gdx.graphics.getWidth():Gdx.graphics.getHeight(); 108 | 109 | int bestDist = Math.abs(width - bestDesc.portraitWidth); 110 | 111 | for (int i = 1; i < descriptors.length; i++) { 112 | Resolution other = descriptors[i]; 113 | int dist = Math.abs(width - other.portraitWidth); 114 | 115 | if (dist < bestDist) { 116 | bestDesc = descriptors[i]; 117 | bestDist = dist; 118 | } 119 | } 120 | } 121 | 122 | public void setResolutions(Resolution[] resolutions) { 123 | this.descriptors = resolutions; 124 | } 125 | 126 | /** 127 | * Sets a fixed prefix, disabling choosing the best resolution. 128 | */ 129 | private void selectFixedResolution() { 130 | for (int i = 0; i < descriptors.length; i++) { 131 | if(descriptors[i].folder.equals(fixResolution)) { 132 | bestDesc = descriptors[i]; 133 | return; 134 | } 135 | } 136 | 137 | if(bestDesc == null) { 138 | Gdx.app.error( TAG, "Requested resolution not found: " + fixResolution); 139 | selectBestResolution(); 140 | } 141 | } 142 | 143 | public void setFixedResolution(String suffix) { 144 | fixResolution = suffix; 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/common/Config.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.common; 17 | 18 | import java.util.Properties; 19 | 20 | import com.badlogic.gdx.Gdx; 21 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 22 | 23 | public class Config { 24 | private static final String TAG="ExternalFunctions"; 25 | 26 | public static final String TITLE_PROP="title"; 27 | public static final String LOAD_GAMESTATE_PROP="load_gamestate"; 28 | public static final String PLAY_RECORD_PROP="play_recording"; 29 | public static final String INITPATH_PROP="init_path"; 30 | public static final String FORCE_RES_PROP = "force_res"; 31 | public static final String DEBUG_PROP = "debug"; 32 | public static final String VERSION_PROP = "version"; 33 | public static final String BLADE_INK_VERSION_PROP = "bladeInkVersion"; 34 | public static final String AUTO_HIDE_TEXTS = "auto_hide_texts"; 35 | public static final String RESOLUTIONS = "resolutions"; 36 | public static final String STORY = "story"; 37 | 38 | public static final String PROPERTIES_FILENAME = "inkplayer.properties"; 39 | 40 | private static Properties config = null; 41 | 42 | public static String getProperty(String key, String defaultValue) { 43 | if(config == null) { 44 | load(); 45 | } 46 | 47 | return config.getProperty(key, defaultValue); 48 | } 49 | 50 | public static void load() { 51 | config = new Properties(); 52 | 53 | try { 54 | config.load(EngineAssetManager.getInstance().getAsset(PROPERTIES_FILENAME).reader()); 55 | } catch (Exception e) { 56 | Gdx.app.error( TAG, "ERROR LOADING PROPERTIES: " + e.getMessage()); 57 | } 58 | } 59 | 60 | public static boolean getProperty(String key, boolean defaultValue) { 61 | boolean result = false; 62 | 63 | try { 64 | result = Boolean.parseBoolean(getProperty(key, String.valueOf(defaultValue))); 65 | } catch (Exception e) { 66 | } 67 | 68 | return result; 69 | } 70 | 71 | public static int getProperty(String key, int defaultValue) { 72 | int result = 0; 73 | 74 | try { 75 | result = Integer.parseInt(getProperty(key, String.valueOf(defaultValue))); 76 | } catch (Exception e) { 77 | } 78 | 79 | return result; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/common/DPIUtils.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.common; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | 5 | public class DPIUtils { 6 | public final static float BASE_DPI = 160.0f; 7 | 8 | /** 9 | * Current DPI 10 | */ 11 | public final static float DPI = BASE_DPI * Gdx.graphics.getDensity(); 12 | 13 | /** 14 | * The Google recommendations are 48 dp -> 9mm for touchable elements 15 | */ 16 | public final static float TOUCH_MIN_SIZE = 48 * Gdx.graphics.getDensity(); 17 | 18 | /** 19 | * The Google recommendations of space between UI objects is 8 dp 20 | */ 21 | public final static float UI_SPACE = 8 * Gdx.graphics.getDensity(); 22 | 23 | /** 24 | * The Google recommendations of space from bottom or top is 16 dp 25 | */ 26 | public final static float MARGIN_SIZE = 16 * Gdx.graphics.getDensity(); 27 | 28 | /** 29 | * The Google recommendations are 56 dp for action buttons 30 | */ 31 | public final static float BUTTON_SIZE = 56 * Gdx.graphics.getDensity(); 32 | 33 | /** 34 | * The Google recommendations are 24 dp for icons inside action buttons 35 | */ 36 | public final static float ICON_SIZE = 24 * Gdx.graphics.getDensity(); 37 | 38 | 39 | /** 40 | * The Google recommendations are 8 dp for space between ui elements 41 | */ 42 | public final static float SPACING = 8 * Gdx.graphics.getDensity(); 43 | 44 | /** 45 | * The screen height in DP 46 | */ 47 | public final static float SCREEN_HEIGHT_DP = Gdx.graphics.getHeight() 48 | / Gdx.graphics.getDensity(); 49 | 50 | public final static float NORMAL_MULTIPLIER = 1.0f; // 3-5" 51 | public final static float LARGE_MULTIPLIER = 1.5f; // 5-7" 52 | public final static float XLARGE_MULTIPLIER = 2f; // 8-10" 53 | public final static float XXLARGE_MULTIPLIER = 2.5f; // > 10" 54 | 55 | /** 56 | * Calcs the button size based in screen size 57 | * 58 | * @return The recommended size in pixels 59 | */ 60 | public static float getPrefButtonSize() { 61 | return getSizeMultiplier() * BUTTON_SIZE; 62 | } 63 | 64 | /** 65 | * Calcs the minimum size based in screen size 66 | * 67 | * @return The recommended size in pixels 68 | */ 69 | public static float getTouchMinSize() { 70 | return getSizeMultiplier() * TOUCH_MIN_SIZE; 71 | } 72 | 73 | /** 74 | * Calcs the margin size based in screen size 75 | * 76 | * @return The recommended size in pixels 77 | */ 78 | public static float getMarginSize() { 79 | return getSizeMultiplier() * MARGIN_SIZE; 80 | } 81 | 82 | /** 83 | * Calcs the space between ui elements based in screen size 84 | * 85 | * @return The recommended size in pixels 86 | */ 87 | public static float getSpacing() { 88 | return getSizeMultiplier() * SPACING; 89 | } 90 | 91 | public static float getSizeMultiplier() { 92 | int p = Gdx.graphics.getWidth() > Gdx.graphics.getHeight()? Gdx.graphics.getWidth():Gdx.graphics.getHeight(); 93 | 94 | float inches = pixelsToInches(p); 95 | float s = inches / 6f; 96 | 97 | return Math.max(1.0f, s); 98 | 99 | } 100 | 101 | public static int dpToPixels(int dp) { 102 | return (int) (dp * Gdx.graphics.getDensity()); 103 | } 104 | 105 | public static int pixelsToDP(int pixels) { 106 | return (int) (pixels / Gdx.graphics.getDensity()); 107 | } 108 | 109 | public static float pixelsToInches(int pixels) { 110 | return (float)pixels / DPI; 111 | } 112 | 113 | public static float ptToPixels(float pts) { 114 | return pts * 72 / DPI; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/common/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.common; 2 | 3 | import com.badlogic.gdx.Application.ApplicationType; 4 | import com.badlogic.gdx.Gdx; 5 | import com.badlogic.gdx.files.FileHandle; 6 | 7 | public class FileUtils { 8 | /** 9 | * For android, the exists method is very slow, this is a fast 10 | * implementation 11 | * 12 | * @return true if file exists 13 | */ 14 | public static boolean exists(FileHandle fh) { 15 | 16 | if (Gdx.app.getType() == ApplicationType.Android) { 17 | try { 18 | fh.read().close(); 19 | return true; 20 | } catch (Exception e) { 21 | return false; 22 | } 23 | } 24 | 25 | return fh.exists(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/common/RectangleRenderer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.common; 17 | 18 | import com.badlogic.gdx.graphics.Color; 19 | import com.badlogic.gdx.graphics.Pixmap; 20 | import com.badlogic.gdx.graphics.Texture; 21 | import com.badlogic.gdx.graphics.g2d.Batch; 22 | 23 | public final class RectangleRenderer { 24 | private static Texture texture; 25 | 26 | private static Texture makePixel() { 27 | Texture _temp; 28 | Pixmap p = new Pixmap(1, 1, Pixmap.Format.RGBA8888); 29 | p.setColor(Color.WHITE); 30 | p.fillRectangle(0, 0, 1, 1); 31 | _temp = new Texture(p, true); 32 | p.dispose(); 33 | return _temp; 34 | } 35 | 36 | public static void draw(Batch batch, float posX, float posY, 37 | float width, float height, Color color) { 38 | 39 | if(texture == null) texture = makePixel(); 40 | 41 | Color tmp = batch.getColor(); 42 | batch.setColor(color); 43 | batch.draw(texture, posX, posY, 0, 44 | 0, width, height, 1, 1, 0, 0, 0, 1, 1, false, false); 45 | 46 | batch.setColor(tmp); 47 | } 48 | 49 | public static void dispose() { 50 | if(texture!=null) texture.dispose(); 51 | texture = null; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/i18n/I18N.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.i18n; 17 | 18 | import java.util.Locale; 19 | import java.util.ResourceBundle; 20 | 21 | import com.badlogic.gdx.Gdx; 22 | 23 | public class I18N { 24 | private static final String TAG="I18N"; 25 | 26 | public static final char PREFIX = '@'; 27 | public static final String ENCODING = "UTF-8"; 28 | 29 | private static Locale locale = Locale.getDefault(); 30 | 31 | public static ResourceBundle getBundle(String filename, boolean clearCache) { 32 | ResourceBundle rb = null; 33 | 34 | try { 35 | if(clearCache) 36 | ResourceBundle.clearCache(); 37 | 38 | rb = ResourceBundle.getBundle(filename, locale, new I18NControl(ENCODING)); 39 | } catch (Exception e) { 40 | Gdx.app.error( TAG, "ERROR LOADING BUNDLE: " + filename); 41 | } 42 | 43 | return rb; 44 | } 45 | 46 | public static void setLocale(Locale l) { 47 | locale = l; 48 | } 49 | 50 | public static Locale getCurrentLocale() { 51 | return locale; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/i18n/I18NControl.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.i18n; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.InputStreamReader; 21 | import java.util.Locale; 22 | import java.util.PropertyResourceBundle; 23 | import java.util.ResourceBundle; 24 | import java.util.ResourceBundle.Control; 25 | 26 | import com.badlogic.gdx.files.FileHandle; 27 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 28 | import com.bladecoder.inkplayer.common.FileUtils; 29 | 30 | public class I18NControl extends Control { 31 | 32 | String encoding; 33 | 34 | public I18NControl(String encoding) { 35 | this.encoding = encoding; 36 | } 37 | 38 | @Override 39 | public ResourceBundle newBundle(String baseName, Locale locale, String format, 40 | ClassLoader loader, boolean reload) throws IllegalAccessException, 41 | InstantiationException, IOException { 42 | String bundleName = toBundleName(baseName, locale); 43 | String resourceName = toResourceName(bundleName, "properties"); 44 | ResourceBundle bundle = null; 45 | InputStream inputStream = null; 46 | 47 | FileHandle fileHandle = EngineAssetManager.getInstance().getAsset(resourceName); 48 | 49 | if (FileUtils.exists(fileHandle)) { 50 | try { 51 | // inputStream = loader.getResourceAsStream(resourceName); 52 | inputStream = fileHandle.read(); 53 | bundle = new PropertyResourceBundle(new InputStreamReader(inputStream, encoding)); 54 | } finally { 55 | if (inputStream != null) 56 | inputStream.close(); 57 | } 58 | } 59 | return bundle; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/AppScreen.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.ui; 2 | 3 | import com.badlogic.gdx.Screen; 4 | 5 | public interface AppScreen extends Screen { 6 | /** 7 | * UI Injector 8 | * 9 | * @param ui 10 | */ 11 | public void setUI(UI ui); 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/BladeSkin.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.ui; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | 6 | import com.badlogic.gdx.Gdx; 7 | import com.badlogic.gdx.files.FileHandle; 8 | import com.badlogic.gdx.graphics.Color; 9 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 10 | import com.badlogic.gdx.graphics.g2d.TextureAtlas; 11 | import com.badlogic.gdx.graphics.g2d.TextureRegion; 12 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; 13 | import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; 14 | import com.badlogic.gdx.scenes.scene2d.ui.Button; 15 | import com.badlogic.gdx.scenes.scene2d.ui.CheckBox; 16 | import com.badlogic.gdx.scenes.scene2d.ui.ImageButton; 17 | import com.badlogic.gdx.scenes.scene2d.ui.ImageTextButton; 18 | import com.badlogic.gdx.scenes.scene2d.ui.Label; 19 | import com.badlogic.gdx.scenes.scene2d.ui.List; 20 | import com.badlogic.gdx.scenes.scene2d.ui.ProgressBar; 21 | import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; 22 | import com.badlogic.gdx.scenes.scene2d.ui.SelectBox; 23 | import com.badlogic.gdx.scenes.scene2d.ui.Skin; 24 | import com.badlogic.gdx.scenes.scene2d.ui.Slider; 25 | import com.badlogic.gdx.scenes.scene2d.ui.SplitPane; 26 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton; 27 | import com.badlogic.gdx.scenes.scene2d.ui.TextField; 28 | import com.badlogic.gdx.scenes.scene2d.ui.TextTooltip; 29 | import com.badlogic.gdx.scenes.scene2d.ui.Touchpad; 30 | import com.badlogic.gdx.scenes.scene2d.ui.Tree; 31 | import com.badlogic.gdx.scenes.scene2d.ui.Window; 32 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 33 | import com.badlogic.gdx.scenes.scene2d.utils.NinePatchDrawable; 34 | import com.badlogic.gdx.scenes.scene2d.utils.SpriteDrawable; 35 | import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; 36 | import com.badlogic.gdx.scenes.scene2d.utils.TiledDrawable; 37 | import com.badlogic.gdx.utils.Json; 38 | import com.badlogic.gdx.utils.Json.ReadOnlySerializer; 39 | import com.badlogic.gdx.utils.JsonValue; 40 | import com.badlogic.gdx.utils.SerializationException; 41 | import com.badlogic.gdx.utils.reflect.ClassReflection; 42 | import com.badlogic.gdx.utils.reflect.ReflectionException; 43 | import com.bladecoder.inkplayer.common.DPIUtils; 44 | import com.bladecoder.inkplayer.common.FileUtils; 45 | 46 | /** 47 | * Custom Skin class to add TTF font support 48 | * 49 | * @author rgarcia 50 | */ 51 | public class BladeSkin extends Skin { 52 | private static final String TAG="BladeSkin"; 53 | 54 | public BladeSkin(FileHandle skinFile) { 55 | super(skinFile); 56 | } 57 | 58 | public BladeSkin(FileHandle skinFile, TextureAtlas atlas) { 59 | super(skinFile, atlas); 60 | } 61 | 62 | /** 63 | * Override BitmapFont.class serializer to support TTF fonts 64 | * 65 | * Also add the size parameter to support bitmaps font size in pt 66 | */ 67 | @Override 68 | protected Json getJsonLoader(final FileHandle skinFile) { 69 | Json json = super.getJsonLoader(skinFile); 70 | 71 | final Skin skin = this; 72 | 73 | json.setSerializer(Skin.class, new ReadOnlySerializer() { 74 | public Skin read (Json json, JsonValue typeToValueMap, @SuppressWarnings("rawtypes") Class ignored) { 75 | for (JsonValue valueMap = typeToValueMap.child; valueMap != null; valueMap = valueMap.next) { 76 | try { 77 | Class type = json.getClass(valueMap.name()); 78 | if (type == null) type = ClassReflection.forName(valueMap.name()); 79 | readNamedObjects(json, type, valueMap); 80 | } catch (ReflectionException ex) { 81 | throw new SerializationException(ex); 82 | } 83 | } 84 | return skin; 85 | } 86 | 87 | private void readNamedObjects (Json json, Class type, JsonValue valueMap) { 88 | Class addType = type == TintedDrawable.class ? Drawable.class : type; 89 | for (JsonValue valueEntry = valueMap.child; valueEntry != null; valueEntry = valueEntry.next) { 90 | Object object = json.readValue(type, valueEntry); 91 | if (object == null) continue; 92 | try { 93 | add(valueEntry.name, object, addType); 94 | if (addType != Drawable.class && ClassReflection.isAssignableFrom(Drawable.class, addType)) 95 | add(valueEntry.name, object, Drawable.class); 96 | } catch (Exception ex) { 97 | throw new SerializationException( 98 | "Error reading " + ClassReflection.getSimpleName(type) + ": " + valueEntry.name, ex); 99 | } 100 | } 101 | } 102 | }); 103 | 104 | json.setSerializer(BitmapFont.class, new ReadOnlySerializer() { 105 | public BitmapFont read(Json json, JsonValue jsonData, @SuppressWarnings("rawtypes") Class type) { 106 | String path = json.readValue("file", String.class, jsonData); 107 | int scaledSize = json.readValue("scaledSize", int.class, -1, jsonData); 108 | Boolean flip = json.readValue("flip", Boolean.class, false, jsonData); 109 | int size = json.readValue("size", int.class, -1, jsonData); 110 | 111 | FileHandle fontFile = skinFile.parent().child(path); 112 | if (!FileUtils.exists(fontFile)) 113 | fontFile = Gdx.files.internal(path); 114 | 115 | if (!FileUtils.exists(fontFile)) 116 | throw new SerializationException("Font file not found: " + fontFile); 117 | 118 | BitmapFont font; 119 | 120 | if (fontFile.extension().equalsIgnoreCase("ttf")) { 121 | 122 | if (size == -1) 123 | throw new SerializationException("'size' mandatory parameter for .ttf fonts"); 124 | 125 | long initTime = System.currentTimeMillis(); 126 | 127 | FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile); 128 | FreeTypeFontParameter parameter = new FreeTypeFontParameter(); 129 | parameter.size = (int) (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()); 130 | parameter.incremental = json.readValue("incremental", boolean.class, true, jsonData); 131 | parameter.borderWidth = json.readValue("borderWidth", int.class, 0, jsonData); 132 | parameter.borderColor = json.readValue("borderColor", Color.class, Color.BLACK, jsonData); 133 | parameter.borderStraight = json.readValue("borderStraight", boolean.class, false, jsonData); 134 | parameter.shadowOffsetX = json.readValue("shadowOffsetX", int.class, 0, jsonData); 135 | parameter.shadowOffsetY = json.readValue("shadowOffsetY", int.class, 0, jsonData); 136 | parameter.shadowColor = json.readValue("shadowColor", Color.class, Color.BLACK, jsonData); 137 | if(parameter.incremental) 138 | parameter.characters = ""; 139 | 140 | // parameter.hinting = Hinting.Medium; 141 | 142 | // parameter.mono = false; 143 | 144 | font = generator.generateFont(parameter); 145 | 146 | Gdx.app.debug( TAG, path + " TIME (ms): " + (System.currentTimeMillis() - initTime)); 147 | 148 | // TODO Dispose all generators. 149 | 150 | } else { 151 | 152 | // Use a region with the same name as the font, else use a 153 | // PNG file in the same directory as the FNT file. 154 | String regionName = fontFile.nameWithoutExtension(); 155 | try { 156 | TextureRegion region = skin.optional(regionName, TextureRegion.class); 157 | if (region != null) 158 | font = new BitmapFont(fontFile, region, flip); 159 | else { 160 | FileHandle imageFile = fontFile.parent().child(regionName + ".png"); 161 | if (FileUtils.exists(imageFile)) 162 | font = new BitmapFont(fontFile, imageFile, flip); 163 | else 164 | font = new BitmapFont(fontFile, flip); 165 | } 166 | // Scaled size is the desired cap height to scale the 167 | // font to. 168 | if (scaledSize != -1) 169 | font.getData().setScale(scaledSize / font.getCapHeight()); 170 | else if (size != -1) // TODO set size in points (dpi 171 | // independent) 172 | font.getData().setScale( 173 | (DPIUtils.dpToPixels(size) * DPIUtils.getSizeMultiplier()) / font.getCapHeight()); 174 | } catch (RuntimeException ex) { 175 | throw new SerializationException("Error loading bitmap font: " + fontFile, ex); 176 | } 177 | } 178 | 179 | font.getData().markupEnabled = true; 180 | 181 | return font; 182 | } 183 | }); 184 | 185 | for (Class cls : TAGGED_STYLES){ 186 | json.addClassTag(cls.getSimpleName(), cls); 187 | } 188 | 189 | return json; 190 | } 191 | 192 | private static final Class[] AUTO_TAGGED_STYLES = { 193 | BitmapFont.class, Color.class, TintedDrawable.class, 194 | NinePatchDrawable.class, SpriteDrawable.class, TextureRegionDrawable.class, TiledDrawable.class, 195 | Button.ButtonStyle.class, CheckBox.CheckBoxStyle.class, ImageButton.ImageButtonStyle.class, 196 | ImageTextButton.ImageTextButtonStyle.class, Label.LabelStyle.class, List.ListStyle.class, 197 | ProgressBar.ProgressBarStyle.class, ScrollPane.ScrollPaneStyle.class, SelectBox.SelectBoxStyle.class, 198 | Slider.SliderStyle.class, SplitPane.SplitPaneStyle.class, TextButton.TextButtonStyle.class, 199 | TextField.TextFieldStyle.class, TextTooltip.TextTooltipStyle.class, Touchpad.TouchpadStyle.class, 200 | Tree.TreeStyle.class, Window.WindowStyle.class 201 | }; 202 | 203 | private final static ArrayList> TAGGED_STYLES = new ArrayList>(Arrays.asList(AUTO_TAGGED_STYLES)); 204 | 205 | public static final void addStyleTag(Class tag) { 206 | TAGGED_STYLES.add(tag); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/ChoicesUI.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.ui; 17 | 18 | import java.util.List; 19 | 20 | import com.badlogic.gdx.graphics.Color; 21 | import com.badlogic.gdx.scenes.scene2d.Actor; 22 | import com.badlogic.gdx.scenes.scene2d.Event; 23 | import com.badlogic.gdx.scenes.scene2d.EventListener; 24 | import com.badlogic.gdx.scenes.scene2d.InputEvent; 25 | import com.badlogic.gdx.scenes.scene2d.actions.Actions; 26 | import com.badlogic.gdx.scenes.scene2d.ui.Button; 27 | import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; 28 | import com.badlogic.gdx.scenes.scene2d.ui.Table; 29 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton; 30 | import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle; 31 | import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; 32 | import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; 33 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 34 | import com.badlogic.gdx.utils.Align; 35 | import com.bladecoder.inkplayer.common.DPIUtils; 36 | 37 | public class ChoicesUI extends ScrollPane { 38 | public static final String DIALOG_END_COMMAND = "dialog_end"; 39 | 40 | private final ChoicesUIStyle style; 41 | 42 | // private Recorder recorder; 43 | 44 | private final Table panel; 45 | 46 | private final Button up; 47 | private final Button down; 48 | 49 | private final StoryScreen sc; 50 | 51 | public ChoicesUI(StoryScreen sc) { 52 | super(new Table(sc.getUI().getSkin()), sc.getUI().getSkin()); 53 | 54 | UI ui = sc.getUI(); 55 | this.sc = sc; 56 | setFadeScrollBars(true); 57 | setOverscroll(false, false); 58 | 59 | up = new Button(ui.getSkin(), "dialog-up"); 60 | down = new Button(ui.getSkin(), "dialog-down"); 61 | 62 | panel = (Table) getActor(); 63 | style = ui.getSkin().get(ChoicesUIStyle.class); 64 | // this.recorder = ui.getRecorder(); 65 | 66 | if (style.background != null) 67 | panel.setBackground(style.background); 68 | 69 | panel.top().left(); 70 | panel.pad(DPIUtils.getMarginSize()); 71 | 72 | setVisible(false); 73 | panel.defaults().expandX().fillX().top().left().padBottom(DPIUtils.getSpacing()); 74 | 75 | addListener(new EventListener() { 76 | 77 | @Override 78 | public boolean handle(Event event) { 79 | if (isScrollY()) { 80 | 81 | if (getScrollPercentY() > 0f && !up.isVisible()) { 82 | up.setVisible(true); 83 | } else if (getScrollPercentY() == 0f && up.isVisible()) { 84 | up.setVisible(false); 85 | } 86 | 87 | if (getScrollPercentY() < 1f && !down.isVisible()) { 88 | down.setVisible(true); 89 | } else if (getScrollPercentY() == 1f && down.isVisible()) { 90 | down.setVisible(false); 91 | } 92 | } 93 | 94 | return false; 95 | } 96 | }); 97 | 98 | up.addListener(new ChangeListener() { 99 | @Override 100 | public void changed(ChangeEvent event, Actor actor) { 101 | setScrollY(getScrollY() - DPIUtils.getPrefButtonSize()); 102 | } 103 | }); 104 | 105 | down.addListener(new ChangeListener() { 106 | @Override 107 | public void changed(ChangeEvent event, Actor actor) { 108 | setScrollY(getScrollY() + DPIUtils.getPrefButtonSize()); 109 | } 110 | }); 111 | } 112 | 113 | public void show(List choices) { 114 | setVisible(true); 115 | 116 | if (choices.isEmpty()) { 117 | setVisible(false); 118 | return; 119 | 120 | } else if (style.autoselect && choices.size() == 1) { 121 | // If only has one option, autoselect it 122 | select(0); 123 | setVisible(false); 124 | return; 125 | } 126 | 127 | panel.clear(); 128 | setScrollPercentY(0); 129 | 130 | for (int i = 0; i < choices.size(); i++) { 131 | String str = choices.get(i); 132 | 133 | TextButton ob = new TextButton(str, style.textButtonStyle); 134 | ob.setUserObject(i); 135 | panel.row(); 136 | panel.add(ob); 137 | ob.getLabel().setWrap(true); 138 | ob.getLabel().setAlignment(Align.left); 139 | 140 | ob.addListener(new ClickListener() { 141 | public void clicked(InputEvent event, float x, float y) { 142 | int i = (Integer) event.getListenerActor().getUserObject(); 143 | 144 | select(i); 145 | } 146 | }); 147 | } 148 | 149 | setWidth(getStage().getViewport().getScreenWidth()); 150 | setHeight(Math.min(panel.getPrefHeight(), getStage().getViewport().getScreenHeight() / 2f)); 151 | 152 | float size = DPIUtils.getPrefButtonSize() * .8f; 153 | float margin = DPIUtils.getSpacing(); 154 | 155 | getStage().addActor(up); 156 | up.setSize(size, size); 157 | up.setPosition(getX() + getWidth() - size - margin, getY() + getHeight() - margin - size); 158 | 159 | getStage().addActor(down); 160 | down.setSize(size, size); 161 | down.setPosition(getX() + getWidth() - size - margin, getY() + margin); 162 | 163 | layout(); 164 | 165 | setUpDownVisibility(); 166 | 167 | if (down.isVisible()) { 168 | down.addAction( 169 | Actions.repeat(3, Actions.sequence(Actions.moveBy(0, 15, .08f), Actions.moveBy(0, -15, .08f)))); 170 | } 171 | 172 | getStage().setScrollFocus(this); 173 | } 174 | 175 | public void setUpDownVisibility() { 176 | if (isScrollY() && getMaxY() > DPIUtils.getMarginSize()) { 177 | 178 | if (getScrollPercentY() > 0f && !up.isVisible()) { 179 | up.setVisible(true); 180 | } else if (getScrollPercentY() == 0f && up.isVisible()) { 181 | up.setVisible(false); 182 | } 183 | 184 | if (getScrollPercentY() < 1f && !down.isVisible()) { 185 | down.setVisible(true); 186 | } else if (getScrollPercentY() == 1f && down.isVisible()) { 187 | down.setVisible(false); 188 | } 189 | } else { 190 | up.setVisible(false); 191 | down.setVisible(false); 192 | } 193 | } 194 | 195 | public void resize(int width, int height) { 196 | setWidth(width); 197 | setHeight(Math.min(panel.getHeight(), height / 2f)); 198 | 199 | float size = DPIUtils.getPrefButtonSize() * .8f; 200 | float margin = DPIUtils.getSpacing(); 201 | 202 | up.setSize(size, size); 203 | up.setPosition(getX() + getWidth() - size - margin, getY() + getHeight() - margin - size); 204 | 205 | down.setSize(size, size); 206 | down.setPosition(getX() + getWidth() - size - margin, getY() + margin); 207 | 208 | } 209 | 210 | private void select(final int i) { 211 | // RECORD 212 | /* 213 | * if (recorder.isRecording()) { recorder.add(i); } 214 | */ 215 | 216 | up.remove(); 217 | down.remove(); 218 | 219 | addAction(Actions.sequence(Actions.fadeOut(1f), Actions.run(new Runnable() { 220 | 221 | @Override 222 | public void run() { 223 | setVisible(false); 224 | setColor(Color.WHITE); 225 | sc.selectChoice(i); 226 | } 227 | }))); 228 | 229 | } 230 | 231 | /** The style for the DialogUI */ 232 | static public class ChoicesUIStyle { 233 | /** Optional. */ 234 | public Drawable background; 235 | 236 | public TextButtonStyle textButtonStyle; 237 | 238 | // If only one option is visible, auto select it. 239 | public boolean autoselect = true; 240 | 241 | public ChoicesUIStyle() { 242 | } 243 | 244 | public ChoicesUIStyle(ChoicesUIStyle style) { 245 | background = style.background; 246 | textButtonStyle = style.textButtonStyle; 247 | autoselect = style.autoselect; 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/CreditsScreen.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.ui; 17 | 18 | import java.io.BufferedReader; 19 | import java.text.MessageFormat; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Locale; 24 | import java.util.Map; 25 | 26 | import com.badlogic.gdx.Gdx; 27 | import com.badlogic.gdx.Input; 28 | import com.badlogic.gdx.InputAdapter; 29 | import com.badlogic.gdx.InputProcessor; 30 | import com.badlogic.gdx.ScreenAdapter; 31 | import com.badlogic.gdx.audio.Music; 32 | import com.badlogic.gdx.graphics.Color; 33 | import com.badlogic.gdx.graphics.GL20; 34 | import com.badlogic.gdx.graphics.Texture; 35 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 36 | import com.badlogic.gdx.graphics.g2d.BitmapFont; 37 | import com.badlogic.gdx.graphics.g2d.GlyphLayout; 38 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 39 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 40 | import com.badlogic.gdx.utils.Align; 41 | import com.badlogic.gdx.utils.viewport.ScreenViewport; 42 | import com.badlogic.gdx.utils.viewport.Viewport; 43 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 44 | import com.bladecoder.inkplayer.common.DPIUtils; 45 | import com.bladecoder.inkplayer.ui.UI.Screens; 46 | 47 | public class CreditsScreen extends ScreenAdapter implements AppScreen { 48 | private static final String TAG="CreditsScreen"; 49 | 50 | private static final String CREDITS_FILENAME = "ui/credits"; 51 | private static final float SPEED = 10 * DPIUtils.getSpacing(); // px/sec. 52 | 53 | // title and texts pair sequence 54 | private final List credits = new ArrayList<>(); 55 | 56 | private CreditScreenStyle style; 57 | 58 | private int stringHead = 0; 59 | private float scrollY = 0; 60 | 61 | private UI ui; 62 | 63 | private Music music; 64 | 65 | private final Map images = new HashMap<>(); 66 | 67 | private Viewport viewport; 68 | 69 | private final GlyphLayout layout = new GlyphLayout(); 70 | 71 | private final InputProcessor inputProcessor = new InputAdapter() { 72 | @Override 73 | public boolean keyUp(int keycode) { 74 | if (keycode == Input.Keys.ESCAPE 75 | || keycode == Input.Keys.BACK) 76 | ui.setCurrentScreen(Screens.MENU_SCREEN); 77 | 78 | return true; 79 | } 80 | 81 | @Override 82 | public boolean touchUp(int screenX, int screenY, int pointer, int button) { 83 | ui.setCurrentScreen(Screens.MENU_SCREEN); 84 | return true; 85 | } 86 | }; 87 | 88 | @Override 89 | public void render(float delta) { 90 | Gdx.graphics.requestRendering(); 91 | 92 | final SpriteBatch batch = ui.getBatch(); 93 | final int width = (int) viewport.getWorldWidth(); 94 | final int height = (int) viewport.getWorldHeight(); 95 | 96 | Gdx.gl.glClearColor(0, 0, 0, 1); 97 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 98 | 99 | batch.setProjectionMatrix(viewport.getCamera().combined); 100 | batch.begin(); 101 | 102 | if (style.background != null) { 103 | style.background.draw(batch, 0, 0, width, height); 104 | } 105 | 106 | scrollY += delta * SPEED * EngineAssetManager.getInstance().getScale(); 107 | 108 | float y = scrollY; 109 | 110 | if (stringHead >= credits.size()) 111 | ui.setCurrentScreen(Screens.MENU_SCREEN); 112 | 113 | for (int i = stringHead; i < credits.size(); i++) { 114 | String s = credits.get(i); 115 | 116 | char type = 'c'; // types are 'c' -> credit, 't' -> title, 'i' -> image, 's' -> space, 'm' -> music 117 | 118 | if (s.indexOf('#') != -1) { 119 | type = s.charAt(0); 120 | s = s.substring(2); 121 | } 122 | 123 | switch (type) { 124 | case 't': 125 | y = processCreditTitle(batch, width, height, y, i, s); 126 | break; 127 | case 'i': 128 | y = processCreditImage(batch, width, height, y, i, s); 129 | break; 130 | case 's': 131 | y = processCreditSpace(height, y, i, s); 132 | break; 133 | case 'm': 134 | processCreditMusic(s); 135 | credits.remove(i); 136 | i--; 137 | break; 138 | default: 139 | y = processCreditDefault(batch, width, height, y, i, s); 140 | break; 141 | } 142 | 143 | if (y < 0) { 144 | break; 145 | } 146 | } 147 | 148 | batch.end(); 149 | } 150 | 151 | private float processCreditTitle(SpriteBatch batch, int width, int height, float y, int i, String s) { 152 | final float lineHeight = style.titleFont.getLineHeight(); 153 | y -= lineHeight * 2; 154 | 155 | drawCenteredScreenX(batch, style.titleFont, s, y, width); 156 | y -= lineHeight; 157 | 158 | if (y > height + lineHeight) { 159 | stringHead = i + 1; 160 | scrollY -= lineHeight; 161 | scrollY -= lineHeight * 2; 162 | } 163 | return y; 164 | } 165 | 166 | private float processCreditImage(SpriteBatch batch, int width, int height, float y, int i, String s) { 167 | Texture img = images.get(s); 168 | final int lineHeight = img.getHeight(); 169 | batch.draw(img, (width - img.getWidth()) / 2, y - lineHeight); 170 | 171 | y -= lineHeight; 172 | 173 | if (y > height) { 174 | stringHead = i + 1; 175 | scrollY -= lineHeight; 176 | } 177 | return y; 178 | } 179 | 180 | private float processCreditSpace(int height, float y, int i, String s) { 181 | int lineHeight = (int) (Integer.valueOf(s) * EngineAssetManager.getInstance().getScale()); 182 | y -= lineHeight; 183 | 184 | if (y > height) { 185 | stringHead = i + 1; 186 | scrollY -= lineHeight; 187 | } 188 | return y; 189 | } 190 | 191 | private void processCreditMusic(String s) { 192 | if (music != null) 193 | music.dispose(); 194 | 195 | music = Gdx.audio.newMusic(EngineAssetManager.getInstance().getAsset(s)); 196 | 197 | try { 198 | music.play(); 199 | } catch(Exception e) { 200 | // sometimes the play method fails on desktop. 201 | Gdx.app.error( TAG, "Error Playing music: " + s, e); 202 | } 203 | } 204 | 205 | private float processCreditDefault(SpriteBatch batch, int width, int height, float y, int i, String s) { 206 | drawCenteredScreenX(batch, style.font, s, y, width); 207 | 208 | final float lineHeight = style.font.getLineHeight(); 209 | y -= lineHeight; 210 | 211 | if (y > height + lineHeight) { 212 | stringHead = i + 1; 213 | scrollY -= lineHeight; 214 | } 215 | return y; 216 | } 217 | 218 | @Override 219 | public void resize(int width, int height) { 220 | viewport.update(width, height, true); 221 | } 222 | 223 | @Override 224 | public void dispose() { 225 | for (Texture t : images.values()) 226 | t.dispose(); 227 | 228 | images.clear(); 229 | credits.clear(); 230 | 231 | if (music != null) { 232 | music.stop(); 233 | music.dispose(); 234 | music = null; 235 | } 236 | } 237 | 238 | private void retrieveAssets() { 239 | style = ui.getSkin().get(CreditScreenStyle.class); 240 | 241 | final Locale locale = Locale.getDefault(); 242 | 243 | String localeFilename = MessageFormat.format("{0}_{1}.txt", CREDITS_FILENAME, locale.getLanguage()); 244 | 245 | if (!EngineAssetManager.getInstance().assetExists(localeFilename)) 246 | localeFilename = MessageFormat.format("{0}.txt", CREDITS_FILENAME); 247 | 248 | BufferedReader reader = EngineAssetManager.getInstance().getAsset(localeFilename).reader(4096, "utf-8"); 249 | 250 | try { 251 | String line; 252 | while ((line = reader.readLine()) != null) { 253 | credits.add(line); 254 | } 255 | } catch (Exception e) { 256 | Gdx.app.error( TAG, e.getMessage()); 257 | 258 | ui.setCurrentScreen(Screens.MENU_SCREEN); 259 | } 260 | 261 | scrollY += style.titleFont.getLineHeight(); 262 | 263 | // Load IMAGES 264 | for (String s : credits) { 265 | if (s.indexOf('#') != -1 && s.charAt(0) == 'i') { 266 | s = s.substring(2); 267 | 268 | Texture tex = new Texture(EngineAssetManager.getInstance().getResAsset("ui/" + s)); 269 | tex.setFilter(TextureFilter.Linear, TextureFilter.Linear); 270 | 271 | images.put(s, tex); 272 | } 273 | } 274 | } 275 | 276 | @Override 277 | public void show() { 278 | retrieveAssets(); 279 | Gdx.input.setInputProcessor(inputProcessor); 280 | 281 | // int wWidth = EngineAssetManager.getInstance().getResolution().portraitWidth; 282 | // int wHeight = EngineAssetManager.getInstance().getResolution().portraitHeight; 283 | 284 | // viewport = new ExtendViewport(wWidth, wHeight); 285 | viewport = new ScreenViewport(); 286 | 287 | stringHead = 0; 288 | scrollY = 0; 289 | } 290 | 291 | @Override 292 | public void hide() { 293 | dispose(); 294 | } 295 | 296 | @Override 297 | public void setUI(UI ui) { 298 | this.ui = ui; 299 | } 300 | 301 | public void drawCenteredScreenX(SpriteBatch batch, BitmapFont font, CharSequence str, float y, int viewportWidth) { 302 | float x = 0; 303 | 304 | layout.setText(font, str, Color.WHITE, viewportWidth, Align.center, true); 305 | 306 | //x = (viewportWidth - layout.width)/2; 307 | 308 | font.draw(batch, layout, x, y); 309 | } 310 | 311 | /** 312 | * The style for the CreditsScreen 313 | */ 314 | public static class CreditScreenStyle { 315 | /** 316 | * Optional. 317 | */ 318 | public Drawable background; 319 | /** 320 | * if 'bg' not specified try to load the bgFile 321 | */ 322 | public String bgFile; 323 | public BitmapFont titleFont; 324 | public BitmapFont font; 325 | 326 | public CreditScreenStyle() { 327 | } 328 | 329 | public CreditScreenStyle(CreditScreenStyle style) { 330 | background = style.background; 331 | bgFile = style.bgFile; 332 | titleFont = style.titleFont; 333 | font = style.font; 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/InitScreen.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.ui; 17 | 18 | import com.badlogic.gdx.Gdx; 19 | import com.badlogic.gdx.ScreenAdapter; 20 | import com.badlogic.gdx.graphics.Color; 21 | import com.badlogic.gdx.graphics.GL20; 22 | import com.badlogic.gdx.graphics.Texture; 23 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 24 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; 25 | import com.badlogic.gdx.utils.viewport.ScreenViewport; 26 | import com.badlogic.gdx.utils.viewport.Viewport; 27 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 28 | import com.bladecoder.inkplayer.ui.UI.Screens; 29 | 30 | public class InitScreen extends ScreenAdapter implements AppScreen { 31 | private final static String FILENAME = "ui/blade_logo.png"; 32 | private final static float FADE_TIME = .6f; 33 | private final static float SCREEN_TIME = .8f; 34 | 35 | private Texture tex; 36 | 37 | private UI ui; 38 | 39 | private float time; 40 | private float fadeTime; 41 | private float scale = 1f; 42 | 43 | private final Viewport viewport = new ScreenViewport(); 44 | 45 | @Override 46 | public void render(float delta) { 47 | Gdx.graphics.requestRendering(); 48 | SpriteBatch batch = ui.getBatch(); 49 | 50 | Gdx.gl.glClearColor(0, 0, 0, 1); 51 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 52 | 53 | batch.setProjectionMatrix(viewport.getCamera().combined); 54 | batch.begin(); 55 | 56 | if (time > FADE_TIME * 2 + SCREEN_TIME) { // EXIT INIT SCREEN 57 | batch.setColor(Color.WHITE); 58 | ui.setCurrentScreen(Screens.MENU_SCREEN); 59 | batch.end(); 60 | return; 61 | } else if (time > FADE_TIME + SCREEN_TIME) { // FADE_OUT 62 | batch.setColor(1, 1, 1, 1 - fadeTime / FADE_TIME); 63 | } else if (time < FADE_TIME) { // FADE IN 64 | batch.setColor(1, 1, 1, fadeTime / FADE_TIME); 65 | } else { 66 | fadeTime = 0; 67 | } 68 | 69 | final int viewportW = viewport.getScreenWidth(); 70 | final int viewportH = viewport.getScreenHeight(); 71 | final float texW = tex.getWidth() * scale; 72 | final float texH = tex.getHeight() * scale; 73 | batch.draw(tex, (viewportW - texW) / 2, (viewportH - texH) / 2, texW, texH); 74 | batch.setColor(Color.WHITE); 75 | 76 | time += delta; 77 | fadeTime += delta; 78 | batch.end(); 79 | } 80 | 81 | @Override 82 | public void resize(int width, int height) { 83 | viewport.update(width, height, true); 84 | scale = width / (float) EngineAssetManager.getInstance().getResolution().portraitWidth; 85 | } 86 | 87 | protected void retrieveAssets() { 88 | tex = new Texture(EngineAssetManager.getInstance().getResAsset(FILENAME)); 89 | tex.setFilter(TextureFilter.Linear, TextureFilter.Linear); 90 | } 91 | 92 | @Override 93 | public void dispose() { 94 | if(tex != null) { 95 | tex.dispose(); 96 | tex = null; 97 | } 98 | } 99 | 100 | @Override 101 | public void show() { 102 | time = fadeTime = 0; 103 | Gdx.input.setInputProcessor(null); 104 | retrieveAssets(); 105 | } 106 | 107 | @Override 108 | public void hide() { 109 | dispose(); 110 | } 111 | 112 | @Override 113 | public void setUI(UI ui) { 114 | this.ui = ui; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/StoryScreen.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright 2014 Rafael Garcia Moreno. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | ******************************************************************************/ 16 | package com.bladecoder.inkplayer.ui; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | import com.badlogic.gdx.Gdx; 22 | import com.badlogic.gdx.Input; 23 | import com.badlogic.gdx.InputAdapter; 24 | import com.badlogic.gdx.InputMultiplexer; 25 | import com.badlogic.gdx.InputProcessor; 26 | import com.badlogic.gdx.graphics.Color; 27 | import com.badlogic.gdx.graphics.GL20; 28 | import com.badlogic.gdx.graphics.Texture; 29 | import com.badlogic.gdx.graphics.Texture.TextureFilter; 30 | import com.badlogic.gdx.math.Interpolation; 31 | import com.badlogic.gdx.scenes.scene2d.InputEvent; 32 | import com.badlogic.gdx.scenes.scene2d.Stage; 33 | import com.badlogic.gdx.scenes.scene2d.actions.Actions; 34 | import com.badlogic.gdx.scenes.scene2d.ui.Button; 35 | import com.badlogic.gdx.scenes.scene2d.ui.Image; 36 | import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; 37 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 38 | import com.badlogic.gdx.scenes.scene2d.utils.UIUtils; 39 | import com.badlogic.gdx.utils.viewport.ScreenViewport; 40 | import com.badlogic.gdx.utils.viewport.Viewport; 41 | import com.bladecoder.inkplayer.Line; 42 | import com.bladecoder.inkplayer.StoryListener; 43 | import com.bladecoder.inkplayer.StoryManager; 44 | import com.bladecoder.inkplayer.assets.EngineAssetManager; 45 | import com.bladecoder.inkplayer.common.DPIUtils; 46 | import com.bladecoder.inkplayer.ui.UI.Screens; 47 | 48 | public class StoryScreen implements AppScreen { 49 | private static final String TAG = "StoryScreen"; 50 | 51 | private static final float CHOICES_SHOW_TIME = 1.5f; 52 | 53 | private UI ui; 54 | private StoryManager storyManager; 55 | 56 | private Stage stage; 57 | 58 | private Button menuButton; 59 | private ChoicesUI choicesUI; 60 | private TextPanel textPanel; 61 | 62 | private Image background; 63 | 64 | private float tmpMoveByAmountY; 65 | 66 | private final Viewport viewport; 67 | 68 | private final StoryListener storyListener = new StoryListener() { 69 | 70 | @Override 71 | public void line(Line line) { 72 | textPanel.addLine(line, new Runnable() { 73 | 74 | @Override 75 | public void run() { 76 | storyManager.next(); 77 | } 78 | 79 | }); 80 | } 81 | 82 | @Override 83 | public void command(String name, HashMap params) { 84 | 85 | } 86 | 87 | @Override 88 | public void choices(List choices) { 89 | showChoices(choices); 90 | } 91 | 92 | @Override 93 | public void end() { 94 | String theEnd = "THE END"; 95 | 96 | Line line = new Line(theEnd, new HashMap(0)); 97 | 98 | textPanel.addLine(line, new Runnable() { 99 | 100 | @Override 101 | public void run() { 102 | textPanel.addAction( 103 | Actions.sequence(Actions.delay(4), Actions.fadeOut(2), Actions.run(new Runnable() { 104 | @Override 105 | public void run() { 106 | ui.setCurrentScreen(Screens.CREDIT_SCREEN); 107 | textPanel.setColor(Color.WHITE); 108 | } 109 | }))); 110 | } 111 | 112 | }); 113 | } 114 | 115 | @Override 116 | public void newGame() { 117 | resetUI(); 118 | storyManager.next(); 119 | } 120 | 121 | @Override 122 | public void loadGame() { 123 | resetUI(); 124 | storyManager.next(); 125 | } 126 | }; 127 | 128 | private final InputProcessor inputProcessor = new InputAdapter() { 129 | 130 | @Override 131 | public boolean keyUp(int keycode) { 132 | switch (keycode) { 133 | case Input.Keys.ESCAPE: 134 | case Input.Keys.BACK: 135 | case Input.Keys.MENU: 136 | showMenu(); 137 | break; 138 | case Input.Keys.D: 139 | if (UIUtils.ctrl()) 140 | // FIXME EngineLogger.toggle(); 141 | break; 142 | case Input.Keys.SPACE: 143 | 144 | break; 145 | } 146 | 147 | return true; 148 | } 149 | 150 | @Override 151 | public boolean keyTyped(char character) { 152 | switch (character) { 153 | case 'f': 154 | // ui.toggleFullScreen(); 155 | break; 156 | } 157 | 158 | return true; 159 | } 160 | }; 161 | 162 | public StoryScreen() { 163 | viewport = new ScreenViewport(); 164 | } 165 | 166 | public UI getUI() { 167 | return ui; 168 | } 169 | 170 | private void update(float delta) { 171 | stage.act(delta); 172 | } 173 | 174 | private void showChoices(List choices) { 175 | choicesUI.show(choices); 176 | choicesUI.clearActions(); 177 | 178 | tmpMoveByAmountY = choicesUI.getHeight() - textPanel.getY() + DPIUtils.getMarginSize(); 179 | 180 | if (tmpMoveByAmountY < 0) { 181 | tmpMoveByAmountY = 0; 182 | } else { 183 | textPanel.addAction(Actions.moveBy(0, tmpMoveByAmountY, CHOICES_SHOW_TIME, Interpolation.fade)); 184 | } 185 | 186 | choicesUI.setVisible(true); 187 | choicesUI.setY(-choicesUI.getHeight()); 188 | choicesUI.addAction(Actions.moveBy(0, choicesUI.getHeight(), CHOICES_SHOW_TIME, Interpolation.fade)); 189 | } 190 | 191 | @Override 192 | public void render(float delta) { 193 | update(delta); 194 | 195 | Gdx.gl.glClearColor(0, 0, 0, 1); 196 | Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 197 | 198 | // STAGE 199 | stage.draw(); 200 | } 201 | 202 | public void dispose() { 203 | stage.dispose(); 204 | } 205 | 206 | private void showMenu() { 207 | pause(); 208 | ui.setCurrentScreen(Screens.MENU_SCREEN); 209 | } 210 | 211 | public Stage getStage() { 212 | return stage; 213 | } 214 | 215 | public void selectChoice(final int i) { 216 | textPanel 217 | .addAction(Actions.sequence(Actions.moveBy(0, -tmpMoveByAmountY, CHOICES_SHOW_TIME, Interpolation.fade), 218 | Actions.run(new Runnable() { 219 | 220 | @Override 221 | public void run() { 222 | storyManager.selectChoice(i); 223 | storyManager.next(); 224 | tmpMoveByAmountY = 0; 225 | } 226 | }))); 227 | } 228 | 229 | private void resetUI() { 230 | choicesUI.setVisible(false); 231 | choicesUI.clearActions(); 232 | textPanel.clearActions(); 233 | tmpMoveByAmountY = 0; 234 | } 235 | 236 | @Override 237 | public void show() { 238 | InputMultiplexer multiplexer = new InputMultiplexer(); 239 | multiplexer.addProcessor(stage); 240 | multiplexer.addProcessor(inputProcessor); 241 | Gdx.input.setInputProcessor(multiplexer); 242 | stage.setScrollFocus(textPanel); 243 | 244 | textPanel.addAction(Actions.sequence(Actions.alpha(0), Actions.alpha(1, 0.7f))); 245 | } 246 | 247 | @Override 248 | public void resize(int width, int height) { 249 | Gdx.app.log(TAG, "RESIZING STORY SCREEN."); 250 | 251 | viewport.update(width, height, true); 252 | 253 | float size = DPIUtils.getPrefButtonSize(); 254 | float margin = DPIUtils.getMarginSize(); 255 | 256 | menuButton.setSize(size, size); 257 | menuButton.setPosition(stage.getViewport().getScreenWidth() - menuButton.getWidth() - margin, 258 | stage.getViewport().getScreenHeight() - menuButton.getHeight() - margin); 259 | 260 | background.setSize(width, height); 261 | 262 | choicesUI.resize(width, height); 263 | textPanel.resize(width, height); 264 | 265 | if (storyManager.hasChoices()) { 266 | textPanel.clearActions(); 267 | showChoices(storyManager.getChoices()); 268 | } 269 | } 270 | 271 | @Override 272 | public void hide() { 273 | } 274 | 275 | @Override 276 | public void pause() { 277 | } 278 | 279 | @Override 280 | public void resume() { 281 | } 282 | 283 | @Override 284 | public void setUI(final UI ui) { 285 | this.ui = ui; 286 | StoryScreenStyle style = ui.getSkin().get(StoryScreenStyle.class); 287 | storyManager = ui.getStoryManager(); 288 | 289 | stage = new Stage(viewport); 290 | 291 | // recorder = ui.getRecorder(); 292 | 293 | menuButton = new Button(ui.getSkin(), "menu"); 294 | choicesUI = new ChoicesUI(this); 295 | textPanel = new TextPanel(ui); 296 | 297 | if (style.bgFile != null) { 298 | Texture tex = new Texture(EngineAssetManager.getInstance().getResAsset(style.bgFile)); 299 | tex.setFilter(TextureFilter.Linear, TextureFilter.Linear); 300 | 301 | background = new Image(tex); 302 | stage.addActor(background); 303 | } 304 | 305 | menuButton.addListener(new ClickListener() { 306 | public void clicked(InputEvent event, float x, float y) { 307 | ui.setCurrentScreen(Screens.MENU_SCREEN); 308 | } 309 | }); 310 | 311 | choicesUI.setVisible(false); 312 | 313 | stage.addActor(textPanel); 314 | stage.addActor(choicesUI); 315 | stage.addActor(menuButton); 316 | 317 | storyManager.setStoryListener(storyListener); 318 | } 319 | 320 | /** The style for the MenuScreen */ 321 | public static class StoryScreenStyle { 322 | /** Optional. */ 323 | public Drawable background; 324 | /** if 'background' is not specified try to load the bgFile */ 325 | public String bgFile; 326 | 327 | public StoryScreenStyle() { 328 | } 329 | 330 | public StoryScreenStyle(StoryScreenStyle style) { 331 | background = style.background; 332 | bgFile = style.bgFile; 333 | } 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /core/src/main/java/com/bladecoder/inkplayer/ui/TextPanel.java: -------------------------------------------------------------------------------- 1 | package com.bladecoder.inkplayer.ui; 2 | 3 | import java.util.List; 4 | 5 | import com.badlogic.gdx.graphics.Color; 6 | import com.badlogic.gdx.scenes.scene2d.actions.Actions; 7 | import com.badlogic.gdx.scenes.scene2d.ui.Cell; 8 | import com.badlogic.gdx.scenes.scene2d.ui.Label; 9 | import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle; 10 | import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane; 11 | import com.badlogic.gdx.scenes.scene2d.ui.Skin; 12 | import com.badlogic.gdx.scenes.scene2d.ui.Table; 13 | import com.badlogic.gdx.scenes.scene2d.utils.Drawable; 14 | import com.badlogic.gdx.utils.Align; 15 | import com.badlogic.gdx.utils.ObjectMap; 16 | import com.bladecoder.inkplayer.Line; 17 | import com.bladecoder.inkplayer.StoryManager; 18 | import com.bladecoder.inkplayer.common.DPIUtils; 19 | 20 | /** 21 | * The TextPanel is a table with a label per row 22 | * 23 | * @author rgarcia 24 | */ 25 | public class TextPanel extends ScrollPane { 26 | private static final float DEFAULT_WAITING_TIME = 0.95f; 27 | 28 | private final String STYLE_PARAM = "style"; 29 | private final String COLOR_PARAM = "color"; 30 | 31 | private TextPanelStyle style; 32 | private ObjectMap labelStyles; 33 | private Table panel; 34 | 35 | private StoryManager sm; 36 | 37 | public TextPanel(UI ui) { 38 | super(new Table(ui.getSkin())); 39 | 40 | Skin skin = ui.getSkin(); 41 | sm = ui.getStoryManager(); 42 | 43 | style = skin.get(TextPanelStyle.class); 44 | labelStyles = skin.getAll(LabelStyle.class); 45 | 46 | setScrollingDisabled(true, false); 47 | 48 | panel = (Table) getActor(); 49 | 50 | if (style.background != null) 51 | panel.setBackground(style.background); 52 | 53 | // panel.debug(); 54 | panel.defaults().expandX().pad(DPIUtils.getSpacing()); 55 | panel.bottom().left(); 56 | } 57 | 58 | public void resize(int width, int height) { 59 | if (height > width) 60 | setSize(width * (DPIUtils.getSizeMultiplier() < 1.2 ? 0.9f : 0.7f), height * 0.75f); // portrait 61 | else 62 | setSize(width * (DPIUtils.getSizeMultiplier() < 1.2 ? 0.8f : 0.6f), height * 0.9f); // landscape 63 | 64 | setPosition((width - getWidth()) / 2, height - getHeight()); 65 | 66 | panel.clearChildren(); 67 | 68 | // refill panel 69 | for (Line l : sm.getRecod()) 70 | addLine(l, null); 71 | } 72 | 73 | public Drawable getBackground() { 74 | return style.background; 75 | } 76 | 77 | public void addLine(Line line, Runnable runWhenEnds) { 78 | 79 | float margin = DPIUtils.getSpacing() * 4; 80 | 81 | Label l = new Label(line.text, line.params.get(STYLE_PARAM) == null ? style.labelStyle 82 | : labelStyles.get(line.params.get(STYLE_PARAM))); 83 | Table tl = new Table(); 84 | tl.defaults().pad(DPIUtils.getSpacing(), margin, DPIUtils.getSpacing(), margin); 85 | tl.setBackground(l.getStyle().background); 86 | 87 | if (line.params.get(COLOR_PARAM) != null) 88 | l.setColor(Color.valueOf(line.params.get(COLOR_PARAM))); 89 | 90 | if (line.params.get(STYLE_PARAM) == null) { 91 | l.setAlignment(Align.left); 92 | } 93 | 94 | int align = Align.left; 95 | 96 | float maxLabelSize = getWidth() - margin * 2; 97 | Cell