├── settings.gradle ├── ios ├── data │ ├── Icon.png │ ├── Default.png │ ├── Icon-72.png │ ├── Icon@2x.png │ ├── Default@2x.png │ ├── Default~ipad.png │ ├── Icon-72@2x.png │ ├── Default-568h@2x.png │ ├── Default@2x~ipad.png │ ├── Default-375w-667h@2x.png │ ├── Default-414w-736h@3x.png │ └── Default-1024w-1366h@2x~ipad.png ├── robovm.properties ├── build.gradle ├── src │ └── io │ │ └── wasin │ │ └── blockbunny │ │ └── IOSLauncher.kt ├── Info.plist.xml └── robovm.xml ├── proguard.append.cfg ├── android ├── assets │ ├── sfx │ │ ├── hit.wav │ │ ├── jump.wav │ │ ├── crystal.wav │ │ ├── changeblock.wav │ │ └── levelselect.wav │ ├── images │ │ ├── bgs.png │ │ ├── hud.png │ │ ├── menu.png │ │ ├── misc.png │ │ ├── bunny.png │ │ ├── spikes.png │ │ └── crystal.png │ ├── maps │ │ ├── blocks.png │ │ ├── blocks.tsx │ │ ├── level1.tmx │ │ ├── level4.tmx │ │ ├── level2.tmx │ │ ├── level5.tmx │ │ ├── level3.tmx │ │ ├── level7.tmx │ │ ├── level6.tmx │ │ ├── level8.tmx │ │ ├── level9.tmx │ │ ├── level12.tmx │ │ ├── level13.tmx │ │ ├── level14.tmx │ │ ├── level15.tmx │ │ ├── level10.tmx │ │ └── level11.tmx │ └── music │ │ ├── bbsong.mp3 │ │ └── bbsong.ogg ├── ic_launcher-web.png ├── 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 │ └── io │ │ └── wasin │ │ └── blockbunny │ │ └── AndroidLauncher.kt ├── project.properties ├── AndroidManifest.xml ├── proguard-project.txt └── build.gradle ├── ios-moe ├── proguard.append.cfg ├── xcode │ ├── ios-moe │ │ ├── Icon.png │ │ ├── Default.png │ │ ├── Icon-72.png │ │ ├── Icon@2x.png │ │ ├── Default@2x.png │ │ ├── Icon-72@2x.png │ │ ├── Default~ipad.png │ │ ├── Default-568h@2x.png │ │ ├── Default@2x~ipad.png │ │ ├── main.cpp │ │ ├── Default-375w-667h@2x.png │ │ ├── Default-414w-736h@3x.png │ │ ├── Default-1024w-1366h@2x~ipad.png │ │ ├── custom.xcconfig │ │ └── Info.plist │ └── ios-moe-Test │ │ ├── main.cpp │ │ └── Info.plist ├── src │ └── io │ │ └── wasin │ │ └── blockbunny │ │ └── IOSMoeLauncher.kt └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── core ├── src │ └── io │ │ └── wasin │ │ └── blockbunny │ │ ├── data │ │ ├── PlayerSaveCache.kt │ │ └── PlayerSave.kt │ │ ├── handlers │ │ ├── Settings.kt │ │ ├── B2DVars.kt │ │ ├── GameRuntimeException.kt │ │ ├── ScreenStopper.kt │ │ ├── Animation.kt │ │ ├── Background.kt │ │ ├── TextRenderer.kt │ │ ├── Content.kt │ │ ├── LevelButton.kt │ │ ├── MyContactListener.kt │ │ ├── GameStateManager.kt │ │ ├── BBInput.kt │ │ ├── PlayerSaveFileManager.kt │ │ └── BBInputProcessor.kt │ │ ├── entities │ │ ├── Bomb.kt │ │ ├── Crystal.kt │ │ ├── B2DSprite.kt │ │ ├── Player.kt │ │ └── HUD.kt │ │ ├── interfaces │ │ └── ISaveFile.kt │ │ ├── states │ │ ├── GameState.kt │ │ ├── Score.kt │ │ ├── LevelSelection.kt │ │ ├── Mainmenu.kt │ │ └── Play.kt │ │ └── Game.kt └── build.gradle ├── desktop ├── src │ └── io │ │ └── wasin │ │ └── blockbunny │ │ └── desktop │ │ └── DesktopLauncher.kt └── build.gradle ├── gradle.properties ├── LICENSE ├── .gitignore ├── gradlew.bat ├── README.md └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'desktop', 'android', 'core', 'ios-moe', 'ios' -------------------------------------------------------------------------------- /ios/data/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Icon.png -------------------------------------------------------------------------------- /ios/data/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default.png -------------------------------------------------------------------------------- /ios/data/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Icon-72.png -------------------------------------------------------------------------------- /ios/data/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Icon@2x.png -------------------------------------------------------------------------------- /proguard.append.cfg: -------------------------------------------------------------------------------- 1 | 2 | -keep class com.badlogic.** { *; } 3 | -keep enum com.badlogic.** { *; } -------------------------------------------------------------------------------- /ios/data/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default@2x.png -------------------------------------------------------------------------------- /ios/data/Default~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default~ipad.png -------------------------------------------------------------------------------- /ios/data/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Icon-72@2x.png -------------------------------------------------------------------------------- /android/assets/sfx/hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/sfx/hit.wav -------------------------------------------------------------------------------- /android/assets/sfx/jump.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/sfx/jump.wav -------------------------------------------------------------------------------- /android/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/ic_launcher-web.png -------------------------------------------------------------------------------- /ios-moe/proguard.append.cfg: -------------------------------------------------------------------------------- 1 | 2 | -keep class com.badlogic.** { *; } 3 | -keep enum com.badlogic.** { *; } -------------------------------------------------------------------------------- /android/assets/images/bgs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/bgs.png -------------------------------------------------------------------------------- /android/assets/images/hud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/hud.png -------------------------------------------------------------------------------- /android/assets/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/menu.png -------------------------------------------------------------------------------- /android/assets/images/misc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/misc.png -------------------------------------------------------------------------------- /android/assets/maps/blocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/maps/blocks.png -------------------------------------------------------------------------------- /android/assets/sfx/crystal.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/sfx/crystal.wav -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Icon.png -------------------------------------------------------------------------------- /ios/data/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default-568h@2x.png -------------------------------------------------------------------------------- /ios/data/Default@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default@2x~ipad.png -------------------------------------------------------------------------------- /android/assets/images/bunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/bunny.png -------------------------------------------------------------------------------- /android/assets/images/spikes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/spikes.png -------------------------------------------------------------------------------- /android/assets/music/bbsong.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/music/bbsong.mp3 -------------------------------------------------------------------------------- /android/assets/music/bbsong.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/music/bbsong.ogg -------------------------------------------------------------------------------- /android/assets/images/crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/images/crystal.png -------------------------------------------------------------------------------- /android/assets/sfx/changeblock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/sfx/changeblock.wav -------------------------------------------------------------------------------- /android/assets/sfx/levelselect.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/assets/sfx/levelselect.wav -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Icon-72.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Icon@2x.png -------------------------------------------------------------------------------- /ios/data/Default-375w-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default-375w-667h@2x.png -------------------------------------------------------------------------------- /ios/data/Default-414w-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default-414w-736h@3x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default@2x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Icon-72@2x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default~ipad.png -------------------------------------------------------------------------------- /ios/data/Default-1024w-1366h@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios/data/Default-1024w-1366h@2x~ipad.png -------------------------------------------------------------------------------- /android/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default-568h@2x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default@2x~ipad.png -------------------------------------------------------------------------------- /android/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/android/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | return moevm(argc, argv); 5 | } 6 | -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe-Test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | return moevm(argc, argv); 5 | } 6 | -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default-375w-667h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default-375w-667h@2x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default-414w-736h@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default-414w-736h@3x.png -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Default-1024w-1366h@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haxpor/blockbunny/HEAD/ios-moe/xcode/ios-moe/Default-1024w-1366h@2x~ipad.png -------------------------------------------------------------------------------- /android/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BlockBunny 5 | 6 | 7 | -------------------------------------------------------------------------------- /ios/robovm.properties: -------------------------------------------------------------------------------- 1 | app.version=1.0 2 | app.id=io.wasin.blockbunny 3 | app.mainclass=io.wasin.blockbunny.IOSLauncher 4 | app.executable=IOSLauncher 5 | app.build=1 6 | app.name=BlockBunny 7 | -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/custom.xcconfig: -------------------------------------------------------------------------------- 1 | LIBGDX_NATIVES = -force_load ${SRCROOT}/native/ios/libObjectAL.a -force_load ${SRCROOT}/native/ios/libgdx.a -force_load ${SRCROOT}/native/ios/libgdx-box2d.a -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/data/PlayerSaveCache.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.data 2 | 3 | /** 4 | * Created by haxpor on 6/1/17. 5 | */ 6 | data class PlayerSaveCache(var data: PlayerSave? = null) -------------------------------------------------------------------------------- /android/assets/maps/blocks.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | sourceCompatibility = 1.6 4 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 5 | 6 | sourceSets.main.java.srcDirs = [ "src/" ] 7 | 8 | 9 | eclipse.project { 10 | name = appName + "-core" 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 21 13:08:26 CEST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/Settings.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | /** 4 | * Created by haxpor on 6/1/17. 5 | */ 6 | class Settings { 7 | companion object { 8 | const val PLAYER_SAVEFILE_RELATIVE_PATH: String = "player.json" 9 | const val TOTAL_LEVELS: Int = 15 10 | } 11 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/entities/Bomb.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.entities 2 | 3 | import com.badlogic.gdx.graphics.g2d.TextureRegion 4 | import com.badlogic.gdx.physics.box2d.Body 5 | import io.wasin.blockbunny.Game 6 | 7 | /** 8 | * Created by haxpor on 6/3/17. 9 | */ 10 | class Bomb(body: Body): B2DSprite(body) { 11 | init { 12 | val tex = Game.res.getTexture("spikes") 13 | val sprites = TextureRegion.split(tex, 32,32)[0] 14 | setAnimation(sprites, 1/12f) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/entities/Crystal.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.entities 2 | 3 | import com.badlogic.gdx.graphics.g2d.TextureRegion 4 | import com.badlogic.gdx.physics.box2d.Body 5 | import io.wasin.blockbunny.Game 6 | 7 | /** 8 | * Created by haxpor on 5/17/17. 9 | */ 10 | class Crystal(body: Body) : B2DSprite(body) { 11 | init { 12 | val tex = Game.res.getTexture("crystal") 13 | val sprites = TextureRegion.split(tex, 16,16)[0] 14 | setAnimation(sprites, 1/12f) 15 | } 16 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/data/PlayerSave.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.data 2 | 3 | /** 4 | * Created by haxpor on 6/1/17. 5 | */ 6 | 7 | /** 8 | * Warning, don't change field name for each data class as it will affect resulting written 9 | * JSON file at the end. 10 | * 11 | * Thus this will have major effect in production environment 12 | */ 13 | 14 | data class LevelResult(var clear: Boolean = false, var collectedCrystal: Int = 0) 15 | data class PlayerSave(var levelResults: Array = emptyArray()) -------------------------------------------------------------------------------- /android/src/io/wasin/blockbunny/AndroidLauncher.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny 2 | 3 | import android.os.Bundle 4 | 5 | import com.badlogic.gdx.backends.android.AndroidApplication 6 | import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration 7 | 8 | class AndroidLauncher : AndroidApplication() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | val config = AndroidApplicationConfiguration() 12 | initialize(Game(), config) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ios/build.gradle: -------------------------------------------------------------------------------- 1 | sourceSets.main.java.srcDirs = [ "src/" ] 2 | 3 | sourceCompatibility = '1.7' 4 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 5 | 6 | ext { 7 | mainClassName = "io.wasin.blockbunny.IOSLauncher" 8 | } 9 | 10 | launchIPhoneSimulator.dependsOn build 11 | launchIPadSimulator.dependsOn build 12 | launchIOSDevice.dependsOn build 13 | createIPA.dependsOn build 14 | 15 | robovm { 16 | archs = "thumbv7:arm64" 17 | } 18 | 19 | eclipse.project { 20 | name = appName + "-ios" 21 | natures 'org.robovm.eclipse.RoboVMNature' 22 | } -------------------------------------------------------------------------------- /android/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/interfaces/ISaveFile.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.interfaces 2 | 3 | import io.wasin.blockbunny.data.PlayerSave 4 | 5 | /** 6 | * Created by haxpor on 6/1/17. 7 | */ 8 | interface ISaveFile { 9 | /** 10 | * Read save file from the specified file path. 11 | * Returned data should be PlayerSave which can be null. 12 | */ 13 | fun readSaveFile(filePath: String): PlayerSave? 14 | 15 | /** 16 | * Write save file to the specified file path. 17 | * Data to be written should be PlayerSave. 18 | */ 19 | fun writeSaveFile(data: PlayerSave, filePath: String) 20 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /desktop/src/io/wasin/blockbunny/desktop/DesktopLauncher.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.desktop 2 | 3 | import com.badlogic.gdx.backends.lwjgl.LwjglApplication 4 | import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration 5 | import io.wasin.blockbunny.Game 6 | 7 | object DesktopLauncher { 8 | @JvmStatic 9 | fun main(arg: Array) { 10 | val config = LwjglApplicationConfiguration() 11 | config.title = Game.TITLE 12 | // always start at design resolution first then scale it to full screen by user later (if need) 13 | config.width = Game.V_WIDTH.toInt() * Game.SCALE 14 | config.height = Game.V_HEIGHT.toInt() * Game.SCALE 15 | LwjglApplication(Game(), config) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/B2DVars.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | /** 4 | * Created by haxpor on 5/16/17. 5 | */ 6 | class B2DVars { 7 | companion object { 8 | // pixel per meters 9 | const val PPM: Float = 100f 10 | 11 | // category bits 12 | const val BIT_PLAYER: Short = 2 13 | const val BIT_RED: Short = 4 14 | const val BIT_GREEN: Short = 8 15 | const val BIT_BLUE: Short = 16 16 | const val BIT_CRYSTAL: Short = 32 17 | const val BIT_BOMB: Short= 64 18 | const val BIT_TOP_PLATFORM: Short = 128 19 | const val BIT_BOTTOM_PLATFORM: Short = 256 20 | const val BIT_LINE1_BLOCK: Short = 512 21 | const val BIT_LINE2_BLOCK: Short = 1024 22 | } 23 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/GameRuntimeException.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | /** 4 | * Created by haxpor on 6/1/17. 5 | */ 6 | class GameRuntimeException: RuntimeException { 7 | 8 | companion object { 9 | const val SAVE_FILE_NOT_FOUND: Int = 9000 10 | const val SAVE_FILE_EMPTY_CONTENT: Int = 9001 11 | const val WRITE_FILE_ERROR: Int = 9002 12 | const val NULL_ERROR: Int = 9003 13 | } 14 | 15 | /** 16 | * Error code reresent this error 17 | */ 18 | val code: Int 19 | 20 | constructor(message: String, code: Int): super(message) { this.code = code } 21 | constructor(t: Throwable, code: Int): super(t) { this.code = code } 22 | constructor(message: String, t: Throwable, code: Int): super(message,t ) { this.code = code } 23 | } -------------------------------------------------------------------------------- /ios-moe/src/io/wasin/blockbunny/IOSMoeLauncher.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny 2 | 3 | import com.badlogic.gdx.backends.iosmoe.IOSApplication 4 | import com.badlogic.gdx.backends.iosmoe.IOSApplicationConfiguration 5 | import org.moe.natj.general.Pointer 6 | 7 | import apple.uikit.c.UIKit 8 | 9 | class IOSMoeLauncher protected constructor(peer: Pointer) : IOSApplication.Delegate(peer) { 10 | 11 | override fun createApplication(): IOSApplication { 12 | val config = IOSApplicationConfiguration() 13 | config.useAccelerometer = false 14 | return IOSApplication(Game(), config) 15 | } 16 | 17 | companion object { 18 | 19 | @JvmStatic fun main(argv: Array) { 20 | UIKit.UIApplicationMain(0, null, null, IOSMoeLauncher::class.java.name) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/src/io/wasin/blockbunny/IOSLauncher.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny 2 | 3 | import org.robovm.apple.foundation.NSAutoreleasePool 4 | import org.robovm.apple.uikit.UIApplication 5 | 6 | import com.badlogic.gdx.backends.iosrobovm.IOSApplication 7 | import com.badlogic.gdx.backends.iosrobovm.IOSApplicationConfiguration 8 | 9 | class IOSLauncher : IOSApplication.Delegate() { 10 | override fun createApplication(): IOSApplication { 11 | val config = IOSApplicationConfiguration() 12 | return IOSApplication(Game(), config) 13 | } 14 | 15 | companion object { 16 | 17 | @JvmStatic fun main(argv: Array) { 18 | val pool = NSAutoreleasePool() 19 | UIApplication.main(argv, null, IOSLauncher::class.java) 20 | pool.close() 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## Project-wide Gradle settings. 2 | # 3 | # For more details on how to configure your build environment visit 4 | # http://www.gradle.org/docs/current/userguide/build_environment.html 5 | # 6 | # Specifies the JVM arguments used for the daemon process. 7 | # The setting is particularly useful for tweaking memory settings. 8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 10 | # 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | #Sat May 13 23:55:36 CST 2017 16 | systemProp.http.proxyHost=127.0.0.1 17 | org.gradle.jvmargs=-Xms128m -Xmx1500m 18 | org.gradle.daemon=true 19 | org.gradle.configureondemand=true 20 | systemProp.http.proxyPort=1086 21 | -------------------------------------------------------------------------------- /android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /android/assets/maps/level1.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 7 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 1,1,1,1,1,1,1,1,0,0,2,2,2,2,2,2,2,2,2,2,2,0,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | According to original tuturial code by author ForeignGuyMike on Youtube channel via download link doesn't contain a specific license, but he publicly distribute code and assets not to mention knowledge without restriction thus I decided to stay in the same course and use MIT. 4 | 5 | Copyright (c) 2017 Wasin Thonkaew 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/entities/B2DSprite.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.entities 2 | 3 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 4 | import com.badlogic.gdx.graphics.g2d.TextureRegion 5 | import com.badlogic.gdx.math.Vector2 6 | import com.badlogic.gdx.physics.box2d.Body 7 | import io.wasin.blockbunny.handlers.Animation 8 | import io.wasin.blockbunny.handlers.B2DVars 9 | 10 | /** 11 | * Created by haxpor on 5/17/17. 12 | */ 13 | open class B2DSprite(body: Body) { 14 | var body: Body = body 15 | private set 16 | protected var animation: Animation = Animation() 17 | 18 | var width: Float = 0f 19 | private set 20 | var height: Float = 0f 21 | private set 22 | 23 | var position: Vector2 = body.position 24 | get() = body.position 25 | private set 26 | 27 | fun setAnimation(regs: Array, delay: Float) { 28 | animation.setFrames(regs, delay) 29 | 30 | if (regs.size > 0) { 31 | // same size sprite 32 | width = regs[0].regionWidth.toFloat() 33 | height = regs[0].regionHeight.toFloat() 34 | } 35 | } 36 | 37 | fun update(dt: Float) { 38 | animation.update(dt) 39 | } 40 | 41 | fun render(sb: SpriteBatch) { 42 | sb.draw( 43 | animation.getCurrentFrame(), 44 | body.position.x * B2DVars.PPM - width / 2, 45 | body.position.y * B2DVars.PPM - height / 2) 46 | } 47 | } -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | MOE.Main.Class 24 | io.wasin.blockbunny.IOSMoeLauncher 25 | UIApplicationExitsOnSuspend 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIRequiresFullScreen 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/ScreenStopper.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.OrthographicCamera 5 | import com.badlogic.gdx.maps.tiled.TiledMap 6 | 7 | /** 8 | * Created by haxpor on 5/29/17. 9 | */ 10 | class ScreenStopper(tileMap: TiledMap, cam: OrthographicCamera) { 11 | // get total width for the input tilemap 12 | val width: Float = tileMap.properties.get("tilewidth", Float::class.java) * tileMap.properties.get("width", Float::class.java) 13 | 14 | private val cam: OrthographicCamera = cam 15 | 16 | private var onReachEndOfLevel: (() -> Unit)? = null 17 | var isReachedEndOfLevel: Boolean = false 18 | private set 19 | 20 | var isStopped: Boolean = false 21 | get() { 22 | return isReachedEndOfLevel || field 23 | } 24 | private set 25 | 26 | fun setOnRearchEndOfLevel(listener: () -> Unit) { 27 | this.onReachEndOfLevel = listener 28 | } 29 | 30 | fun stop() { 31 | isStopped = true 32 | } 33 | 34 | fun update(dt: Float) { 35 | if (!isStopped && cam.position.x + cam.viewportWidth/2f >= width) { 36 | Gdx.app.log("ScreenStopper", "reached end of level") 37 | 38 | // stop camera from progression 39 | cam.position.x = width - cam.viewportWidth/2f 40 | cam.update() 41 | 42 | isReachedEndOfLevel = true 43 | isStopped = true 44 | 45 | onReachEndOfLevel?.invoke() 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /ios-moe/xcode/ios-moe-Test/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | MOE.Main.Class 24 | org.moe.mdt.junit.MoeRemoteTestRunner 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/assets/maps/level4.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0, 15 | 0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,1,1,1,2,2,2,2,2,3,3,3 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/assets/maps/level2.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 7 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,0,0,3,3,3,3,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,0,0,0,0,0,0,3,3,3,3,3,3, 13 | 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/Animation.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.graphics.g2d.TextureRegion 4 | 5 | /** 6 | * Created by haxpor on 5/17/17. 7 | */ 8 | class Animation{ 9 | 10 | private var frames: Array? = null 11 | private var time: Float = 1.0f 12 | private var delay: Float = 1/12f 13 | private var currentFrame: Int = 1 14 | private var timesPlayed: Int = 0 15 | 16 | constructor() { } 17 | 18 | constructor(frames: Array) { 19 | setFrames(frames, 1/12f) 20 | } 21 | 22 | constructor(frames: Array, delay: Float) { 23 | setFrames(frames, delay) 24 | } 25 | 26 | fun setFrames(frames: Array, delay: Float) { 27 | this.frames = frames 28 | this.delay = delay 29 | time = 0f 30 | currentFrame = 0 31 | timesPlayed = 0 32 | } 33 | 34 | fun update(dt: Float) { 35 | // it's not making any sense to update if frames is null, or has zero insize 36 | if (frames == null || frames?.size == 0) 37 | return 38 | 39 | if (delay <= 0f) return 40 | time += dt 41 | while (time >= delay) { 42 | step() 43 | } 44 | } 45 | 46 | private fun step() { 47 | time -= delay 48 | currentFrame++ 49 | if (currentFrame == frames!!.size) { 50 | currentFrame = 0 51 | timesPlayed++ 52 | } 53 | } 54 | 55 | fun getCurrentFrame() : TextureRegion? { 56 | if (frames == null || frames?.size == 0) 57 | return null 58 | else 59 | return frames!![currentFrame] 60 | } 61 | 62 | fun getTimesPlayed() : Int { 63 | return timesPlayed 64 | } 65 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/Background.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.graphics.OrthographicCamera 4 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 5 | import com.badlogic.gdx.graphics.g2d.TextureRegion 6 | import com.badlogic.gdx.math.Vector2 7 | 8 | /** 9 | * Created by haxpor on 5/21/17. 10 | */ 11 | class Background(image: TextureRegion, gameCam: OrthographicCamera, scale: Float){ 12 | 13 | private var image: TextureRegion = image 14 | private var cam: OrthographicCamera = gameCam 15 | 16 | var scale: Float = scale 17 | private set 18 | var velocity: Vector2 = Vector2(-1f, 0f) // default is to move along screenX-direction only 19 | 20 | private var numXDraw: Int 21 | private var position: Vector2 = Vector2.Zero 22 | 23 | init { 24 | // calculate how many time we need to draw across screenX-direction 25 | // num draw will be at least 2 26 | numXDraw = Math.ceil((cam.viewportWidth / this.image.regionWidth).toDouble()).toInt() + 1 27 | position.x = (cam.viewportWidth/2 - cam.position.x) * scale 28 | position.y = (cam.viewportHeight/2 - cam.position.y) * scale 29 | } 30 | 31 | fun update(dt: Float) { 32 | // calculate diff of position to be updated to absolute position later 33 | position.x += velocity.x * scale * dt 34 | position.y += velocity.y * scale * dt 35 | } 36 | 37 | fun render(sb: SpriteBatch) { 38 | 39 | for (i in 0..numXDraw-1) { 40 | 41 | val x = (((position.x + cam.viewportWidth/2 - cam.position.x)*scale)% image.regionWidth) + (i * image.regionWidth) 42 | val y = (position.y + cam.viewportHeight/2 - cam.position.y)*scale 43 | 44 | sb.draw(image, x, y) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/TextRenderer.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 4 | import com.badlogic.gdx.graphics.g2d.TextureRegion 5 | 6 | /** 7 | * Created by haxpor on 5/22/17. 8 | * 9 | * TextRenderer accepts 10 | * - sequence of number starting from 0-9 11 | * - slash (/) 12 | */ 13 | class TextRenderer(numberFontTextureRegions: Array, slashFontTextureRegion: TextureRegion) { 14 | var numberFontTextureRegions: Array = numberFontTextureRegions 15 | private set 16 | var slashFontTextureRegion: TextureRegion = slashFontTextureRegion 17 | private set 18 | 19 | var fontWidth: Int = numberFontTextureRegions[0].regionWidth 20 | private set 21 | var fontHeight: Int = numberFontTextureRegions[0].regionHeight 22 | private set 23 | var fontHalfWidth: Int = fontWidth/2 24 | private set 25 | var fontHalfHeight: Int = fontHeight/2 26 | private set 27 | 28 | fun renderNumber(number: Int, x: Float, y: Float, sb: SpriteBatch) { 29 | // use the same region width to offset 30 | // assume that it's equal in size for all individual font 31 | 32 | var numStr = number.toString() 33 | val length = numStr.length 34 | val startX = x - length/2f*fontWidth 35 | val posY = y - fontHeight/2f 36 | 37 | for (i in 0..length-1) { 38 | val regionIndex = numStr.get(i).toInt() - 48 39 | sb.draw(numberFontTextureRegions[regionIndex], startX + (i * fontWidth), posY) 40 | } 41 | } 42 | 43 | fun renderSlash(x: Float, y: Float, sb: SpriteBatch) { 44 | val posX = x - fontWidth/2f 45 | val posY = y - fontHeight/2f 46 | 47 | sb.draw(slashFontTextureRegion, posX, posY) 48 | } 49 | } -------------------------------------------------------------------------------- /desktop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | sourceCompatibility = 1.6 4 | sourceSets.main.java.srcDirs = [ "src/" ] 5 | 6 | project.ext.mainClassName = "io.wasin.blockbunny.desktop.DesktopLauncher" 7 | project.ext.assetsDir = new File("../android/assets"); 8 | 9 | task run(dependsOn: classes, type: JavaExec) { 10 | main = project.mainClassName 11 | classpath = sourceSets.main.runtimeClasspath 12 | standardInput = System.in 13 | workingDir = project.assetsDir 14 | ignoreExitValue = true 15 | } 16 | 17 | task debug(dependsOn: classes, type: JavaExec) { 18 | main = project.mainClassName 19 | classpath = sourceSets.main.runtimeClasspath 20 | standardInput = System.in 21 | workingDir = project.assetsDir 22 | ignoreExitValue = true 23 | debug = true 24 | } 25 | 26 | task dist(type: Jar) { 27 | from files(sourceSets.main.output.classesDir) 28 | from files(sourceSets.main.output.resourcesDir) 29 | from {configurations.compile.collect {zipTree(it)}} 30 | from files(project.assetsDir); 31 | 32 | manifest { 33 | attributes 'Main-Class': project.mainClassName 34 | } 35 | } 36 | 37 | dist.dependsOn classes 38 | 39 | eclipse { 40 | project { 41 | name = appName + "-desktop" 42 | linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/android/assets' 43 | } 44 | } 45 | 46 | task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { 47 | doLast { 48 | def classpath = new XmlParser().parse(file(".classpath")) 49 | new Node(classpath, "classpathentry", [ kind: 'src', path: 'assets' ]); 50 | def writer = new FileWriter(file(".classpath")) 51 | def printer = new XmlNodePrinter(new PrintWriter(writer)) 52 | printer.setPreserveWhitespace(true) 53 | printer.print(classpath) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /android/proguard-project.txt: -------------------------------------------------------------------------------- 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 | -keep class com.badlogic.gdx.controllers.android.AndroidControllers 32 | 33 | -keepclassmembers class com.badlogic.gdx.backends.android.AndroidInput* { 34 | (com.badlogic.gdx.Application, android.content.Context, java.lang.Object, com.badlogic.gdx.backends.android.AndroidApplicationConfiguration); 35 | } 36 | 37 | -keepclassmembers class com.badlogic.gdx.physics.box2d.World { 38 | boolean contactFilter(long, long); 39 | void beginContact(long); 40 | void endContact(long); 41 | void preSolve(long, long); 42 | void postSolve(long, long); 43 | boolean reportFixture(long); 44 | float reportRayFixture(long, float, float, float, float, float); 45 | } -------------------------------------------------------------------------------- /ios/Info.plist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${app.name} 9 | CFBundleExecutable 10 | ${app.executable} 11 | CFBundleIdentifier 12 | ${app.id} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${app.name} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | ${app.version} 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | ${app.build} 25 | LSRequiresIPhoneOS 26 | 27 | UIViewControllerBasedStatusBarAppearance 28 | 29 | UIStatusBarHidden 30 | 31 | UIDeviceFamily 32 | 33 | 1 34 | 2 35 | 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | opengles-2 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | CFBundleIcons 47 | 48 | CFBundlePrimaryIcon 49 | 50 | CFBundleIconFiles 51 | 52 | Icon 53 | Icon-72 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /android/assets/maps/level5.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0, 15 | 0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,1,1,1,2,2,2,2,2,3,3,3 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios-moe/build.gradle: -------------------------------------------------------------------------------- 1 | // Exclude all files from Gradle's test runner 2 | test { exclude '**' } 3 | 4 | task copyNatives << { 5 | file("xcode/native/ios/").mkdirs(); 6 | def LD_FLAGS = "LIBGDX_NATIVES = " 7 | configurations.natives.files.each { jar-> 8 | def outputDir = null 9 | if (jar.name.endsWith("natives-ios.jar")) outputDir = file("xcode/native/ios") 10 | if (outputDir != null) { 11 | FileCollection fileCollection = zipTree(jar) 12 | for (File libFile : fileCollection) { 13 | if (libFile.getAbsolutePath().endsWith(".a") && !libFile.getAbsolutePath().contains("/tvos/")) { 14 | copy { 15 | from libFile.getAbsolutePath() 16 | into outputDir 17 | } 18 | LD_FLAGS += " -force_load \${SRCROOT}/native/ios/" + libFile.getName() 19 | } 20 | } 21 | } 22 | } 23 | def outFlags = file("xcode/ios-moe/custom.xcconfig"); 24 | outFlags.write LD_FLAGS 25 | 26 | def proguard = file("proguard.append.cfg") 27 | if (!proguard.exists()) { 28 | proguard = new File("proguard.append.cfg") 29 | proguard << "\n-keep class com.badlogic.** { *; }\n" 30 | proguard << "-keep enum com.badlogic.** { *; }\n" 31 | } 32 | } 33 | 34 | sourceSets.main.java.srcDirs = [ "src/" ] 35 | 36 | // Setup Multi-OS Engine 37 | moe { 38 | xcode { 39 | project 'xcode/ios-moe.xcodeproj' 40 | mainTarget 'ios-moe' 41 | testTarget 'ios-moe-Test' 42 | } 43 | } 44 | 45 | moeMainReleaseIphoneosXcodeBuild.dependsOn copyNatives 46 | moeMainDebugIphoneosXcodeBuild.dependsOn copyNatives 47 | moeMainReleaseIphonesimulatorXcodeBuild.dependsOn copyNatives 48 | moeMainDebugIphonesimulatorXcodeBuild.dependsOn copyNatives 49 | 50 | // Setup Eclipse 51 | eclipse { 52 | // Set Multi-OS Engine nature 53 | project { 54 | natures 'org.multi-os-engine.project' 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/robovm.xml: -------------------------------------------------------------------------------- 1 | 2 | ${app.executable} 3 | ${app.mainclass} 4 | ios 5 | thumbv7 6 | ios 7 | Info.plist.xml 8 | 9 | 10 | ../android/assets 11 | 12 | ** 13 | 14 | true 15 | 16 | 17 | data 18 | 19 | 20 | 21 | com.badlogic.gdx.scenes.scene2d.ui.* 22 | com.badlogic.gdx.graphics.g3d.particles.** 23 | com.android.okhttp.HttpHandler 24 | com.android.okhttp.HttpsHandler 25 | com.android.org.conscrypt.** 26 | com.android.org.bouncycastle.jce.provider.BouncyCastleProvider 27 | com.android.org.bouncycastle.jcajce.provider.keystore.BC$Mappings 28 | com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi 29 | com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std 30 | com.android.org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi 31 | com.android.org.bouncycastle.crypto.digests.AndroidDigestFactoryOpenSSL 32 | org.apache.harmony.security.provider.cert.DRLCertFactory 33 | org.apache.harmony.security.provider.crypto.CryptoProvider 34 | 35 | 36 | z 37 | 38 | 39 | UIKit 40 | OpenGLES 41 | QuartzCore 42 | CoreGraphics 43 | OpenAL 44 | AudioToolbox 45 | AVFoundation 46 | 47 | 48 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/entities/Player.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.entities 2 | 3 | import com.badlogic.gdx.graphics.g2d.TextureRegion 4 | import com.badlogic.gdx.math.Vector2 5 | import com.badlogic.gdx.physics.box2d.Body 6 | import com.badlogic.gdx.physics.box2d.Filter 7 | import io.wasin.blockbunny.Game 8 | 9 | /** 10 | * Created by haxpor on 5/17/17. 11 | */ 12 | class Player(body: Body) : B2DSprite(body) { 13 | 14 | private var numCrystals: Int = 0 15 | private var totalCrystals: Int = 0 16 | var died: Boolean = false 17 | private set 18 | 19 | init { 20 | val tex = Game.res.getTexture("bunny") 21 | val sprites: Array = TextureRegion.split(tex, 32, 32)[0] 22 | setAnimation(sprites, 1 / 12f) 23 | } 24 | 25 | fun collectCrystal() { 26 | numCrystals++ 27 | } 28 | 29 | fun getNumCrystals(): Int { 30 | return numCrystals 31 | } 32 | 33 | fun setTotalCrystals(value: Int) { 34 | totalCrystals = value 35 | } 36 | 37 | fun getTotalCrystals(): Int { 38 | return totalCrystals 39 | } 40 | 41 | fun makeDirectDie() { 42 | died = true 43 | } 44 | 45 | fun actDie() { 46 | // set mask bit to none 47 | // player will not anymore collide with anything 48 | val center = body.fixtureList.first() 49 | val foot = body.fixtureList[1] 50 | val front = body.fixtureList[2] 51 | 52 | var tmpFilterData: Filter 53 | 54 | tmpFilterData = center.filterData 55 | tmpFilterData.maskBits = 0 56 | center.filterData = tmpFilterData 57 | 58 | tmpFilterData = foot.filterData 59 | tmpFilterData.maskBits = 0 60 | foot.filterData = tmpFilterData 61 | 62 | tmpFilterData = front.filterData 63 | tmpFilterData.maskBits = 0 64 | front.filterData = tmpFilterData 65 | 66 | // apply force upward and slightly back 67 | body.applyForce(Vector2(-70f, 250f), body.getWorldPoint(Vector2(1f, 1f)), true) 68 | died = true 69 | } 70 | } -------------------------------------------------------------------------------- /android/assets/maps/level3.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 7 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,2,2,2,2, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,0,0,3,3,3,0,0,1,1,1,0,0,2,2,2,2, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,0,0,3,3,3,0,0,1,1,1,0,0,2,2,2,2, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,3,3,3,0,0,3,3,3,0,0,1,1,1,0,0,2,2,2,2, 12 | 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,0,0,3,3,3,0,0,3,3,3,0,0,1,1,1,0,0,2,2,2,2, 13 | 1,1,1,1,1,1,1,1,1,1,0,0,2,2,2,2,2,2,2,0,0,3,3,3,0,0,3,3,3,0,0,1,1,1,0,0,2,2,2,2 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/Content.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.audio.Music 5 | import com.badlogic.gdx.audio.Sound 6 | import com.badlogic.gdx.graphics.Texture 7 | import com.badlogic.gdx.utils.Disposable 8 | 9 | /** 10 | * Created by haxpor on 5/17/17. 11 | */ 12 | class Content: Disposable { 13 | 14 | private var textures: HashMap = HashMap() 15 | private var sounds: HashMap = HashMap() 16 | private var musics: HashMap = HashMap() 17 | 18 | // ** Textures ** 19 | fun loadTexture(path: String, key: String) { 20 | val tex = Texture(Gdx.files.internal(path)) 21 | textures.put(key, tex) 22 | } 23 | 24 | fun getTexture(key: String) : Texture? { 25 | return textures[key] 26 | } 27 | 28 | fun removeTexture(key: String) { 29 | val tex = textures[key] 30 | if (tex != null) { 31 | textures.remove(key) 32 | tex.dispose() 33 | } 34 | } 35 | 36 | // ** Sounds ** // 37 | fun loadSound(path: String, key: String) { 38 | val s = Gdx.audio.newSound(Gdx.files.internal(path)) 39 | sounds.put(key, s) 40 | } 41 | 42 | fun getSound(key: String): Sound? { 43 | return sounds[key] 44 | } 45 | 46 | fun removeSound(key: String) { 47 | val s = sounds[key] 48 | if (s != null) { 49 | sounds.remove(key) 50 | s.dispose() 51 | } 52 | } 53 | 54 | // ** Musics ** // 55 | fun loadMusic(path: String, key: String) { 56 | val m = Gdx.audio.newMusic(Gdx.files.internal(path)) 57 | musics.put(key, m) 58 | } 59 | 60 | fun getMusic(key: String): Music? { 61 | return musics[key] 62 | } 63 | 64 | fun removeMusic(key: String) { 65 | val m = musics[key] 66 | if (m != null) { 67 | musics.remove(key) 68 | m.dispose() 69 | } 70 | } 71 | 72 | override fun dispose() { 73 | // remove all resource 74 | textures.keys.toSet().asIterable().forEach { removeTexture(it) } 75 | 76 | // music and sound will be automatically removed 77 | } 78 | } -------------------------------------------------------------------------------- /android/assets/maps/level7.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,3,3,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,3,3,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,3,3,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,3,3,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,0,3,3,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,2,2,0,0,0,0,1,1,0,0,0,0,0,0, 15 | 1,1,1,1,1,1,1,1,3,3,3,3,3,3,2,2,2,2,2,2,1,1,1,1,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/entities/HUD.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.entities 2 | 3 | import com.badlogic.gdx.graphics.g2d.BitmapFont 4 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 5 | import com.badlogic.gdx.graphics.g2d.TextureRegion 6 | import com.badlogic.gdx.utils.Disposable 7 | import io.wasin.blockbunny.Game 8 | import io.wasin.blockbunny.handlers.B2DVars 9 | import kotlin.experimental.and 10 | 11 | /** 12 | * Created by haxpor on 5/18/17. 13 | */ 14 | class HUD(player: Player): Disposable { 15 | private var player: Player = player 16 | private var blocks: Array = Array(3, { i -> null }) 17 | private var backBase: TextureRegion 18 | private var crystal: TextureRegion 19 | private var font: BitmapFont 20 | 21 | init { 22 | var tex = Game.res.getTexture("hud") 23 | 24 | for (i in 0..2) { 25 | blocks[i] = TextureRegion(tex, 32 + i*16, 0, 16, 16) 26 | } 27 | 28 | backBase = TextureRegion(tex, 0, 0, 32, 32) 29 | crystal = TextureRegion(tex, 80, 0, 16, 16) 30 | 31 | font = BitmapFont() 32 | } 33 | 34 | fun render(sb: SpriteBatch) { 35 | // draw base 36 | sb.draw(backBase, 40f - backBase.regionWidth/4f, 200f - backBase.regionHeight/4f) 37 | 38 | // draw current active platform switching 39 | val bits = player.body.fixtureList.first().filterData.maskBits 40 | 41 | if ((bits and B2DVars.BIT_RED) != 0.toShort()) { 42 | sb.draw(blocks[0], 40f, 200f) 43 | } 44 | if ((bits and B2DVars.BIT_GREEN) != 0.toShort()) { 45 | sb.draw(blocks[1], 40f, 200f) 46 | } 47 | if ((bits and B2DVars.BIT_BLUE) != 0.toShort()) { 48 | sb.draw(blocks[2], 40f, 200f) 49 | } 50 | 51 | // draw crystals 52 | sb.draw(crystal, 40f - backBase.regionWidth/4f + 66f + crystal.regionWidth/2f, 200f - backBase.regionHeight/4f + crystal.regionHeight/2f) 53 | 54 | // draw text 55 | font.draw(sb, "${player.getNumCrystals()} / ${player.getTotalCrystals()}", 40f - backBase.regionWidth/4f + 66f + crystal.regionWidth + 30f, 200f + backBase.regionHeight/4f + crystal.regionHeight/2f - 2f) 56 | } 57 | 58 | override fun dispose() { 59 | font.dispose() 60 | } 61 | } -------------------------------------------------------------------------------- /android/assets/maps/level6.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,3,0,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0, 13 | 3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,0,0,0,0,3,3,3,3,3,3,3,1,1,1,1,0,1,1,1,1,1,1,1,1,1, 14 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,3,3,3,3, 15 | 1,1,1,1,1,1,1,1,2,2,2,2,2,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Java 2 | 3 | *.class 4 | *.war 5 | *.ear 6 | hs_err_pid* 7 | 8 | ## Robovm 9 | /ios/robovm-build/ 10 | 11 | ## GWT 12 | /html/war/ 13 | /html/gwt-unitCache/ 14 | .apt_generated/ 15 | .gwt/ 16 | gwt-unitCache/ 17 | www-test/ 18 | .gwt-tmp/ 19 | 20 | ## Android Studio and Intellij and Android in general 21 | /android/libs/armeabi/ 22 | /android/libs/armeabi-v7a/ 23 | /android/libs/arm64-v8a/ 24 | /android/libs/x86/ 25 | /android/libs/x86_64/ 26 | /android/gen/ 27 | .idea/ 28 | *.ipr 29 | *.iws 30 | *.iml 31 | /android/out/ 32 | com_crashlytics_export_strings.xml 33 | 34 | ## Eclipse 35 | 36 | .classpath 37 | .project 38 | .metadata/ 39 | /android/bin/ 40 | /core/bin/ 41 | /desktop/bin/ 42 | /html/bin/ 43 | /ios/bin/ 44 | /ios-moe/bin/ 45 | *.tmp 46 | *.bak 47 | *.swp 48 | *~.nib 49 | .settings/ 50 | .loadpath 51 | .externalToolBuilders/ 52 | *.launch 53 | 54 | ## NetBeans 55 | 56 | /nbproject/private/ 57 | /android/nbproject/private/ 58 | /core/nbproject/private/ 59 | /desktop/nbproject/private/ 60 | /html/nbproject/private/ 61 | /ios/nbproject/private/ 62 | /ios-moe/nbproject/private/ 63 | 64 | /build/ 65 | /android/build/ 66 | /core/build/ 67 | /desktop/build/ 68 | /html/build/ 69 | /ios/build/ 70 | /ios-moe/build/ 71 | 72 | /nbbuild/ 73 | /android/nbbuild/ 74 | /core/nbbuild/ 75 | /desktop/nbbuild/ 76 | /html/nbbuild/ 77 | /ios/nbbuild/ 78 | /ios-moe/nbbuild/ 79 | 80 | /dist/ 81 | /android/dist/ 82 | /core/dist/ 83 | /desktop/dist/ 84 | /html/dist/ 85 | /ios/dist/ 86 | /ios-moe/dist/ 87 | 88 | /nbdist/ 89 | /android/nbdist/ 90 | /core/nbdist/ 91 | /desktop/nbdist/ 92 | /html/nbdist/ 93 | /ios/nbdist/ 94 | /ios-moe/nbdist/ 95 | 96 | nbactions.xml 97 | nb-configuration.xml 98 | 99 | ## Gradle 100 | 101 | /local.properties 102 | .gradle/ 103 | gradle-app.setting 104 | /build/ 105 | /android/build/ 106 | /core/build/ 107 | /desktop/build/ 108 | /html/build/ 109 | /ios/build/ 110 | /ios-moe/build/ 111 | 112 | ## OS Specific 113 | .DS_Store 114 | Thumbs.db 115 | 116 | ## iOS 117 | /ios/xcode/*.xcodeproj/* 118 | !/ios/xcode/*.xcodeproj/xcshareddata 119 | !/ios/xcode/*.xcodeproj/project.pbxproj 120 | /ios/xcode/native/ 121 | 122 | /ios-moe/xcode/*.xcodeproj/* 123 | !/ios-moe/xcode/*.xcodeproj/xcshareddata 124 | !/ios-moe/xcode/*.xcodeproj/project.pbxproj 125 | /ios-moe/xcode/native/ 126 | 127 | ## player's save file 128 | # ignore it to avoid problem of unintentional release player's save file in production 129 | android/assets/player.json 130 | -------------------------------------------------------------------------------- /android/assets/maps/level8.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,2,2,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,1,1, 14 | 0,0,0,0,0,0,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,0,0,0,0, 15 | 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/LevelButton.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.graphics.OrthographicCamera 4 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 5 | import com.badlogic.gdx.graphics.g2d.TextureRegion 6 | import com.badlogic.gdx.math.Rectangle 7 | import com.badlogic.gdx.math.Vector2 8 | import com.badlogic.gdx.math.Vector3 9 | import com.badlogic.gdx.utils.viewport.Viewport 10 | 11 | /** 12 | * Created by haxpor on 5/22/17. 13 | */ 14 | class LevelButton(textureRegion: TextureRegion, levelNumber: Int, isClear: Boolean, x: Float, y: Float) { 15 | 16 | private var region: TextureRegion = textureRegion 17 | var levelNumber: Int = levelNumber 18 | private set 19 | var isClear: Boolean = isClear 20 | private set 21 | private var listener: ((Int) -> Unit)? = null 22 | private var bounds: Rectangle 23 | var position: Vector2 = Vector2(x, y) 24 | private set 25 | 26 | init { 27 | bounds = Rectangle(x - region.regionWidth/2f, y - region.regionHeight/2f, region.regionWidth.toFloat(), region.regionHeight.toFloat()) 28 | } 29 | 30 | fun setOnClickListener(listener: (Int) -> Unit) { 31 | this.listener = listener 32 | } 33 | 34 | fun update(cam: OrthographicCamera, viewport: Viewport, dt: Float) { 35 | 36 | if (BBInput.isPressed()) { 37 | // convert from screen position into world position to check collision (clicking) 38 | val screenCoor = Vector3(BBInput.screenX.toFloat(), BBInput.screenY.toFloat(), 0f) 39 | val worldCoor = cam.unproject(screenCoor, viewport.screenX.toFloat(), viewport.screenY.toFloat(), viewport.screenWidth.toFloat(), viewport.screenHeight.toFloat()) 40 | 41 | if (bounds.contains(worldCoor.x, worldCoor.y)) { 42 | listener?.invoke(levelNumber) 43 | } 44 | } 45 | } 46 | 47 | fun render(textRenderer: TextRenderer, sb: SpriteBatch) { 48 | // draw base 49 | sb.draw(region, position.x - region.regionWidth/2f, position.y - region.regionHeight/2f) 50 | // draw text on top based on whether the level is clear or not 51 | if (isClear) { 52 | // draw at the half-center of the base 53 | // spare another part for indication of level cleared by slash 54 | textRenderer.renderNumber(levelNumber, position.x, position.y + textRenderer.fontHalfHeight, sb) 55 | textRenderer.renderSlash(position.x, position.y - textRenderer.fontHeight + 2f, sb) 56 | } 57 | else { 58 | // draw at the center of the base 59 | textRenderer.renderNumber(levelNumber, position.x, position.y, sb) 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/states/GameState.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.states 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.OrthographicCamera 5 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 6 | import com.badlogic.gdx.utils.viewport.FitViewport 7 | import com.badlogic.gdx.utils.viewport.Viewport 8 | import io.wasin.blockbunny.Game 9 | import io.wasin.blockbunny.handlers.GameStateManager 10 | 11 | /** 12 | * Created by haxpor on 5/14/17. 13 | */ 14 | abstract class GameState(gsm: GameStateManager) { 15 | protected var gsm: GameStateManager = gsm 16 | 17 | lateinit protected var camViewport: Viewport 18 | lateinit protected var hudViewport: Viewport 19 | 20 | // for convenient in reference and use in derived class 21 | protected val game: Game = gsm.game 22 | protected val sb: SpriteBatch = gsm.game.sb 23 | lateinit protected var cam: OrthographicCamera 24 | lateinit protected var hudCam: OrthographicCamera 25 | 26 | init { 27 | setupCamera(Game.V_WIDTH, Game.V_HEIGHT) 28 | setupViewport(cam, hudCam, Game.V_WIDTH, Game.V_HEIGHT) 29 | // always update viewport 30 | camViewport.update(Gdx.app.graphics.width, Gdx.app.graphics.height) 31 | hudViewport.update(Gdx.app.graphics.width, Gdx.app.graphics.height, true) 32 | } 33 | 34 | abstract fun handleInput() 35 | abstract fun update(dt: Float) 36 | abstract fun render() 37 | abstract fun dispose() 38 | abstract fun resize_user(width: Int, height: Int) 39 | 40 | fun resize(width: Int, height: Int) { 41 | camViewport.update(width, height) 42 | hudViewport.update(width, height, true) 43 | 44 | resize_user(width, height) 45 | } 46 | 47 | /** 48 | * Set up cam, and hudCam. 49 | * If needed to create a different type of camera and viewport, then override and implement it in 50 | * GameState class. 51 | */ 52 | open protected fun setupCamera(viewportWidth: Float, viewportHeight: Float) { 53 | // set up cam 54 | cam = OrthographicCamera() 55 | cam.setToOrtho(false, viewportWidth, viewportHeight) 56 | 57 | // set up hud-cam 58 | hudCam = OrthographicCamera() 59 | hudCam.setToOrtho(false, viewportWidth, viewportHeight) 60 | } 61 | 62 | /** 63 | * Set up camViewport, and hudViewport. 64 | * If needed to create a different type of viewport, then override and implement it in GameState class. 65 | */ 66 | open protected fun setupViewport(cam: OrthographicCamera, hudCam: OrthographicCamera, viewportWidth: Float, viewportHeight: Float) { 67 | camViewport = FitViewport(viewportWidth, viewportHeight, cam) 68 | hudViewport = FitViewport(viewportWidth, viewportHeight, hudCam) 69 | } 70 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "screenX%~1" == "screenX" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/states/Score.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.states 2 | 3 | import com.badlogic.gdx.graphics.OrthographicCamera 4 | import com.badlogic.gdx.graphics.g2d.BitmapFont 5 | import com.badlogic.gdx.graphics.g2d.GlyphLayout 6 | import com.badlogic.gdx.utils.viewport.ExtendViewport 7 | import io.wasin.blockbunny.Game 8 | import io.wasin.blockbunny.handlers.BBInput 9 | import io.wasin.blockbunny.handlers.GameStateManager 10 | 11 | /** 12 | * Created by haxpor on 5/30/17. 13 | */ 14 | class Score(failed: Boolean, crystalsAmount: Int, maxCrystalAmount: Int, gsm: GameStateManager): GameState(gsm) { 15 | 16 | private var font: BitmapFont 17 | 18 | private val failedLine1: String = "Game Over" 19 | private val failedLine2: String = "Press any key to continue" 20 | 21 | private val successLine1: String = "Level Clear!" 22 | private val successLine2: String = "Press any key to continue" 23 | 24 | private val glyph1: GlyphLayout 25 | private val glyph2: GlyphLayout 26 | 27 | private val textMargin: Float = 5f 28 | 29 | constructor(gsm: GameStateManager): this(true, 0, 0, gsm) {} 30 | 31 | init { 32 | font = BitmapFont() 33 | 34 | glyph1 = GlyphLayout() 35 | glyph2 = GlyphLayout() 36 | 37 | if (failed) { 38 | glyph1.setText(font, failedLine1) 39 | glyph2.setText(font, failedLine2) 40 | } 41 | else { 42 | glyph1.setText(font, successLine1) 43 | glyph2.setText(font, successLine2) 44 | } 45 | } 46 | 47 | override fun handleInput() { 48 | if (BBInput.isPressed(BBInput.BUTTON1) || BBInput.isPressed(BBInput.BUTTON2) || 49 | BBInput.isMouseDown(BBInput.MOUSE_BUTTON_LEFT) || 50 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_2)) { 51 | // go back to level selection 52 | gsm.setState(GameStateManager.LEVEL_SELECTION) 53 | } 54 | } 55 | 56 | override fun update(dt: Float) { 57 | handleInput() 58 | } 59 | 60 | override fun render() { 61 | sb.projectionMatrix = hudCam.combined 62 | hudViewport.apply(true) 63 | 64 | sb.begin() 65 | font.draw(sb, glyph1, hudCam.viewportWidth/2f - glyph1.width/2f, hudCam.viewportHeight/2f + glyph1.height/2f + textMargin/2f) 66 | font.draw(sb, glyph2, hudCam.viewportWidth/2f - glyph2.width/2f, hudCam.viewportHeight/2f - glyph2.height/2f - textMargin/2f) 67 | sb.end() 68 | } 69 | 70 | override fun dispose() { 71 | font.dispose() 72 | } 73 | 74 | override fun resize_user(width: Int, height: Int) { 75 | 76 | } 77 | 78 | override fun setupViewport(cam: OrthographicCamera, hudCam: OrthographicCamera, viewportWidth: Float, viewportHeight: Float) { 79 | camViewport = ExtendViewport(viewportWidth, viewportHeight, cam) 80 | hudViewport = ExtendViewport(viewportWidth, viewportHeight, hudCam) 81 | } 82 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | donate 2 | 3 | # blockbunny 4 | 5 | Libgdx-based game for Android, iOS, and PC following the tutorial by [ForeignGuyMike](https://www.youtube.com/channel/UC_IV37n-uBpRp64hQIwywWQ) on youtube channel starting at [this](https://www.youtube.com/watch?v=85A1w1iD2oA) but port to Kotlin with improvements, controller support, and mobile ready (iOS via MOE). 6 | 7 | # Overview 8 | 9 | This project follows along with tutorial but code written in Kotlin `1.1.2-3` on Android Studio `2.3.2` with Gradle `2.14.1`. 10 | 11 | The assets and idea follows the original project which written in Java on Eclipse; originally based on desktop only, this project will make it buildable and runnable on Android, iOS (via multi-OS engine), and PC. 12 | 13 | # Changes 14 | 15 | * Ported to Kotlin; initially closely following tutorial but latter slightly diverse 16 | * Added controller (mainly tested with Xbox360 controller) support across the game 17 | * Added 4-directional selection for level in level selection screen 18 | * Added save file to keep track of player's progress in JSON format (intentionally without encryption) 19 | * iOS buildable and runnable on simulator and real device with Multi-OS engine version `1.3.6`. 20 | * Different level from original 21 | * Support wide-screen in gameplay session 22 | * Optimized creation process for tiles, only a single layer and checking against tile's ID 23 | 24 | # What It Looks Like 25 | 26 | ![Blockbunny in action 1](http://i.imgur.com/05P8lh8.gif) 27 | 28 | ![Blockbunny in action 2](http://i.imgur.com/k98jwnl.gif) 29 | 30 | ![Blockbunny with controller](http://i.imgur.com/tJYqnam.gif) 31 | 32 | # What's Next? 33 | 34 | * Add remaining level of 13-15 (as of now it's just a copied of level 12 to prevent crash if select on such levels) 35 | 36 | # Button Control 37 | 38 | ## Desktop (without controller) 39 | 40 | * `Z` for jumping or enter 41 | * `X` for switch block 42 | * `Left`, `Right`, `Up`, `Down` for selecting which level to play in level selection screen 43 | 44 | ## Desktop with controller 45 | 46 | This is Xbox360 layout, currently the game has no configuration screen to map button to other controllers' layout. 47 | 48 | * `A` for jumping or enter 49 | * `B` for switching block 50 | * `D-Pad` for selecting which level to play in level selection screen 51 | 52 | ## Mobile (Android and iOS) 53 | 54 | ### Gameplay screen 55 | 56 | * Touch on left side of the screen to jump 57 | * Touch on right side of the screen to switch block 58 | 59 | ### Other screens 60 | 61 | * Touch to enter 62 | * Touch to select which level to play in level selection screen 63 | 64 | # Credits 65 | 66 | Big shout out to *ForeignGuyMike* for making a great tutorial video that this project builds upon on top of that. 67 | 68 | # License 69 | 70 | [MIT](https://github.com/haxpor/blockbunny/blob/master/LICENSE), Wasin Thonkaew 71 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/MyContactListener.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.physics.box2d.* 4 | import io.wasin.blockbunny.Game 5 | 6 | /** 7 | * Created by haxpor on 5/16/17. 8 | */ 9 | class MyContactListener : ContactListener { 10 | 11 | private var numFootContacts: Int = 0 12 | var bodiesToRemove: MutableList = mutableListOf() 13 | private set 14 | 15 | var playerOnGround: Boolean = false 16 | get() = numFootContacts > 0 17 | 18 | var playerFrontCollided: Boolean = false 19 | var playerBackCollided: Boolean = false 20 | var playerCollidedWithBomb: Boolean = false 21 | 22 | override fun beginContact(contact: Contact?) { 23 | val fa: Fixture? = contact?.fixtureA 24 | val fb: Fixture? = contact?.fixtureB 25 | 26 | if (fa == null || fb == null) return 27 | 28 | if (fa!!.userData != null && fa!!.userData.equals("foot")) { 29 | numFootContacts++ 30 | } 31 | if (fb!!.userData != null && fb!!.userData.equals("foot")) { 32 | numFootContacts++ 33 | } 34 | 35 | if (fa!!.userData != null && fa!!.userData.equals("crystal")) { 36 | Game.res.getSound("crystal")!!.play() 37 | bodiesToRemove.add(fa.body) 38 | } 39 | if (fb!!.userData != null && fb!!.userData.equals("crystal")) { 40 | Game.res.getSound("crystal")!!.play() 41 | bodiesToRemove.add(fb.body) 42 | } 43 | 44 | if (fa!!.userData != null && fa!!.userData.equals("bomb")) { 45 | playerCollidedWithBomb = true 46 | } 47 | if (fb!!.userData != null && fb!!.userData.equals("bomb")) { 48 | playerCollidedWithBomb = true 49 | } 50 | 51 | if (fa!!.userData != null && fa!!.userData.equals("front")) { 52 | playerFrontCollided = true 53 | } 54 | if (fb!!.userData != null && fb!!.userData.equals("front")) { 55 | playerFrontCollided = true 56 | } 57 | 58 | if (fa!!.userData != null && fa!!.userData.equals("back")) { 59 | playerBackCollided = true 60 | } 61 | if (fb!!.userData != null && fb!!.userData.equals("back")) { 62 | playerBackCollided = true 63 | } 64 | } 65 | 66 | override fun endContact(contact: Contact?) { 67 | val fa: Fixture? = contact?.fixtureA 68 | val fb: Fixture? = contact?.fixtureB 69 | 70 | if (fa == null || fb == null) return 71 | 72 | if (fa!!.userData != null && fa!!.userData.equals("foot")) { 73 | numFootContacts-- 74 | } 75 | if (fb!!.userData != null && fb!!.userData.equals("foot")) { 76 | numFootContacts-- 77 | } 78 | } 79 | 80 | override fun preSolve(contact: Contact?, oldManifold: Manifold?) { 81 | 82 | } 83 | 84 | override fun postSolve(contact: Contact?, impulse: ContactImpulse?) { 85 | 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/GameStateManager.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.utils.Disposable 4 | import io.wasin.blockbunny.Game 5 | import io.wasin.blockbunny.states.* 6 | 7 | import java.util.Stack 8 | 9 | /** 10 | * Created by haxpor on 5/14/17. 11 | */ 12 | 13 | class GameStateManager(game: Game): Disposable { 14 | var game: Game 15 | private set 16 | private var gameStates: Stack 17 | 18 | private var isCurrentStateClear: Boolean = false 19 | private var currentStateCystalsAmount: Int = 0 20 | private var currentMaxCrystalAmount: Int = 0 21 | 22 | init { 23 | this.game = game 24 | this.gameStates = Stack() 25 | } 26 | 27 | companion object { 28 | const val PLAY = 5000 29 | const val LEVEL_SELECTION = 5001 30 | const val SCORE = 5002 31 | const val MAINMENU = 5003 32 | } 33 | 34 | fun update(dt: Float) { 35 | this.gameStates.peek().update(dt) 36 | } 37 | 38 | fun resize(width: Int, height: Int) { 39 | this.gameStates.peek().resize(width, height) 40 | } 41 | 42 | fun render() { 43 | for (state in this.gameStates) { 44 | state.render() 45 | } 46 | } 47 | 48 | private fun getState(state: Int): GameState? { 49 | if (state == PLAY) return Play(this) 50 | else if (state == LEVEL_SELECTION) return LevelSelection(this) 51 | else if (state == SCORE) { 52 | if (isCurrentStateClear) { 53 | return Score(false, currentStateCystalsAmount, currentMaxCrystalAmount, this) 54 | } 55 | else { 56 | return Score(this) 57 | } 58 | } 59 | else if (state == MAINMENU) return Mainmenu(this) 60 | return null 61 | } 62 | 63 | fun setState(state: Int) { 64 | // dispose all gamestates first 65 | this.gameStates.forEach { it.dispose() } 66 | // clear all game states 67 | this.gameStates.clear() 68 | // set new one via push 69 | this.pushState(state) 70 | } 71 | 72 | fun pushState(state: Int) { 73 | this.gameStates.push(this.getState(state)) 74 | } 75 | 76 | fun popState() { 77 | val g = this.gameStates.pop() 78 | g.dispose() 79 | } 80 | 81 | fun setCurrentActiveLevelAsGameOver() { 82 | isCurrentStateClear = false 83 | currentStateCystalsAmount = 0 84 | currentMaxCrystalAmount = 0 85 | } 86 | 87 | fun setCurrentActiveLevelAsClear(crystals: Int, maxCrystals: Int) { 88 | isCurrentStateClear = true 89 | currentStateCystalsAmount = crystals 90 | currentMaxCrystalAmount = maxCrystals 91 | } 92 | 93 | fun resetPreviousActiveLevelState() { 94 | setCurrentActiveLevelAsGameOver() 95 | } 96 | 97 | override fun dispose() { 98 | gameStates.forEach { it.dispose() } 99 | } 100 | } -------------------------------------------------------------------------------- /android/assets/maps/level9.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,1,1,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,2,2,2,2, 14 | 0,0,0,0,0,0,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,2,2,0,0,1,1,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3, 15 | 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/Game.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny 2 | 3 | import com.badlogic.gdx.ApplicationAdapter 4 | import com.badlogic.gdx.Gdx 5 | import com.badlogic.gdx.controllers.Controllers 6 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 7 | import io.wasin.blockbunny.handlers.* 8 | 9 | class Game : ApplicationAdapter() { 10 | 11 | lateinit var sb: SpriteBatch 12 | private set 13 | lateinit var gsm: GameStateManager 14 | private set 15 | lateinit var playerSaveFileManager: PlayerSaveFileManager 16 | private set 17 | 18 | companion object { 19 | const val TITLE = "Block Bunny" 20 | const val V_WIDTH = 320f 21 | const val V_HEIGHT = 240f 22 | const val SCALE = 2 23 | 24 | var res: Content = Content() 25 | private set 26 | } 27 | 28 | override fun create() { 29 | 30 | Gdx.input.inputProcessor = BBInputProcessor() 31 | 32 | sb = SpriteBatch() 33 | 34 | gsm = GameStateManager(this) 35 | 36 | // create player's savefile manager with pre-set of savefile's path 37 | playerSaveFileManager = PlayerSaveFileManager(Settings.PLAYER_SAVEFILE_RELATIVE_PATH) 38 | 39 | res.loadTexture("images/bunny.png", "bunny") 40 | res.loadTexture("images/crystal.png", "crystal") 41 | res.loadTexture("images/hud.png", "hud") 42 | res.loadTexture("images/bgs.png", "bgs") 43 | res.loadTexture("images/menu.png", "menu") 44 | res.loadTexture("images/spikes.png", "spikes") 45 | res.loadTexture("images/misc.png", "misc") 46 | 47 | res.loadMusic("music/bbsong.mp3", "bbsong") 48 | res.loadSound("sfx/changeblock.wav", "changeblock") 49 | res.loadSound("sfx/crystal.wav", "crystal") 50 | res.loadSound("sfx/hit.wav", "hit") 51 | res.loadSound("sfx/jump.wav", "jump") 52 | res.loadSound("sfx/levelselect.wav", "levelselect") 53 | 54 | // set to play background music endlessly now 55 | val bgMusic = res.getMusic("bbsong")!! 56 | bgMusic.play() 57 | bgMusic.isLooping = true 58 | 59 | // only this time to check for controller 60 | // if user plug in controller after this then they have to restart the game 61 | setupFirstActiveController() 62 | 63 | // set to begin with Play state 64 | gsm.pushState(GameStateManager.MAINMENU) 65 | } 66 | 67 | private fun setupFirstActiveController() { 68 | if (Controllers.getControllers().count() > 0) { 69 | val bbInputProcessor = Gdx.input.inputProcessor as BBInputProcessor 70 | val controller = Controllers.getControllers().first() 71 | controller.addListener(bbInputProcessor) 72 | bbInputProcessor.setActiveController(controller) 73 | } 74 | } 75 | 76 | override fun render() { 77 | Gdx.graphics.setTitle(TITLE + " -- FPS: " + Gdx.graphics.framesPerSecond) 78 | gsm.update(Gdx.graphics.deltaTime) 79 | gsm.render() 80 | BBInput.update() 81 | } 82 | 83 | override fun dispose() { 84 | gsm.dispose() 85 | sb.dispose() 86 | res.dispose() 87 | } 88 | 89 | override fun resize(width: Int, height: Int) { 90 | gsm.resize(width, height) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /android/assets/maps/level12.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,2,2,2,2,2,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,3,3,3,3,3,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1, 15 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /android/assets/maps/level13.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,2,2,2,2,2,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,3,3,3,3,3,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1, 15 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /android/assets/maps/level14.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,2,2,2,2,2,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,3,3,3,3,3,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1, 15 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /android/assets/maps/level15.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 12 | 0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,0,0,2,2,2,2,2,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 13 | 0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,1,1,1,1,2,2,2,2,2,2,2,2,2,1,1,3,3,3,3,3,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,1, 15 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /android/assets/maps/level10.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,2, 10 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,2, 11 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,2, 12 | 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3, 13 | 0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0, 14 | 0,0,0,0,0,0,0,0,3,3,3,3,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0, 15 | 0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,2,0,0,0,0,0 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | buildToolsVersion "25.0.3" 3 | compileSdkVersion 25 4 | sourceSets { 5 | main { 6 | manifest.srcFile 'AndroidManifest.xml' 7 | java.srcDirs = ['src'] 8 | aidl.srcDirs = ['src'] 9 | renderscript.srcDirs = ['src'] 10 | res.srcDirs = ['res'] 11 | assets.srcDirs = ['assets'] 12 | jniLibs.srcDirs = ['libs'] 13 | } 14 | 15 | instrumentTest.setRoot('tests') 16 | } 17 | packagingOptions { 18 | exclude 'META-INF/robovm/ios/robovm.xml' 19 | } 20 | defaultConfig { 21 | applicationId "io.wasin.blockbunny" 22 | minSdkVersion 9 23 | targetSdkVersion 25 24 | } 25 | } 26 | 27 | 28 | // called every time gradle gets executed, takes the native dependencies of 29 | // the natives configuration, and extracts them to the proper libs/ folders 30 | // so they get packed with the APK. 31 | task copyAndroidNatives() { 32 | file("libs/armeabi/").mkdirs(); 33 | file("libs/armeabi-v7a/").mkdirs(); 34 | file("libs/arm64-v8a/").mkdirs(); 35 | file("libs/x86_64/").mkdirs(); 36 | file("libs/x86/").mkdirs(); 37 | 38 | configurations.natives.files.each { jar -> 39 | def outputDir = null 40 | if(jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a") 41 | if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a") 42 | if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi") 43 | if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64") 44 | if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86") 45 | if(outputDir != null) { 46 | copy { 47 | from zipTree(jar) 48 | into outputDir 49 | include "*.so" 50 | } 51 | } 52 | } 53 | } 54 | 55 | task run(type: Exec) { 56 | def path 57 | def localProperties = project.file("../local.properties") 58 | if (localProperties.exists()) { 59 | Properties properties = new Properties() 60 | localProperties.withInputStream { instr -> 61 | properties.load(instr) 62 | } 63 | def sdkDir = properties.getProperty('sdk.dir') 64 | if (sdkDir) { 65 | path = sdkDir 66 | } else { 67 | path = "$System.env.ANDROID_HOME" 68 | } 69 | } else { 70 | path = "$System.env.ANDROID_HOME" 71 | } 72 | 73 | def adb = path + "/platform-tools/adb" 74 | commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.wasin.blockbunny/io.wasin.blockbunny.AndroidLauncher' 75 | } 76 | 77 | // sets up the Android Eclipse project, using the old Ant based build. 78 | eclipse { 79 | // need to specify Java source sets explicitly, SpringSource Gradle Eclipse plugin 80 | // ignores any nodes added in classpath.file.withXml 81 | sourceSets { 82 | main { 83 | java.srcDirs "src", 'gen' 84 | } 85 | } 86 | 87 | jdt { 88 | sourceCompatibility = 1.6 89 | targetCompatibility = 1.6 90 | } 91 | 92 | classpath { 93 | plusConfigurations += [ project.configurations.compile ] 94 | containers 'com.android.ide.eclipse.adt.ANDROID_FRAMEWORK', 'com.android.ide.eclipse.adt.LIBRARIES' 95 | } 96 | 97 | project { 98 | name = appName + "-android" 99 | natures 'com.android.ide.eclipse.adt.AndroidNature' 100 | buildCommands.clear(); 101 | buildCommand "com.android.ide.eclipse.adt.ResourceManagerBuilder" 102 | buildCommand "com.android.ide.eclipse.adt.PreCompilerBuilder" 103 | buildCommand "org.eclipse.jdt.core.javabuilder" 104 | buildCommand "com.android.ide.eclipse.adt.ApkBuilder" 105 | } 106 | } 107 | 108 | // sets up the Android Idea project, using the old Ant based build. 109 | idea { 110 | module { 111 | sourceDirs += file("src"); 112 | scopes = [ COMPILE: [plus:[project.configurations.compile]]] 113 | 114 | iml { 115 | withXml { 116 | def node = it.asNode() 117 | def builder = NodeBuilder.newInstance(); 118 | builder.current = node; 119 | builder.component(name: "FacetManager") { 120 | facet(type: "android", name: "Android") { 121 | configuration { 122 | option(name: "UPDATE_PROPERTY_FILES", value:"true") 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/BBInput.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.controllers.Controller 4 | 5 | /** 6 | * Created by haxpor on 5/16/17. 7 | */ 8 | class BBInput { 9 | companion object { 10 | 11 | var screenX: Int = 0 12 | var screenY: Int = 0 13 | 14 | var down: Boolean = false 15 | var pdown: Boolean = false 16 | 17 | var mouseDown: Boolean = false 18 | var pMouseDown: Boolean = false 19 | 20 | var controllerDown: Boolean = false 21 | var pControllerDown: Boolean = false 22 | 23 | var controller: Controller? = null 24 | 25 | const val NUM_KEYS: Int = 6 26 | const val BUTTON1: Int = 0 27 | const val BUTTON2: Int = 1 28 | const val BUTTON_LEFT: Int = 2 29 | const val BUTTON_RIGHT: Int = 3 30 | const val BUTTON_UP: Int = 4 31 | const val BUTTON_DOWN: Int = 5 32 | 33 | const val NUM_MOUSE_KEYS: Int = 2 34 | const val MOUSE_BUTTON_LEFT: Int = 0 35 | const val MOUSE_BUTTON_RIGHT: Int = 1 36 | 37 | const val NUM_CONTROLLER_KEYS: Int = 6 38 | const val CONTROLLER_BUTTON_1: Int = 0 39 | const val CONTROLLER_BUTTON_2: Int = 1 40 | const val CONTROLLER_BUTTON_LEFT: Int = 2 41 | const val CONTROLLER_BUTTON_RIGHT: Int = 3 42 | const val CONTROLLER_BUTTON_UP: Int = 4 43 | const val CONTROLLER_BUTTON_DOWN: Int = 5 44 | 45 | var keys: Array = Array(NUM_KEYS, { i -> false}) 46 | var pkeys: Array = Array(NUM_KEYS, { i -> false}) 47 | 48 | var mouseKeys: Array = Array(NUM_MOUSE_KEYS, { i -> false}) 49 | var pMouseKeys: Array = Array(NUM_MOUSE_KEYS, { i -> false}) 50 | 51 | var controllerKeys: Array = Array(NUM_CONTROLLER_KEYS, { i -> false}) 52 | var pControllerKeys: Array = Array(NUM_CONTROLLER_KEYS, { i -> false}) 53 | 54 | fun update() { 55 | // update previous down 56 | pdown = down 57 | pMouseDown = mouseDown 58 | pControllerDown = controllerDown 59 | 60 | for (i in 0..NUM_KEYS-1) { 61 | pkeys[i] = keys[i] 62 | } 63 | 64 | for (i in 0..NUM_MOUSE_KEYS-1) { 65 | pMouseKeys[i] = mouseKeys[i] 66 | } 67 | 68 | for (i in 0..NUM_CONTROLLER_KEYS-1) { 69 | pControllerKeys[i] = controllerKeys[i] 70 | } 71 | } 72 | 73 | /** isDown - specific **/ 74 | fun isDown(i: Int): Boolean { 75 | return keys[i] 76 | } 77 | fun isMouseDown(i: Int): Boolean { 78 | return mouseKeys[i] 79 | } 80 | fun isControllerDown(i: Int): Boolean { 81 | return controllerKeys[i] 82 | } 83 | 84 | /** isPressed - specific **/ 85 | fun isPressed(i: Int): Boolean { 86 | return keys[i] && !pkeys[i] 87 | } 88 | fun isMousePressed(i: Int): Boolean { 89 | return mouseKeys[i] && !pMouseKeys[i] 90 | } 91 | fun isControllerPressed(i: Int): Boolean { 92 | return controllerKeys[i] && !pControllerKeys[i] 93 | } 94 | 95 | /** setKey **/ 96 | fun setKey(i: Int, b: Boolean) { 97 | keys[i] = b 98 | } 99 | fun setMouseKey(i: Int, b: Boolean) { 100 | mouseKeys[i] = b 101 | } 102 | fun setControllerKey(i: Int, b: Boolean) { 103 | controllerKeys[i] = b 104 | } 105 | 106 | /** isDown **/ 107 | fun isDown(): Boolean { 108 | return down 109 | } 110 | fun isMouseDown(): Boolean { 111 | return mouseDown 112 | } 113 | fun isControllerDown(): Boolean { 114 | return controllerDown 115 | } 116 | 117 | /** isPressed **/ 118 | fun isPressed(): Boolean { 119 | return down && !pdown 120 | } 121 | fun isMousePressed(): Boolean { 122 | return mouseDown && !pMouseDown 123 | } 124 | fun isControllerPressed(): Boolean { 125 | return controllerDown && !pControllerDown 126 | } 127 | 128 | /** isReleased **/ 129 | fun isReleased(): Boolean { 130 | return pdown && !down 131 | } 132 | fun isMouseReleased(): Boolean { 133 | return pMouseDown && !mouseDown 134 | } 135 | fun isControllerReleased(): Boolean { 136 | return pControllerDown && !controllerDown 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/PlayerSaveFileManager.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.utils.GdxRuntimeException 5 | import com.badlogic.gdx.utils.Json 6 | import com.badlogic.gdx.utils.JsonWriter 7 | import com.badlogic.gdx.utils.SerializationException 8 | import io.wasin.blockbunny.data.LevelResult 9 | import io.wasin.blockbunny.data.PlayerSave 10 | import io.wasin.blockbunny.data.PlayerSaveCache 11 | import io.wasin.blockbunny.interfaces.ISaveFile 12 | 13 | /** 14 | * Created by haxpor on 5/31/17. 15 | */ 16 | class PlayerSaveFileManager(filePath: String): ISaveFile { 17 | var saveFilePath: String = filePath 18 | private set 19 | var cache: PlayerSaveCache 20 | private set 21 | private var json: Json 22 | 23 | init { 24 | json = Json() 25 | json.setOutputType(JsonWriter.OutputType.json) 26 | json.setUsePrototypes(false) 27 | 28 | cache = PlayerSaveCache() 29 | } 30 | 31 | /** 32 | * @throws GameRuntimeException if there's any error happening 33 | */ 34 | @Throws(GameRuntimeException::class) 35 | fun readSaveFile(): PlayerSave? { 36 | return readSaveFile(saveFilePath) 37 | } 38 | 39 | /** 40 | * @throws GameRuntimeException if there's any error happening 41 | * @throws SerializationException if file's content cannot be parsed back to Json object, file might be corrupted 42 | */ 43 | @Throws(GameRuntimeException::class, SerializationException::class) 44 | override fun readSaveFile(filePath: String): PlayerSave? { 45 | val handle = Gdx.files.local(filePath) 46 | if(!handle.exists()) { 47 | throw GameRuntimeException("Save file doesn't exist at $filePath", GameRuntimeException.SAVE_FILE_NOT_FOUND) 48 | } 49 | 50 | val jsonString = handle.readString() 51 | if (jsonString.length == 0) { 52 | // invalidate internal cached then return null 53 | cache.data = null 54 | throw GameRuntimeException("Save file's content is empty. Save file might be corrupted.", GameRuntimeException.SAVE_FILE_EMPTY_CONTENT) 55 | } 56 | 57 | Gdx.app.log("PlayerSaveFileManager", "save file content") 58 | Gdx.app.log("PlayerSaveFileManager", jsonString) 59 | val playerSave = json.fromJson(PlayerSave::class.java, jsonString) 60 | // syn to internal cache 61 | cache.data = playerSave 62 | 63 | return playerSave 64 | } 65 | 66 | /** 67 | * @throws GameRuntimeException if there's any error haeppning 68 | */ 69 | fun writeSaveFile(data: PlayerSave) { 70 | writeSaveFile(data, saveFilePath) 71 | } 72 | 73 | /** 74 | * @throws GameRuntimeException if there's any error happening 75 | */ 76 | @Throws(GameRuntimeException::class) 77 | override fun writeSaveFile(data: PlayerSave, filePath: String) { 78 | val handle = Gdx.files.local(filePath) 79 | val toWriteString = json.prettyPrint(data) 80 | Gdx.app.log("PlayerSaveFileManager", "Write content") 81 | Gdx.app.log("PlayerSaveFileManager", toWriteString) 82 | 83 | try { 84 | handle.writeString(toWriteString, false) 85 | } 86 | catch(e: GdxRuntimeException) { 87 | throw GameRuntimeException("${e.message}", GameRuntimeException.WRITE_FILE_ERROR) 88 | } 89 | finally { 90 | // sync with internal cache 91 | cache.data = data.copy() 92 | Gdx.app.log("PlayerSaveFileManager", "synced with internal cache") 93 | } 94 | } 95 | 96 | /** 97 | * Update level result for particular level with LevelResult with an option to write updated 98 | * content to the file immediately. 99 | * @throws GameRuntimeException if cache's data is null 100 | */ 101 | @Throws(GameRuntimeException::class) 102 | fun updateLevelResult(level: Int, levelResult: LevelResult, writeImmediately: Boolean) { 103 | 104 | if (cache.data == null) throw GameRuntimeException("Cache's data should not be null prior to calling this method", GameRuntimeException.NULL_ERROR) 105 | 106 | val l = cache.data!!.levelResults[level - 1] 107 | l.clear = levelResult.clear 108 | l.collectedCrystal = levelResult.collectedCrystal 109 | 110 | if (writeImmediately) { 111 | // write out cached data 112 | writeSaveFile(cache.data!!) 113 | } 114 | } 115 | 116 | /** 117 | * Write a fresh save file for specified total number of levels. 118 | * @throws GameRuntimeException if there's any error regarding to I/O operation 119 | */ 120 | fun writeFreshSaveFile(totalLevels: Int) { 121 | val tmpArray = ArrayList() 122 | for (i in 0..totalLevels-1) { 123 | tmpArray.add(LevelResult(false, 0)) 124 | } 125 | val data = PlayerSave(tmpArray.toTypedArray()) 126 | writeSaveFile(data) 127 | 128 | // sync to internal cache 129 | cache.data = data 130 | } 131 | 132 | /** 133 | * Get a copy of level result at specified level. 134 | * @return LevelResult if there is such level result available, otherwise return null which might be of null cache, or index out of bound 135 | */ 136 | fun getLevelResult(level: Int): LevelResult? { 137 | if (cache.data == null) return null 138 | if (level > cache.data!!.levelResults.count()) return null 139 | 140 | return cache.data!!.levelResults[level-1].copy() 141 | } 142 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -screenX "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -screenX "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/assets/maps/level11.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0,0,0,0,0,0,0,2,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,2,0,0,0,0,1,0,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,2,2,2,2,2,2,2,2,2, 9 | 0,0,0,0,0,0,0,2,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,2,0,0,0,0,1,0,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,2,2,2,2,2,2,2,2,2, 10 | 0,0,0,0,0,0,0,2,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,2,0,0,0,0,1,0,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,3,3,3,3,3,3,1,1,1, 11 | 0,0,0,0,0,0,0,2,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,2,0,0,0,0,3,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,1,1,1, 12 | 0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,1,0,0,0,2,0,0,1,1,1,2,2,2,3,3,3, 13 | 0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,1,1,0,0,0,3,3,0,0,0,2,2,0,0,0,1,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,1,1,1,2,2,2,3,3,3, 14 | 0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,1,1,0,0,0,3,3,0,0,0,2,2,0,0,0,1,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,1,1,1,2,2,2,3,3,3, 15 | 1,1,1,1,1,1,1,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,2,2,0,0,0,3,3,0,0,0,1,1,0,0,0,1,1,0,0,0,3,3,0,0,0,2,2,0,0,0,1,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,1,1,1,2,2,2,3,3,3 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/handlers/BBInputProcessor.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.handlers 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.Input 5 | import com.badlogic.gdx.InputAdapter 6 | import com.badlogic.gdx.controllers.Controller 7 | import com.badlogic.gdx.controllers.ControllerListener 8 | import com.badlogic.gdx.controllers.PovDirection 9 | import com.badlogic.gdx.controllers.mappings.Xbox 10 | import com.badlogic.gdx.math.Vector3 11 | 12 | /** 13 | * Created by haxpor on 5/16/17. 14 | */ 15 | class BBInputProcessor : InputAdapter(), ControllerListener { 16 | 17 | /** Keyboard, Mouse, and Touch **/ 18 | override fun mouseMoved(screenX: Int, screenY: Int): Boolean { 19 | BBInput.screenX = screenX 20 | BBInput.screenY = screenY 21 | return true 22 | } 23 | 24 | override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { 25 | // keyboard 26 | BBInput.screenX = screenX 27 | BBInput.screenY = screenY 28 | BBInput.down = true 29 | 30 | // mouse 31 | BBInput.mouseDown = true 32 | if (button == Input.Buttons.LEFT) { 33 | BBInput.setMouseKey(BBInput.MOUSE_BUTTON_LEFT, true) 34 | } 35 | if (button == Input.Buttons.RIGHT) { 36 | BBInput.setMouseKey(BBInput.MOUSE_BUTTON_RIGHT, true) 37 | } 38 | 39 | return true 40 | } 41 | 42 | override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { 43 | // keyboard 44 | BBInput.screenX = screenX 45 | BBInput.screenY = screenY 46 | BBInput.down = false 47 | 48 | // mouse 49 | BBInput.mouseDown = false 50 | if (button == Input.Buttons.LEFT) { 51 | BBInput.setMouseKey(BBInput.MOUSE_BUTTON_LEFT, false) 52 | } 53 | if (button == Input.Buttons.RIGHT) { 54 | BBInput.setMouseKey(BBInput.MOUSE_BUTTON_RIGHT, false) 55 | } 56 | 57 | return true 58 | } 59 | 60 | override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean { 61 | BBInput.screenX = screenX 62 | BBInput.screenY = screenY 63 | BBInput.down = true 64 | BBInput.mouseDown = true 65 | return true 66 | } 67 | 68 | override fun keyDown(keycode: Int): Boolean { 69 | if (keycode == Input.Keys.Z) { 70 | BBInput.setKey(BBInput.BUTTON1, true) 71 | } 72 | if (keycode == Input.Keys.X) { 73 | BBInput.setKey(BBInput.BUTTON2, true) 74 | } 75 | if (keycode == Input.Keys.LEFT) { 76 | BBInput.setKey(BBInput.BUTTON_LEFT, true) 77 | } 78 | if (keycode == Input.Keys.RIGHT) { 79 | BBInput.setKey(BBInput.BUTTON_RIGHT, true) 80 | } 81 | if (keycode == Input.Keys.UP) { 82 | BBInput.setKey(BBInput.BUTTON_UP, true) 83 | } 84 | if (keycode == Input.Keys.DOWN) { 85 | BBInput.setKey(BBInput.BUTTON_DOWN, true) 86 | } 87 | return true 88 | } 89 | 90 | override fun keyUp(keycode: Int): Boolean { 91 | if (keycode == Input.Keys.Z) { 92 | BBInput.setKey(BBInput.BUTTON1, false) 93 | } 94 | if (keycode == Input.Keys.X) { 95 | BBInput.setKey(BBInput.BUTTON2, false) 96 | } 97 | if (keycode == Input.Keys.LEFT) { 98 | BBInput.setKey(BBInput.BUTTON_LEFT, false) 99 | } 100 | if (keycode == Input.Keys.RIGHT) { 101 | BBInput.setKey(BBInput.BUTTON_RIGHT, false) 102 | } 103 | if (keycode == Input.Keys.UP) { 104 | BBInput.setKey(BBInput.BUTTON_UP, false) 105 | } 106 | if (keycode == Input.Keys.DOWN) { 107 | BBInput.setKey(BBInput.BUTTON_DOWN, false) 108 | } 109 | return true 110 | } 111 | 112 | /** Contrllers **/ 113 | override fun buttonDown(controller: Controller?, buttonCode: Int): Boolean { 114 | 115 | // only consider for active controller 116 | // return false to allow other system to handle event too 117 | if (controller != BBInput.controller || controller == null) return false 118 | 119 | BBInput.controllerDown = true 120 | 121 | // TODO: we fix to use xbox360 layout for mapping here, if *you* have time add configuration for user to map key and let the game use that configuration file 122 | if (buttonCode == Xbox.B) { 123 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_1, true) 124 | } 125 | if (buttonCode == Xbox.A) { 126 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_2, true) 127 | } 128 | if (buttonCode == Xbox.DPAD_LEFT) { 129 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_LEFT, true) 130 | } 131 | if (buttonCode == Xbox.DPAD_RIGHT) { 132 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_RIGHT, true) 133 | } 134 | if (buttonCode == Xbox.DPAD_UP) { 135 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_UP, true) 136 | } 137 | if (buttonCode == Xbox.DPAD_DOWN) { 138 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_DOWN, true) 139 | } 140 | return true 141 | } 142 | 143 | override fun buttonUp(controller: Controller?, buttonCode: Int): Boolean { 144 | // only consider for active controller 145 | // return false to allow other system to handle event too 146 | if (controller != BBInput.controller || controller == null) return false 147 | 148 | BBInput.controllerDown = false 149 | 150 | // TODO: we fix to use xbox360 layout for mapping here, if *you* have time add configuration for user to map key and let the game use that configuration file 151 | if (buttonCode == Xbox.B) { 152 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_1, false) 153 | } 154 | if (buttonCode == Xbox.A) { 155 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_2, false) 156 | } 157 | if (buttonCode == Xbox.DPAD_LEFT) { 158 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_LEFT, false) 159 | } 160 | if (buttonCode == Xbox.DPAD_RIGHT) { 161 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_RIGHT, false) 162 | } 163 | if (buttonCode == Xbox.DPAD_UP) { 164 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_UP, false) 165 | } 166 | if (buttonCode == Xbox.DPAD_DOWN) { 167 | BBInput.setControllerKey(BBInput.CONTROLLER_BUTTON_DOWN, false) 168 | } 169 | return true 170 | } 171 | 172 | override fun axisMoved(controller: Controller?, axisCode: Int, value: Float): Boolean { 173 | // ignore axis 174 | // no need to return false to let other system handle it further 175 | return true 176 | } 177 | 178 | override fun povMoved(controller: Controller?, povCode: Int, value: PovDirection?): Boolean { 179 | return true 180 | } 181 | 182 | override fun xSliderMoved(controller: Controller?, sliderCode: Int, value: Boolean): Boolean { 183 | return true 184 | } 185 | 186 | override fun ySliderMoved(controller: Controller?, sliderCode: Int, value: Boolean): Boolean { 187 | return true 188 | } 189 | 190 | override fun accelerometerMoved(controller: Controller?, accelerometerCode: Int, value: Vector3?): Boolean { 191 | return true 192 | } 193 | 194 | override fun connected(controller: Controller?) { 195 | 196 | Gdx.app.log("BBInputProcessor", "New controller connected ${controller?.name}") 197 | 198 | if (controller == null) return 199 | 200 | // only consider only one controller 201 | // consecutive controller won't be effect for this game 202 | if (BBInput.controller == null) { 203 | BBInput.controller = controller 204 | } 205 | } 206 | 207 | override fun disconnected(controller: Controller?) { 208 | 209 | Gdx.app.log("BBInputProcessor", "Controller disconnected ${controller?.name}") 210 | 211 | if (controller == null) return 212 | 213 | if (BBInput.controller == controller && BBInput.controller != null) { 214 | // remove previous listener 215 | BBInput.controller!!.removeListener(this) 216 | // reset controller 217 | BBInput.controller = null 218 | } 219 | } 220 | 221 | fun setActiveController(controller: Controller) { 222 | BBInput.controller = controller 223 | } 224 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/states/LevelSelection.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.states 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.GL20 5 | import com.badlogic.gdx.graphics.g2d.SpriteBatch 6 | import com.badlogic.gdx.graphics.g2d.TextureRegion 7 | import io.wasin.blockbunny.Game 8 | import io.wasin.blockbunny.handlers.* 9 | 10 | /** 11 | * Created by haxpor on 5/22/17. 12 | */ 13 | class LevelSelection(gsm: GameStateManager): GameState(gsm) { 14 | 15 | companion object { 16 | const private val LEVEL_PER_PAGE: Int = 15 17 | const private val ROW_PER_PAGE: Int = 3 18 | const private val COLUMN_PER_PAGE: Int = 5 19 | private var sPreviousActiveSelectionIndex: Int = 0 20 | } 21 | 22 | private var bg: Background 23 | lateinit private var textRenderer: TextRenderer 24 | lateinit private var levelButtons: Array 25 | private var activeSelectionTextureRegion: TextureRegion 26 | private var activeSelectionIndex: Int = sPreviousActiveSelectionIndex 27 | private var activePage: Int = 1 28 | 29 | init { 30 | var bgTexture = Game.res.getTexture("bgs")!! 31 | var bgTextureRegion = TextureRegion(bgTexture, 0, 0, bgTexture.width, bgTexture.height / 3) 32 | bg = Background(bgTextureRegion, hudCam, 0f) 33 | 34 | // this applies for keyboard, and controller 35 | var tex = Game.res.getTexture("misc")!! 36 | activeSelectionTextureRegion = TextureRegion(tex, 0, 0, tex.width, tex.height) 37 | 38 | createTextRenderer() 39 | createLevelButtons(Settings.TOTAL_LEVELS) 40 | } 41 | 42 | private fun createTextRenderer() { 43 | val texture = Game.res.getTexture("hud")!! 44 | var numberRegions = arrayListOf() 45 | 46 | // fixed size for font 47 | val fontWidth = 9 48 | val fontHeight = 9 49 | 50 | // 1. number regions 51 | // top row for 6 elements 52 | for (i in 0..5) { 53 | numberRegions.add(TextureRegion(texture, 32 + (i*fontWidth), 16, fontWidth, fontHeight)) 54 | } 55 | 56 | // bottom row for 4 elements 57 | for (i in 0..3) { 58 | numberRegions.add(TextureRegion(texture, 32 + (i*fontWidth), 25, fontWidth, fontHeight)) 59 | } 60 | 61 | // 2. slash region 62 | var slashRegion = TextureRegion(texture, 68, 25, fontWidth, fontHeight) 63 | 64 | // create text renderer 65 | textRenderer = TextRenderer(numberRegions.toTypedArray(), slashRegion) 66 | } 67 | 68 | private fun createLevelButtons(forNumLevel: Int) { 69 | val tex = Game.res.getTexture("hud")!! 70 | val baseTexRegion = TextureRegion(tex, 32, 32) 71 | 72 | val tmpList = arrayListOf() 73 | // at this point, our cached data should be present and there should be no problem 74 | val syncedPlayerSave = game.playerSaveFileManager.cache.data!! 75 | 76 | // design for 5x3 in total of 15 matches the value set in Settings.TOTAL_LEVELS 77 | for (j in 0..ROW_PER_PAGE-1) { 78 | for (i in (activePage-1)* LEVEL_PER_PAGE..(activePage-1) * LEVEL_PER_PAGE + COLUMN_PER_PAGE-1) { 79 | val levelResult = syncedPlayerSave.levelResults[j* COLUMN_PER_PAGE + i] 80 | val b = LevelButton(baseTexRegion, (i + 1) + (j * COLUMN_PER_PAGE), levelResult.clear, 64f/2f + (i * 64f), hudCam.viewportHeight - 64f - (j * 64f)) 81 | b.setOnClickListener { level -> run { 82 | // as touch position is sudden so we manually set it and save for later 83 | activeSelectionIndex = b.levelNumber - 1 84 | sPreviousActiveSelectionIndex = activeSelectionIndex 85 | 86 | Game.res.getSound("levelselect")!!.play() 87 | this.onLevelButtonClick(level) 88 | } 89 | } 90 | tmpList.add(b) 91 | } 92 | } 93 | 94 | levelButtons = tmpList.toTypedArray() 95 | } 96 | 97 | private fun onLevelButtonClick(level: Int) { 98 | Gdx.app.log("LevelSelection", "clicked on ${level}") 99 | 100 | when (level) { 101 | // check against the current possibility of all level number in current page 102 | in (activePage-1) * LEVEL_PER_PAGE + 1..activePage * LEVEL_PER_PAGE + 1 -> { 103 | Play.sToPlayLevel = level 104 | // clear state of score screen 105 | gsm.resetPreviousActiveLevelState() 106 | gsm.setState(GameStateManager.PLAY) 107 | } 108 | } 109 | } 110 | 111 | override fun handleInput() { 112 | if (BBInput.isPressed(BBInput.BUTTON_LEFT) || 113 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_LEFT)) { 114 | activeSelectionIndex = moveActiveSelectionLeft(activeSelectionIndex) 115 | } 116 | if (BBInput.isPressed(BBInput.BUTTON_RIGHT) || 117 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_RIGHT)) { 118 | activeSelectionIndex = moveActiveSelectionRight(activeSelectionIndex) 119 | } 120 | if (BBInput.isPressed(BBInput.BUTTON_UP) || 121 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_UP)) { 122 | activeSelectionIndex = moveActiveSelectionUp(activeSelectionIndex) 123 | } 124 | if (BBInput.isPressed(BBInput.BUTTON_DOWN) || 125 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_DOWN)) { 126 | activeSelectionIndex = moveActiveSelectionDown(activeSelectionIndex) 127 | } 128 | 129 | if (BBInput.isPressed(BBInput.BUTTON1) || 130 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_2)) { 131 | // save active selection index 132 | sPreviousActiveSelectionIndex = activeSelectionIndex 133 | 134 | // programmatically select a level button 135 | Game.res.getSound("levelselect")!!.play() 136 | this.onLevelButtonClick(activeSelectionIndex+1) 137 | } 138 | } 139 | 140 | /** 141 | * Move activeIndex to the left and wrap around to the right of its row if necessary. 142 | * @return New moved left index 143 | */ 144 | private fun moveActiveSelectionLeft(activeIndex: Int): Int { 145 | val leftMostIndexes = ArrayList() 146 | 147 | val startingIndex: Int = (activePage-1)* LEVEL_PER_PAGE 148 | leftMostIndexes.add(startingIndex) 149 | 150 | for (i in 1..ROW_PER_PAGE-1) { 151 | leftMostIndexes.add(startingIndex + i * COLUMN_PER_PAGE) 152 | } 153 | 154 | val chkIndex = leftMostIndexes.indexOf(activeIndex) 155 | if (chkIndex != -1) { 156 | return leftMostIndexes[chkIndex] + COLUMN_PER_PAGE - 1 157 | } 158 | else { 159 | return activeIndex - 1 160 | } 161 | } 162 | 163 | private fun moveActiveSelectionRight(activeIndex: Int): Int { 164 | val rightMostIndexes = ArrayList() 165 | 166 | val startingIndex: Int = (activePage-1) * LEVEL_PER_PAGE + COLUMN_PER_PAGE - 1 167 | rightMostIndexes.add(startingIndex) 168 | 169 | for (i in 1..ROW_PER_PAGE-1) { 170 | rightMostIndexes.add(startingIndex + i * COLUMN_PER_PAGE) 171 | } 172 | 173 | val chkIndex = rightMostIndexes.indexOf(activeIndex) 174 | if (chkIndex != -1) { 175 | return rightMostIndexes[chkIndex] - COLUMN_PER_PAGE + 1 176 | } 177 | else { 178 | return activeIndex + 1 179 | } 180 | } 181 | 182 | private fun moveActiveSelectionUp(activeIndex: Int): Int { 183 | val topMostIndexes = ArrayList() 184 | 185 | val startingIndex: Int = (activePage-1) * LEVEL_PER_PAGE 186 | topMostIndexes.add(startingIndex) 187 | 188 | for (i in 1..COLUMN_PER_PAGE-1) { 189 | topMostIndexes.add(startingIndex + i) 190 | } 191 | 192 | val chkIndex = topMostIndexes.indexOf(activeIndex) 193 | if (chkIndex != -1) { 194 | return topMostIndexes[chkIndex] + (ROW_PER_PAGE-1)* COLUMN_PER_PAGE 195 | } 196 | else { 197 | return activeIndex - COLUMN_PER_PAGE 198 | } 199 | } 200 | 201 | private fun moveActiveSelectionDown(activeIndex: Int): Int { 202 | val bottomMostIndexes = ArrayList() 203 | 204 | val startingIndex: Int = (activePage-1) * LEVEL_PER_PAGE + (ROW_PER_PAGE-1)* COLUMN_PER_PAGE 205 | bottomMostIndexes.add(startingIndex) 206 | 207 | for (i in 1..COLUMN_PER_PAGE-1) { 208 | bottomMostIndexes.add(startingIndex + i) 209 | } 210 | 211 | val chkIndex = bottomMostIndexes.indexOf(activeIndex) 212 | if (chkIndex != -1) { 213 | return bottomMostIndexes[chkIndex] - (ROW_PER_PAGE-1)* COLUMN_PER_PAGE 214 | } 215 | else { 216 | return activeIndex + COLUMN_PER_PAGE 217 | } 218 | } 219 | 220 | override fun update(dt: Float) { 221 | handleInput() 222 | bg.update(dt) 223 | 224 | for (b in levelButtons) { 225 | b.update(hudCam, hudViewport, dt) 226 | } 227 | } 228 | 229 | override fun render() { 230 | // clear screen 231 | Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT) 232 | 233 | sb.begin() 234 | 235 | sb.projectionMatrix = hudCam.combined 236 | hudViewport.apply(true) 237 | bg.render(sb) 238 | 239 | for (b in levelButtons) { 240 | b.render(textRenderer, sb) 241 | } 242 | 243 | renderActiveSelection(sb) 244 | 245 | sb.end() 246 | } 247 | 248 | private fun renderActiveSelection(sb: SpriteBatch) { 249 | val activeLevelButton = levelButtons[activeSelectionIndex] 250 | sb.draw(activeSelectionTextureRegion, activeLevelButton.position.x - activeSelectionTextureRegion.regionWidth/2f, activeLevelButton.position.y - activeSelectionTextureRegion.regionHeight/2f) 251 | } 252 | 253 | override fun dispose() { 254 | 255 | } 256 | 257 | override fun resize_user(width: Int, height: Int) { 258 | 259 | } 260 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/states/Mainmenu.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.states 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.GL20 5 | import com.badlogic.gdx.graphics.OrthographicCamera 6 | import com.badlogic.gdx.graphics.g2d.TextureRegion 7 | import com.badlogic.gdx.math.MathUtils 8 | import com.badlogic.gdx.math.Vector2 9 | import com.badlogic.gdx.physics.box2d.* 10 | import com.badlogic.gdx.utils.SerializationException 11 | import com.badlogic.gdx.utils.viewport.FitViewport 12 | import com.badlogic.gdx.utils.viewport.Viewport 13 | import io.wasin.blockbunny.Game 14 | import io.wasin.blockbunny.entities.B2DSprite 15 | import io.wasin.blockbunny.handlers.* 16 | import kotlin.experimental.or 17 | 18 | /** 19 | * Created by haxpor on 5/30/17. 20 | */ 21 | class Mainmenu(gsm: GameStateManager): GameState(gsm) { 22 | 23 | private var bg: TextureRegion 24 | 25 | private var b2dViewport: Viewport 26 | private var b2dCam: OrthographicCamera 27 | private var b2dDebug: Boolean = false 28 | 29 | private var world: World 30 | private var b2dr: Box2DDebugRenderer 31 | 32 | lateinit private var topChildBlocks: Array 33 | lateinit private var bottomChildBlocks: Array 34 | private var blockTextureRegions: Array 35 | 36 | private val playTextureRegion: TextureRegion 37 | 38 | init { 39 | val texture = Game.res.getTexture("menu")!! 40 | bg = TextureRegion(texture, texture.width, texture.height) 41 | 42 | val hudTexture = Game.res.getTexture("hud")!! 43 | // set all texture regions used to draw individual blocks 44 | blockTextureRegions = arrayOf( 45 | TextureRegion(hudTexture, 58, 34, 5, 5), 46 | TextureRegion(hudTexture, 58 + 5, 34, 5, 5), 47 | TextureRegion(hudTexture, 58 + 5*2, 34, 5, 5) 48 | ) 49 | 50 | playTextureRegion = TextureRegion(hudTexture, 0, 34, 58, 28) 51 | 52 | // set up box2d related stuff 53 | world = World(Vector2(0f, -1.0f), true) 54 | b2dr = Box2DDebugRenderer() 55 | b2dCam = OrthographicCamera() 56 | b2dCam.setToOrtho(false, Game.V_WIDTH / B2DVars.PPM, Game.V_HEIGHT / B2DVars.PPM) 57 | b2dViewport = FitViewport(Game.V_WIDTH / B2DVars.PPM, Game.V_HEIGHT / B2DVars.PPM, b2dCam) 58 | 59 | createPhysicsTextBlocks() 60 | 61 | // read player's savefile 62 | // this will read it into cache, thus it will be maintained and used throughout the life 63 | // cycle of the game 64 | try { 65 | Gdx.app.log("Mainmenu", "read save file") 66 | game.playerSaveFileManager.readSaveFile() 67 | } 68 | catch(e: GameRuntimeException) { 69 | if (e.code == GameRuntimeException.SAVE_FILE_NOT_FOUND || 70 | e.code == GameRuntimeException.SAVE_FILE_EMPTY_CONTENT) { 71 | // write a new fresh save file to resolve the issue 72 | Gdx.app.log("Mainmenu", "write a fresh save file") 73 | game.playerSaveFileManager.writeFreshSaveFile(Settings.TOTAL_LEVELS) 74 | } 75 | } 76 | catch(e: SerializationException) { 77 | Gdx.app.log("Mainmenu", "save file is corrupted, rewrite a fresh one : ${e.message}") 78 | 79 | game.playerSaveFileManager.writeFreshSaveFile(Settings.TOTAL_LEVELS) 80 | } 81 | } 82 | 83 | override fun handleInput() { 84 | if (BBInput.isPressed(BBInput.BUTTON1) || BBInput.isPressed(BBInput.BUTTON2) || 85 | BBInput.isMouseDown(BBInput.MOUSE_BUTTON_LEFT) || BBInput.isDown() || 86 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_2)) { 87 | // go back to level selection 88 | gsm.setState(GameStateManager.LEVEL_SELECTION) 89 | } 90 | } 91 | 92 | override fun update(dt: Float) { 93 | handleInput() 94 | world.step(dt, 6, 2) 95 | } 96 | 97 | override fun render() { 98 | Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT) 99 | 100 | sb.begin() 101 | sb.projectionMatrix = hudCam.combined 102 | hudViewport.apply(true) 103 | 104 | sb.draw(bg, 0f, 0f) 105 | 106 | // draw top child blocks 107 | for (b in topChildBlocks) { 108 | b.render(sb) 109 | } 110 | for (b in bottomChildBlocks) { 111 | b.render(sb) 112 | } 113 | 114 | sb.draw(playTextureRegion, Game.V_WIDTH / 2f - playTextureRegion.regionWidth/2f, playTextureRegion.regionHeight/2f + 50f) 115 | 116 | sb.end() 117 | 118 | // draw box2d world 119 | if (b2dDebug) { 120 | b2dr.render(world, b2dCam.combined) 121 | } 122 | } 123 | 124 | override fun dispose() { 125 | // destroy all bodies in box2d world 126 | // we need to do this first before calling dispose on World 127 | val bodies = com.badlogic.gdx.utils.Array() 128 | world.getBodies(bodies) 129 | bodies.forEach { world.destroyBody(it) } 130 | world.dispose() 131 | 132 | b2dr.dispose() 133 | } 134 | 135 | override fun resize_user(width: Int, height: Int) { 136 | 137 | } 138 | 139 | private fun createPhysicsTextBlocks() { 140 | val tileSize = 5f 141 | val offsetY = 20f // offset in y-position to initially place each block before physics kicks in 142 | 143 | // create top platform block 144 | _createB2DStaticBlock(B2DVars.BIT_TOP_PLATFORM, B2DVars.BIT_LINE1_BLOCK, Vector2(Game.V_WIDTH/2f, 175f), 250f, 1f) 145 | // create bottom platform block 146 | _createB2DStaticBlock(B2DVars.BIT_BOTTOM_PLATFORM, B2DVars.BIT_LINE2_BLOCK, Vector2(Game.V_WIDTH/2f, 110f), 250f, 1f) 147 | 148 | // definition of text as arrays 1,0 149 | // size is 40x8 150 | // line1 represents "B L O C K" 151 | val line1Blocks = arrayOf( 152 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0), 153 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0), 154 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0), 155 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0), 156 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0), 157 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0), 158 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0), 159 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0) 160 | ) 161 | topChildBlocks = _createChildBlocks(line1Blocks, 1, BodyDef.BodyType.DynamicBody, B2DVars.BIT_LINE1_BLOCK, B2DVars.BIT_TOP_PLATFORM or B2DVars.BIT_LINE1_BLOCK, 162 | { i -> (Game.V_WIDTH/2f - line1Blocks[0].count()*tileSize/2f + i*tileSize) / B2DVars.PPM }, 163 | { rowIndex -> (175f+offsetY + (line1Blocks.count()-1 - rowIndex)*tileSize + tileSize*2) / B2DVars.PPM }) 164 | 165 | val line2Blocks = arrayOf( 166 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0), 167 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0), 168 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), 169 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), 170 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), 171 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), 172 | arrayOf(0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0), 173 | arrayOf(0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0) 174 | ) 175 | bottomChildBlocks = _createChildBlocks(line2Blocks, 1, BodyDef.BodyType.DynamicBody, B2DVars.BIT_LINE2_BLOCK, B2DVars.BIT_BOTTOM_PLATFORM or B2DVars.BIT_LINE2_BLOCK, 176 | { i -> (Game.V_WIDTH/2f - line2Blocks[0].count()*tileSize/2f + i*tileSize) / B2DVars.PPM }, 177 | { rowIndex -> (110f+offsetY + (line2Blocks.count()-1 - rowIndex)*tileSize + tileSize*2) / B2DVars.PPM }) 178 | } 179 | 180 | private fun _createB2DStaticBlock(categoryBits: Short, maskBits: Short, position: Vector2, width: Float, height: Float) { 181 | // used for each physics element to fall down on it 182 | val bdef = BodyDef() 183 | bdef.position.set(position.x/B2DVars.PPM, position.y/B2DVars.PPM) 184 | bdef.type = BodyDef.BodyType.StaticBody 185 | 186 | val shape = PolygonShape() 187 | val fdef = FixtureDef() 188 | 189 | val body = world.createBody(bdef) 190 | shape.setAsBox(width/2f/B2DVars.PPM, height/2f/B2DVars.PPM) 191 | fdef.shape = shape 192 | fdef.friction = 0.0f 193 | fdef.filter.categoryBits = categoryBits 194 | fdef.filter.maskBits = maskBits 195 | body.createFixture(fdef) 196 | } 197 | 198 | private fun _createBlocksAgainstTiles(rowTiles: Array, targetValue: Int, bodyType: BodyDef.BodyType, categoryBits: Short, maskBits: Short, textureRegion: TextureRegion, 199 | position: ((Int) -> Vector2)): Array { 200 | val tileSize = 5f 201 | 202 | val tmpList = ArrayList() 203 | 204 | // top platform 205 | for (i in 0..rowTiles.count()-1) { 206 | 207 | // create a top platform 208 | // used for each physics element to fall down on it 209 | val bdef = BodyDef() 210 | bdef.position.set(position.invoke(i)) 211 | bdef.type = bodyType 212 | bdef.fixedRotation = true 213 | 214 | val shape = PolygonShape() 215 | val fdef = FixtureDef() 216 | 217 | val body = world.createBody(bdef) 218 | shape.setAsBox(tileSize/2f/B2DVars.PPM, tileSize/2f/B2DVars.PPM) 219 | fdef.shape = shape 220 | fdef.filter.categoryBits = categoryBits 221 | fdef.filter.maskBits = maskBits 222 | body.createFixture(fdef) 223 | 224 | // create b2dsprite 225 | if (rowTiles[i] == targetValue) { 226 | val b2dsprite = B2DSprite(body) 227 | b2dsprite.setAnimation(arrayOf(textureRegion), 1 / 12f) 228 | tmpList.add(b2dsprite) 229 | } 230 | } 231 | return tmpList.toTypedArray() 232 | } 233 | 234 | private fun _createChildBlocks(blockTiles: Array>, targetValue: Int, bodyType: BodyDef.BodyType, categoryBits: Short, maskBits: Short, 235 | positionX: ((i:Int) -> Float), positionY: ((rowIndex:Int) -> Float)): Array { 236 | 237 | var tmpRowList: Array 238 | val mutableList = ArrayList() 239 | 240 | for (row in 0..blockTiles.count()-1) { 241 | tmpRowList = _createBlocksAgainstTiles(blockTiles[row], targetValue, bodyType, categoryBits, maskBits, 242 | blockTextureRegions[MathUtils.random(blockTextureRegions.count()-1)], 243 | { i -> Vector2(positionX.invoke(i), positionY.invoke(row)) }) 244 | mutableList.addAll(tmpRowList) 245 | } 246 | // set result to top child blocks 247 | return mutableList.toTypedArray() 248 | } 249 | } -------------------------------------------------------------------------------- /core/src/io/wasin/blockbunny/states/Play.kt: -------------------------------------------------------------------------------- 1 | package io.wasin.blockbunny.states 2 | 3 | import com.badlogic.gdx.Gdx 4 | import com.badlogic.gdx.graphics.Camera 5 | import com.badlogic.gdx.graphics.GL20 6 | import com.badlogic.gdx.graphics.OrthographicCamera 7 | import com.badlogic.gdx.graphics.g2d.TextureRegion 8 | import com.badlogic.gdx.maps.tiled.TiledMap 9 | import com.badlogic.gdx.maps.tiled.TiledMapTileLayer 10 | import com.badlogic.gdx.maps.tiled.TmxMapLoader 11 | import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer 12 | import com.badlogic.gdx.math.Vector2 13 | import com.badlogic.gdx.math.Vector3 14 | import com.badlogic.gdx.physics.box2d.* 15 | import com.badlogic.gdx.utils.viewport.ExtendViewport 16 | import com.badlogic.gdx.utils.viewport.Viewport 17 | import io.wasin.blockbunny.Game 18 | import io.wasin.blockbunny.data.LevelResult 19 | import io.wasin.blockbunny.entities.Bomb 20 | import io.wasin.blockbunny.entities.Crystal 21 | import io.wasin.blockbunny.entities.HUD 22 | import io.wasin.blockbunny.entities.Player 23 | import io.wasin.blockbunny.handlers.* 24 | import kotlin.experimental.and 25 | import kotlin.experimental.inv 26 | import kotlin.experimental.or 27 | 28 | /** 29 | * Created by haxpor on 5/16/17. 30 | */ 31 | class Play(gsm: GameStateManager) : GameState(gsm) { 32 | 33 | companion object { 34 | // level number to be set before entering Play game state by LevelSelection 35 | // default to 1st level 36 | var sToPlayLevel: Int = 1 37 | } 38 | 39 | private var b2dDebug: Boolean = false 40 | 41 | var b2dViewport: Viewport 42 | var b2dCam: OrthographicCamera 43 | private set 44 | 45 | private var world: World 46 | private var b2dr: Box2DDebugRenderer 47 | private var cl: MyContactListener 48 | 49 | lateinit private var tileMap: TiledMap 50 | private var tileSize: Float = 0f 51 | lateinit private var tmr: OrthogonalTiledMapRenderer 52 | 53 | lateinit private var player: Player 54 | lateinit private var dummyPlayer: Player 55 | lateinit private var crystals: MutableList 56 | lateinit private var bombs: MutableList 57 | private var hud: HUD 58 | lateinit private var bgs: Array 59 | 60 | private var screenStopper: ScreenStopper 61 | private var isWentToScoreScreen: Boolean = false 62 | 63 | init { 64 | world = World(Vector2(0f, -9.81f), true) 65 | 66 | // set up box2d stuff 67 | cl = MyContactListener() 68 | world.setContactListener(cl) 69 | b2dr = Box2DDebugRenderer() 70 | 71 | // create player 72 | createPlayer() 73 | createDummyPlayer() 74 | 75 | // create tiles 76 | createTiles() 77 | 78 | // create objects in game 79 | createObjects() 80 | 81 | // set up box2d camera 82 | b2dCam = OrthographicCamera() 83 | b2dCam.setToOrtho(false, cam.viewportWidth / B2DVars.PPM, cam.viewportHeight / B2DVars.PPM) 84 | b2dViewport = ExtendViewport(cam.viewportWidth / B2DVars.PPM, cam.viewportHeight / B2DVars.PPM, b2dCam) 85 | 86 | // set up HUD 87 | hud = HUD(player) 88 | 89 | // backgrounds 90 | createBackgrounds() 91 | 92 | screenStopper = ScreenStopper(tileMap, cam) 93 | screenStopper.setOnRearchEndOfLevel { -> this.onReachEndOfLevel() } 94 | 95 | // set total of crystals in the map to player 96 | player.setTotalCrystals(getTotalObjetCountOfCrystals()) 97 | } 98 | 99 | override fun setupViewport(cam: OrthographicCamera, hudCam: OrthographicCamera, viewportWidth: Float, viewportHeight: Float) { 100 | camViewport = ExtendViewport(viewportWidth, viewportHeight, cam) 101 | hudViewport = ExtendViewport(viewportWidth, viewportHeight, hudCam) 102 | } 103 | 104 | override fun resize_user(width: Int, height: Int) { 105 | // update from updated camViewport 106 | b2dViewport.update((camViewport.camera.viewportWidth / B2DVars.PPM).toInt(), 107 | (camViewport.camera.viewportHeight / B2DVars.PPM).toInt()) 108 | } 109 | 110 | fun onReachEndOfLevel() { 111 | } 112 | 113 | /** 114 | * Be careful, it's expensive call. 115 | * Cache the result for better performance. 116 | */ 117 | fun getTotalObjetCountOfCrystals(): Int { 118 | val layer = tileMap.layers.get("objects") 119 | var count = 0 120 | 121 | for (obj in layer.objects) { 122 | if (obj.properties.get("type") == "crystal") { 123 | count++ 124 | } 125 | } 126 | 127 | return count 128 | } 129 | 130 | /** 131 | * Be careful, it's expensive call. 132 | * Cache the result for better performance. 133 | */ 134 | fun getTotalObjectCountOfBomb(): Int { 135 | val layer = tileMap.layers.get("objects") 136 | var count = 0 137 | 138 | for (obj in layer.objects) { 139 | if (obj.properties.get("type") == "bomb") { 140 | count++ 141 | } 142 | } 143 | 144 | return count 145 | } 146 | 147 | override fun handleInput() { 148 | // ** Keyboard & Mouse ** 149 | // convert screen coordinate to world coordinate in context of hud-camera 150 | val screenCoor = Vector3(BBInput.screenX.toFloat(), BBInput.screenY.toFloat(), 0f) 151 | val worldCoor = hudCam.unproject(screenCoor, hudViewport.screenX.toFloat(), hudViewport.screenY.toFloat(), 152 | hudViewport.screenWidth.toFloat(), hudViewport.screenHeight.toFloat()) 153 | 154 | // player jump if BBInput.BUTTON1 is pressed, or click on left half of the screen 155 | if (BBInput.isPressed(BBInput.BUTTON1) || 156 | ((worldCoor.x <= hudCam.viewportWidth/2f) && BBInput.isMousePressed(BBInput.MOUSE_BUTTON_LEFT)) || 157 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_2)) { 158 | if (cl.playerOnGround) { 159 | Game.res.getSound("jump")!!.play() 160 | player.body.applyForceToCenter(0f, 250f, true) 161 | } 162 | } 163 | 164 | // switch block color if BBInput.BUTTON2 is pressed, or click on right half of the screen 165 | if (BBInput.isPressed(BBInput.BUTTON2) || 166 | (worldCoor.x > hudCam.viewportWidth/2f && BBInput.isMousePressed(BBInput.MOUSE_BUTTON_LEFT)) || 167 | BBInput.isControllerPressed(BBInput.CONTROLLER_BUTTON_1)) { 168 | switchBlocks() 169 | } 170 | } 171 | 172 | override fun update(dt: Float) { 173 | handleInput() 174 | world.step(dt, 6, 2) 175 | 176 | // update backgrounds 177 | for (b in bgs) { 178 | b.update(dt) 179 | } 180 | 181 | // remove crystals 182 | var bodies = cl.bodiesToRemove 183 | for (b in bodies) { 184 | // this manual loop section fixed this issue https://github.com/multi-os-engine/multi-os-engine/issues/114 185 | for (c in crystals) { 186 | if (c == b.userData as Crystal) { 187 | crystals.remove(c) 188 | break 189 | } 190 | } 191 | world.destroyBody(b) 192 | player.collectCrystal() 193 | } 194 | bodies.clear() 195 | 196 | player.update(dt) 197 | 198 | if (!screenStopper.isStopped) { 199 | dummyPlayer.update(dt) 200 | } 201 | 202 | for (c in crystals) { 203 | c.update(dt) 204 | } 205 | for (b in bombs) { 206 | b.update(dt) 207 | } 208 | 209 | screenStopper.update(dt) 210 | 211 | // check to act die for player (front collided with tile) 212 | if ((cl.playerFrontCollided || cl.playerBackCollided) && !player.died) { 213 | Gdx.app.log("Play", "Player collided with tile") 214 | 215 | val hit = Game.res.getSound("hit")!! 216 | val hitId = hit.play() 217 | hit.setVolume(hitId, 2.0f) 218 | 219 | screenStopper.stop() 220 | player.actDie() 221 | } 222 | // check to act die for player (bomb) 223 | if (cl.playerCollidedWithBomb && !player.died) { 224 | Gdx.app.log("Play", "Player collided with bomb") 225 | 226 | val hit = Game.res.getSound("hit")!! 227 | val hitId = hit.play() 228 | hit.setVolume(hitId, 2.0f) 229 | 230 | screenStopper.stop() 231 | player.actDie() 232 | } 233 | 234 | // if player is outside of the screen then go to SCORE screen 235 | // GAME OVER state 236 | if (!isWentToScoreScreen && player.position.y * B2DVars.PPM + player.height/2 < 0f) { 237 | Gdx.app.log("Play", "Player died and is outside of the screen") 238 | 239 | // no need to update save file 240 | 241 | // go to Score screen 242 | gsm.pushState(GameStateManager.SCORE) 243 | isWentToScoreScreen = true 244 | } 245 | // if player is outside of the total width of tilemap, then player clears the level 246 | // WIN state 247 | else if (!isWentToScoreScreen && player.position.x * B2DVars.PPM - player.width/2 > screenStopper.width) { 248 | Gdx.app.log("Play", "Player clears the level") 249 | 250 | // update to player save file if level isn't cleared yet OR 251 | // update to player save file if level is cleared and its high score is beated 252 | val levelResult: LevelResult? = game.playerSaveFileManager.getLevelResult(sToPlayLevel) 253 | if ((levelResult != null && !levelResult.clear) || 254 | (levelResult != null && levelResult.clear && levelResult.collectedCrystal < player.getNumCrystals())) { 255 | game.playerSaveFileManager.updateLevelResult(sToPlayLevel, LevelResult(true, player.getNumCrystals()), true) 256 | } 257 | 258 | // go to Score screen 259 | gsm.setCurrentActiveLevelAsClear(player.getNumCrystals(), player.getTotalCrystals()) 260 | gsm.pushState(GameStateManager.SCORE) 261 | isWentToScoreScreen = true 262 | } 263 | } 264 | 265 | override fun render() { 266 | // clear screen 267 | Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT) 268 | 269 | sb.begin() 270 | 271 | // set camera to follow player 272 | if (!screenStopper.isStopped) { 273 | cam.position.set(dummyPlayer.position.x * B2DVars.PPM + cam.viewportWidth / 4f, cam.viewportHeight / 2f, 0f) 274 | cam.update() 275 | } 276 | 277 | // draw bgs 278 | sb.projectionMatrix = hudCam.combined 279 | hudViewport.apply(true) 280 | for (b in bgs) { 281 | b.render(sb) 282 | } 283 | sb.end() 284 | 285 | // draw tile map 286 | camViewport.apply() 287 | tmr.setView(cam) 288 | tmr.render() 289 | 290 | 291 | sb.begin() 292 | // draw player 293 | sb.projectionMatrix = cam.combined 294 | camViewport.apply() 295 | player.render(sb) 296 | 297 | // draw crystals 298 | for (c in crystals) { 299 | c.render(sb) 300 | } 301 | for (b in bombs) { 302 | b.render(sb) 303 | } 304 | 305 | // draw hud 306 | sb.projectionMatrix = hudCam.combined 307 | hudViewport.apply(true) 308 | hud.render(sb) 309 | sb.end() 310 | 311 | // draw box2d world 312 | if (b2dDebug) { 313 | b2dCam.position.set(cam.position.x / B2DVars.PPM, cam.position.y / B2DVars.PPM, 0f) 314 | b2dCam.update() 315 | b2dr.render(world, b2dCam.combined) 316 | } 317 | } 318 | 319 | override fun dispose() { 320 | hud.dispose() 321 | tileMap.dispose() 322 | tmr.dispose() 323 | 324 | // destroy all bodies in box2d world 325 | // we need to do this first before calling dispose on World 326 | val bodies = com.badlogic.gdx.utils.Array() 327 | world.getBodies(bodies) 328 | bodies.forEach { world.destroyBody(it) } 329 | world.dispose() 330 | 331 | b2dr.dispose() 332 | } 333 | 334 | private fun createPlayer() { 335 | val bdef = BodyDef() 336 | 337 | bdef.position.set(100f / B2DVars.PPM, 200f / B2DVars.PPM) 338 | bdef.type = BodyDef.BodyType.DynamicBody 339 | bdef.linearVelocity.set(1f, 0f) 340 | 341 | // shape, and fdef 342 | var shape = PolygonShape() 343 | var fdef = FixtureDef() 344 | 345 | val body: Body = world.createBody(bdef) 346 | shape.setAsBox(13f / B2DVars.PPM, 13f / B2DVars.PPM) 347 | fdef.shape = shape 348 | fdef.filter.categoryBits = B2DVars.BIT_PLAYER 349 | fdef.filter.maskBits = B2DVars.BIT_RED or B2DVars.BIT_CRYSTAL or B2DVars.BIT_BOMB 350 | body.createFixture(fdef).userData = "player" 351 | 352 | // reuse 353 | shape = PolygonShape() 354 | fdef = FixtureDef() 355 | 356 | // create foot sensor 357 | shape.setAsBox(13 / B2DVars.PPM, 2 / B2DVars.PPM, Vector2(0f, -13 / B2DVars.PPM), 0f) 358 | fdef.shape = shape 359 | fdef.filter.categoryBits = B2DVars.BIT_PLAYER 360 | fdef.filter.maskBits = B2DVars.BIT_RED 361 | fdef.isSensor = true 362 | body.createFixture(fdef).userData = "foot" 363 | 364 | // reuse 365 | shape = PolygonShape() 366 | fdef = FixtureDef() 367 | 368 | // create front sensor 369 | shape.setAsBox(2 / B2DVars.PPM, 6 / B2DVars.PPM, Vector2(13 / B2DVars.PPM, 0f), 0f) 370 | fdef.shape = shape 371 | fdef.filter.categoryBits = B2DVars.BIT_PLAYER 372 | fdef.filter.maskBits = B2DVars.BIT_RED 373 | fdef.isSensor = true 374 | body.createFixture(fdef).userData = "front" 375 | 376 | // reuse 377 | shape = PolygonShape() 378 | fdef = FixtureDef() 379 | 380 | // create back sensor 381 | shape.setAsBox(2 / B2DVars.PPM, 6 / B2DVars.PPM, Vector2(-13 / B2DVars.PPM, 0f), 0f) 382 | fdef.shape = shape 383 | fdef.filter.categoryBits = B2DVars.BIT_PLAYER 384 | fdef.filter.maskBits = B2DVars.BIT_RED 385 | fdef.isSensor = true 386 | body.createFixture(fdef).userData = "back" 387 | 388 | // create player 389 | player = Player(body) 390 | // circular reference from body->player, and player->body 391 | body.userData = player 392 | } 393 | 394 | private fun createDummyPlayer() { 395 | // dummy player will be used to update camera's position 396 | val bdef = BodyDef() 397 | 398 | bdef.position.set(100f / B2DVars.PPM, 200f / B2DVars.PPM) 399 | bdef.type = BodyDef.BodyType.KinematicBody // it's kinematic type as we don't want it to be affected by physics simulation 400 | bdef.linearVelocity.set(1f, 0f) // important, it needs to have the same linear velocity as of player 401 | 402 | // shape, and fdef 403 | var shape = PolygonShape() 404 | var fdef = FixtureDef() 405 | 406 | val body: Body = world.createBody(bdef) 407 | shape.setAsBox(13f / B2DVars.PPM, 13f / B2DVars.PPM) 408 | fdef.shape = shape 409 | fdef.isSensor = true 410 | body.createFixture(fdef) 411 | 412 | // create dummy player 413 | dummyPlayer = Player(body) 414 | } 415 | 416 | private fun createTiles() { 417 | // load tile map from selected level 418 | tileMap = TmxMapLoader().load("maps/level${sToPlayLevel}.tmx") 419 | tmr = OrthogonalTiledMapRenderer(tileMap) 420 | 421 | tileSize = tileMap.properties.get("tilewidth", Float::class.java) 422 | 423 | // note: this section is not optimized, the code loops 3 times for each layer unneccesary 424 | // we can do better by adding custom property to Tile via color name and check it in 1 single loop 425 | createB2DTileFromLayerWithMappings(tileMap.layers.get("tiles") as TiledMapTileLayer, hashMapOf(1 to B2DVars.BIT_RED, 2 to B2DVars.BIT_GREEN, 3 to B2DVars.BIT_BLUE)) 426 | } 427 | 428 | private fun createB2DTileFromLayerWithMappings(layer: TiledMapTileLayer, idMaps: HashMap) { 429 | // go through all the cells in the layer 430 | for (row in 0..layer.height-1) { 431 | for (col in 0..layer.width-1) { 432 | // get cell 433 | val cell = layer.getCell(col, row) 434 | 435 | // check if tile exists 436 | if (cell == null) continue 437 | if (cell.tile == null) continue 438 | if (!idMaps.containsKey(cell.tile.id)) continue 439 | 440 | // creae a body fixture from cell 441 | val bdef = BodyDef() 442 | bdef.type = BodyDef.BodyType.StaticBody 443 | bdef.position.set( 444 | (col + 0.5f) * tileSize / B2DVars.PPM, 445 | (row + 0.5f) * tileSize / B2DVars.PPM 446 | ) 447 | 448 | val cs = ChainShape() 449 | val v = Array(3, { i -> Vector2.Zero }) 450 | v[0] = Vector2(-tileSize / 2 / B2DVars.PPM, -tileSize / 2 / B2DVars.PPM) 451 | v[1] = Vector2(-tileSize / 2 / B2DVars.PPM, tileSize / 2 / B2DVars.PPM) 452 | v[2] = Vector2(tileSize / 2 / B2DVars.PPM, tileSize / 2 / B2DVars.PPM) 453 | cs.createChain(v) 454 | 455 | val fdef = FixtureDef() 456 | fdef.shape = cs 457 | fdef.friction = 0f 458 | fdef.filter.categoryBits = idMaps.getValue(cell.tile.id) 459 | fdef.filter.maskBits = B2DVars.BIT_PLAYER 460 | 461 | world.createBody(bdef).createFixture(fdef) 462 | } 463 | } 464 | } 465 | 466 | private fun createObjects() { 467 | // create mutable list for all type of objects 468 | crystals = mutableListOf() 469 | bombs = mutableListOf() 470 | 471 | // all types of object are inside "objects" layer 472 | val layer = tileMap.layers.get("objects") 473 | 474 | val bdef = BodyDef() 475 | val fdef = FixtureDef() 476 | 477 | for (mo in layer.objects) { 478 | 479 | val type = mo.properties.get("type") 480 | 481 | when(type) { 482 | "crystal" -> { 483 | bdef.type = BodyDef.BodyType.StaticBody 484 | val x = mo.properties.get("x", Float::class.java) / B2DVars.PPM 485 | val y = mo.properties.get("y", Float::class.java) / B2DVars.PPM 486 | bdef.position.set(x, y) 487 | 488 | val cshape = CircleShape() 489 | cshape.radius = 8f / B2DVars.PPM 490 | 491 | fdef.shape = cshape 492 | fdef.isSensor = true 493 | fdef.filter.categoryBits = B2DVars.BIT_CRYSTAL 494 | fdef.filter.maskBits = B2DVars.BIT_PLAYER 495 | 496 | val body = world.createBody(bdef) 497 | body.createFixture(fdef).userData = "crystal" 498 | 499 | val type = mo.properties.get("type") 500 | 501 | val c = Crystal(body) 502 | body.userData = c 503 | crystals.add(c) 504 | } 505 | 506 | "bomb" -> { 507 | bdef.type = BodyDef.BodyType.StaticBody 508 | val x = mo.properties.get("x", Float::class.java) / B2DVars.PPM 509 | val y = mo.properties.get("y", Float::class.java) / B2DVars.PPM 510 | bdef.position.set(x, y) 511 | 512 | val cshape = CircleShape() 513 | cshape.radius = 13f / B2DVars.PPM // no the exact same size of texture region 514 | 515 | fdef.shape = cshape 516 | fdef.isSensor = true 517 | fdef.filter.categoryBits = B2DVars.BIT_BOMB 518 | fdef.filter.maskBits = B2DVars.BIT_PLAYER 519 | 520 | val body = world.createBody(bdef) 521 | body.createFixture(fdef).userData = "bomb" 522 | 523 | val c = Bomb(body) 524 | body.userData = c 525 | bombs.add(c) 526 | } 527 | } 528 | } 529 | } 530 | 531 | private fun switchBlocks() { 532 | Game.res.getSound("changeblock")!!.play() 533 | 534 | val body = player.body.fixtureList.first() 535 | val foot = player.body.fixtureList[1] 536 | val front = player.body.fixtureList[2] 537 | val back = player.body.fixtureList[3] 538 | 539 | // get current bits set on player's body 540 | var bits = body.filterData.maskBits 541 | // temp filter data to hold data and set back to each fixture 542 | var tmpFilterData: Filter 543 | 544 | // switch to next color 545 | // red -> green -> blue -> red 546 | if ((bits and B2DVars.BIT_RED) != 0.toShort()) { 547 | bits = bits and B2DVars.BIT_RED.inv() 548 | bits = bits or B2DVars.BIT_GREEN 549 | } 550 | else if ((bits and B2DVars.BIT_GREEN) != 0.toShort()) { 551 | bits = bits and B2DVars.BIT_GREEN.inv() 552 | bits = bits or B2DVars.BIT_BLUE 553 | } 554 | else if ((bits and B2DVars.BIT_BLUE) != 0.toShort()) { 555 | bits = bits and B2DVars.BIT_BLUE.inv() 556 | bits = bits or B2DVars.BIT_RED 557 | } 558 | 559 | // set new mask bits to body 560 | tmpFilterData = body.filterData 561 | tmpFilterData.maskBits = bits 562 | body.filterData = tmpFilterData 563 | 564 | // set new mask bits to foot 565 | tmpFilterData = foot.filterData 566 | tmpFilterData.maskBits = bits and B2DVars.BIT_CRYSTAL.inv() and B2DVars.BIT_BOMB.inv() 567 | foot.filterData = tmpFilterData 568 | 569 | // set new mask bits to front 570 | tmpFilterData = front.filterData 571 | tmpFilterData.maskBits = bits and B2DVars.BIT_CRYSTAL.inv() and B2DVars.BIT_BOMB.inv() 572 | front.filterData = tmpFilterData 573 | 574 | // set new mask bits to back 575 | tmpFilterData = back.filterData 576 | tmpFilterData.maskBits = bits and B2DVars.BIT_CRYSTAL.inv() and B2DVars.BIT_BOMB.inv() 577 | back.filterData = tmpFilterData 578 | } 579 | 580 | private fun createBackgrounds() { 581 | val texture = Game.res.getTexture("bgs")!! 582 | val textureRegions = TextureRegion.split(texture, texture.width, texture.height / 3) 583 | 584 | val sky = Background(textureRegions[0][0], hudCam, 0.0f) 585 | val cloud = Background(textureRegions[1][0], hudCam, 1.0f) 586 | val rocks = Background(textureRegions[2][0], hudCam, 8.0f) 587 | 588 | bgs = arrayOf(sky, cloud, rocks) 589 | } 590 | } --------------------------------------------------------------------------------