├── .gitignore ├── IDEAS.md ├── README.md ├── android ├── release.sh └── src │ └── main │ ├── AndroidManifest.xml │ ├── res │ ├── drawable-mdpi │ └── mipmap-xxhdpi │ │ └── ic_launcher.png │ └── scala │ └── MainActivity.scala ├── art ├── bug.svg ├── clouds.svg ├── feature.svg ├── icon.svg ├── jumper.svg ├── platform.svg └── test │ ├── character_idle.png │ ├── character_jump.png │ ├── character_jump1.png │ ├── character_jump2.png │ ├── character_jump3.png │ ├── character_jump4.png │ ├── character_jump5.png │ ├── character_jump6.png │ ├── character_prejump.png │ ├── character_prejump1.png │ ├── character_prejump2.png │ └── character_prejump3.png ├── build.sbt ├── core └── src │ └── main │ └── scala │ ├── App.scala │ ├── Background.scala │ ├── LoadingScreen.scala │ └── MainScreen.scala ├── desktop └── src │ └── main │ ├── resources │ └── drawable │ │ ├── bug.png │ │ ├── character_idle.png │ │ ├── character_jump.png │ │ ├── character_prejump.png │ │ ├── clouds.png │ │ └── platform.png │ └── scala │ └── Main.scala ├── html5 ├── index.html ├── src │ └── main │ │ └── scala │ │ └── Main.scala └── static ├── marketing ├── feature.png ├── icon.png └── screenshots │ ├── screenshot1.png │ ├── screenshot2.png │ ├── screenshot3.png │ └── screenshot4.png └── project ├── build.properties └── plugins.sbt /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .history 3 | *.swp 4 | -------------------------------------------------------------------------------- /IDEAS.md: -------------------------------------------------------------------------------- 1 | IDEAS 2 | ===== 3 | 4 | * Bonus points when succeed with long jump 5 | Or just make the score time-dependent, which encourages taking more risks with longer jumps 6 | 7 | * Some flying object representing dynamic languages as ennemy? 8 | 9 | * Auto scroll up, losing if not going up fast enough 10 | 11 | * landing effect could push the platform down slightly, then back up 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scalavator 2 | 3 | Scalavator is the unofficial Scala game. It is a cross-platform, open-source, 4 | arcade game where you must jump from platform to platform to reach higher and 5 | higher (scores). 6 | 7 | Scalavator is inspired by the classic [Doodle 8 | Jump](https://wikipedia.org/wiki/Doodle_Jump), but its mecanisms have been 9 | adapted to be enjoyable to play in non-mobile platforms such as the web or the 10 | desktop. It kept the simplicity of the controls in order to be fully playable 11 | on mobile as well. 12 | 13 | Scalavator was started as a way to demonstrate how to use [the Scala Game 14 | Library (SGL)](https://github.com/regb/scala-game-library), a cross-platform 15 | game engine for the Scala programming language. It is fully open source and its 16 | code is under the MIT license. The goal was to show a small, but complete game 17 | implemented with SGL and taking advantage of the cross-platform features of the 18 | library. 19 | 20 | The current implementation supports three platforms: Android, HTML5, and 21 | Desktop JVM. The core game logic is under the [core](/core) directory tree. The 22 | platform-specific implementations consist of very simple sub-projects (under 23 | [android](/android), [html5](/html5), and [desktop](/desktop), respectively), 24 | with only a single file to define the correct dependencies. For example, here 25 | is the only required [Android 26 | code](/android/src/main/scala/MainActivity.scala), just a couple lines of code. 27 | 28 | Scalavator is available to [try 29 | online](http://regblanc.com/games/scalavator/play.html) from your browser, or 30 | to download for your Android device on [Google 31 | Play](https://play.google.com/store/apps/details?id=com.regblanc.scalavator). 32 | I invite you to try both and spot the differences. 33 | 34 | You can also build and run Scalavator yourself, just run the following command: 35 | 36 | sbt desktop/run 37 | 38 | It should start the Desktop JVM version of Scalavator. You can also build 39 | an Android APK (after installing the Android dependencies) with `sbt 40 | android/android:packageDebug` and a javascript build (after installing 41 | scala.js dependencies) with `sbt html5/fastOptJS`. 42 | 43 | Not that anyone would ever need them, but just in case, all the art assets that 44 | I provided for the game are released in the public domain. Some assets on this 45 | repository might come from a third-party source and are thus only reusable from 46 | their oiginal source (usually, under a creative common license). 47 | 48 | The game is not quite finished yet. The goal is to evolve along with the 49 | development of SGL. The game will be ported to as many platforms as possible, 50 | most notably to iOS, and as an executable for Windows, OSx, and Linux. I also 51 | plan to add support for Leaderboards, achievements, and analytics, and show how 52 | these can be integrated over all these platforms in a shared Scala code base. 53 | Graphics are what they are --- programmer art.. I don't plan to spend much 54 | more time on them, but if you are of the artistic kind, I will appreciate 55 | any contribution. 56 | 57 | If you are interested in trying out SGL, this repository is a good starting 58 | point with a working project. You can then modify the core game logic to go 59 | into any direction you wish. If you do build a game with SGL, please reach out 60 | to [the SGL project](https://github.com/regb/scala-game-library), or get in 61 | touch directly with [me](http://regblanc.com/contact/). 62 | 63 | ## Credits 64 | 65 | * Author, designer, programmer: [Régis Blanc](http://regblanc.com) 66 | * [Ce-gars-là](https://twitter.com/manojah_shanti/status/781884092111548416), 67 | original concept from [Manohar Jonnalagedda](https://twitter.com/manojah_shanti) and [Nicolas Stucki](https://twitter.com/stucki_nicolas). 68 | -------------------------------------------------------------------------------- /android/release.sh: -------------------------------------------------------------------------------- 1 | 2 | jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore release.keystore target/android/output/scalavator-android-release-unsigned.apk scalavator 3 | 4 | zipalign -v 4 target/android/output/scalavator-android-release-unsigned.apk scalavator.apk 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /android/src/main/res/drawable-mdpi: -------------------------------------------------------------------------------- 1 | ../../../../desktop/src/main/resources/drawable -------------------------------------------------------------------------------- /android/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/android/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/src/main/scala/MainActivity.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | 6 | import core._ 7 | 8 | import sgl._ 9 | import sgl.android._ 10 | import sgl.util._ 11 | import sgl.scene._ 12 | 13 | class MainActivity extends Activity with AndroidApp with AbstractApp 14 | with SceneComponent with NoLoggingProvider with SaveComponent { 15 | 16 | override val TargetFps = Some(30) 17 | 18 | type Save = AndroidSave 19 | override val save = new AndroidSave("scalavator-savefile", this) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /art/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 48 | 59 | 70 | 81 | 92 | 103 | 104 | 126 | 128 | 129 | 131 | image/svg+xml 132 | 134 | 135 | 136 | 137 | 138 | 241 | 344 | 349 | 355 | 363 | 371 | 377 | 383 | 389 | 395 | 403 | 411 | 419 | 426 | 432 | 438 | 446 | 447 | 461 | 475 | 488 | 574 | 587 | 600 | 613 | 614 | -------------------------------------------------------------------------------- /art/clouds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 56 | 62 | 69 | 76 | 83 | 90 | 97 | 98 | 99 | 103 | 106 | 113 | 120 | 127 | 134 | 135 | 138 | 145 | 152 | 159 | 166 | 173 | 174 | 177 | 180 | 187 | 194 | 201 | 208 | 215 | 222 | 223 | 224 | 227 | 234 | 241 | 248 | 255 | 262 | 269 | 270 | 273 | 280 | 287 | 294 | 301 | 308 | 309 | 310 | 311 | -------------------------------------------------------------------------------- /art/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 31 | 34 | 38 | 42 | 43 | 53 | 56 | 60 | 64 | 65 | 74 | 77 | 81 | 85 | 86 | 95 | 105 | 115 | 125 | 135 | 138 | 142 | 146 | 147 | 158 | 169 | 170 | 188 | 190 | 191 | 193 | image/svg+xml 194 | 196 | 197 | 198 | 199 | 200 | 205 | 214 | 215 | 219 | 223 | 229 | 233 | 242 | 251 | 260 | 269 | 278 | 287 | 296 | 301 | 306 | 311 | 312 | 321 | 326 | 331 | 336 | 337 | 342 | 347 | 352 | 353 | 354 | 361 | 368 | 374 | 380 | 386 | 393 | 400 | 407 | 413 | 419 | 425 | 432 | 433 | 434 | 435 | -------------------------------------------------------------------------------- /art/platform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 24 | 31 | 32 | 35 | 42 | 43 | 46 | 53 | 54 | 57 | 64 | 65 | 68 | 75 | 76 | 79 | 86 | 87 | 90 | 97 | 98 | 101 | 108 | 109 | 112 | 119 | 120 | 123 | 130 | 131 | 134 | 141 | 142 | 145 | 152 | 153 | 156 | 163 | 164 | 167 | 174 | 175 | 178 | 185 | 186 | 189 | 196 | 197 | 200 | 207 | 208 | 211 | 218 | 219 | 222 | 229 | 230 | 233 | 245 | 246 | 249 | 261 | 262 | 265 | 277 | 278 | 281 | 293 | 294 | 297 | 309 | 310 | 313 | 325 | 326 | 329 | 341 | 342 | 345 | 357 | 358 | 361 | 373 | 374 | 377 | 389 | 390 | 393 | 405 | 406 | 409 | 421 | 422 | 425 | 437 | 438 | 441 | 453 | 454 | 457 | 469 | 470 | 473 | 485 | 486 | 489 | 501 | 502 | 505 | 517 | 518 | 521 | 533 | 534 | 537 | 549 | 550 | 553 | 564 | 565 | 566 | 584 | 586 | 587 | 589 | image/svg+xml 590 | 592 | 593 | 594 | 595 | 596 | 600 | 612 | 623 | 634 | 645 | 656 | 667 | 678 | 694 | 710 | 726 | 742 | 758 | 774 | 790 | 806 | 822 | 839 | 850 | 866 | 882 | 894 | 895 | 896 | -------------------------------------------------------------------------------- /art/test/character_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_idle.png -------------------------------------------------------------------------------- /art/test/character_jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump.png -------------------------------------------------------------------------------- /art/test/character_jump1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump1.png -------------------------------------------------------------------------------- /art/test/character_jump2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump2.png -------------------------------------------------------------------------------- /art/test/character_jump3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump3.png -------------------------------------------------------------------------------- /art/test/character_jump4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump4.png -------------------------------------------------------------------------------- /art/test/character_jump5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump5.png -------------------------------------------------------------------------------- /art/test/character_jump6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_jump6.png -------------------------------------------------------------------------------- /art/test/character_prejump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_prejump.png -------------------------------------------------------------------------------- /art/test/character_prejump1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_prejump1.png -------------------------------------------------------------------------------- /art/test/character_prejump2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_prejump2.png -------------------------------------------------------------------------------- /art/test/character_prejump3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/art/test/character_prejump3.png -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val scalaVer = "2.12.0" 2 | 3 | lazy val commonSettings = Seq( 4 | version := "1.0", 5 | scalaVersion := scalaVer, 6 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") 7 | ) 8 | 9 | val sglHead = "80ac6f77b708e1dfd49e609156b5f7b9452047ea" 10 | val sglGitHubLink = s"git://github.com/regb/scala-game-library.git#$sglHead" 11 | 12 | lazy val sglCoreJVM = ProjectRef(uri(sglGitHubLink), "coreJVM") 13 | lazy val sglCoreJS = ProjectRef(uri(sglGitHubLink), "coreJS") 14 | lazy val sglCoreAndroid = ProjectRef(uri(sglGitHubLink), "coreAndroid") 15 | lazy val sglHtml5 = ProjectRef(uri(sglGitHubLink), "html5") 16 | lazy val sglDesktop = ProjectRef(uri(sglGitHubLink), "desktopAWT") 17 | lazy val sglAndroid = ProjectRef(uri(sglGitHubLink), "android") 18 | 19 | lazy val core = (crossProject.crossType(CrossType.Pure) in file("./core")) 20 | .settings(commonSettings: _*) 21 | .settings(name := "scalavator-core") 22 | .jvmSettings( 23 | exportJars := true 24 | ) 25 | .jvmConfigure(_.dependsOn(sglCoreJVM)) 26 | .jsConfigure(_.dependsOn(sglCoreJS)) 27 | 28 | lazy val coreJVM = core.jvm 29 | lazy val coreJS = core.js 30 | 31 | 32 | lazy val script = taskKey[File]("Create the desktop runner script") 33 | 34 | lazy val runnerScriptTemplate = 35 | """#!/bin/sh 36 | java -classpath "%s" %s "$@" 37 | """ 38 | 39 | lazy val desktop = (project in file("./desktop")) 40 | .settings(commonSettings: _*) 41 | .settings( 42 | name := "scalavator-desktop", 43 | script := { 44 | val cp = (fullClasspath in Runtime).value 45 | val mainClass = "com.regblanc.scalavator.desktop.Main" 46 | val contents = runnerScriptTemplate.format(cp.files.absString, mainClass) 47 | val out = target.value / "scalavator" 48 | IO.write(out, contents) 49 | out.setExecutable(true) 50 | out 51 | } 52 | ) 53 | .dependsOn(sglCoreJVM, sglDesktop, coreJVM) 54 | 55 | lazy val html5 = (project in file("./html5")) 56 | .enablePlugins(ScalaJSPlugin) 57 | .settings(commonSettings: _*) 58 | .settings( 59 | name := "scalavator-html5", 60 | libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.9.1" 61 | ) 62 | .dependsOn(sglCoreJS, sglHtml5, coreJS) 63 | 64 | val scalaAndroidVer = "2.11.8" 65 | 66 | val commonAndroidSettings = Seq( 67 | scalaVersion := scalaAndroidVer, 68 | scalacOptions += "-target:jvm-1.7", 69 | javacOptions ++= Seq("-source", "1.7", "-target", "1.7"), 70 | exportJars := true 71 | ) 72 | 73 | lazy val coreAndroid = (project in file("./core")) 74 | .settings(commonSettings: _*) 75 | .settings(commonAndroidSettings: _*) 76 | .settings( 77 | name := "scalavator-core", 78 | target := baseDirectory.value / ".android" / "target" 79 | ) 80 | .dependsOn(sglCoreAndroid) 81 | 82 | lazy val android = (project in file("./android")) 83 | .enablePlugins(AndroidApp) 84 | .settings(commonSettings: _*) 85 | .settings(commonAndroidSettings: _*) 86 | .settings( 87 | name := "scalavator-android", 88 | useProguard := true, 89 | proguardOptions ++= Seq( 90 | "-dontobfuscate", 91 | "-dontoptimize", 92 | "-keepattributes Signature", 93 | "-dontwarn scala.collection.**", // required from Scala 2.11.3 94 | "-dontwarn scala.collection.mutable.**", // required from Scala 2.11.0 95 | "-dontwarn android.webkit.**", //required by adcolony 96 | "-dontwarn com.immersion.**", //required by adcolony 97 | "-dontnote com.immersion.**", //required by adcolony 98 | "-ignorewarnings", 99 | "-keep class scala.Dynamic", 100 | "-keep class test.**" 101 | ), 102 | platformTarget := "android-23" 103 | ) 104 | .dependsOn(sglCoreAndroid, sglAndroid, coreAndroid) 105 | -------------------------------------------------------------------------------- /core/src/main/scala/App.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package core 3 | 4 | import sgl._ 5 | import scene._ 6 | import util._ 7 | 8 | trait AbstractApp extends MainScreenComponent with LoadingScreenComponent with ViewportComponent { 9 | this: GraphicsProvider with InputProvider with WindowProvider with AudioProvider 10 | with GameStateComponent with SystemProvider 11 | with SceneComponent with LoggingProvider with SaveComponent => 12 | 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/Background.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package core 3 | 4 | import sgl._ 5 | import util._ 6 | 7 | trait BackgroundComponent { 8 | this: MainScreenComponent with GraphicsProvider with WindowProvider 9 | with SystemProvider with LoggingProvider => 10 | 11 | import Graphics._ 12 | 13 | private implicit val Tag = Logger.Tag("background") 14 | 15 | /* 16 | * The background is an infinite sky with clouds. We handle 17 | * scrolling position and try to simulate a paralax scrolling 18 | * effect, so that the sky goes up slower than the foreground. 19 | */ 20 | class Background(cloudsBitmap: Bitmap) { 21 | 22 | /* 23 | * The current height, in terms of the background. 24 | * This is updated by the scrolling mecanism, that 25 | * makes sure to move pixel up slower that the 26 | * foreground. 27 | */ 28 | private var currentHeight: Double = 0 29 | 30 | //whenever the player moves up, you should call scroll up 31 | //with the corresponding delta. 32 | def scrollUp(delta: Double): Unit = { 33 | currentHeight += delta 34 | popNewClouds() 35 | } 36 | def scrollDown(delta: Double): Unit = { 37 | currentHeight -= delta 38 | } 39 | 40 | private case class Cloud(var x: Double, y: Int, cloudFrame: Int) { 41 | val velocity = randomCloudVelocity 42 | } 43 | 44 | private val CloudBaseVelocity: Double = dp2px(10) 45 | private val CloudBonusVelocity: Double = dp2px(5) 46 | private def randomCloudVelocity = CloudBaseVelocity + (1 - 2*math.random)*CloudBonusVelocity 47 | 48 | private val cloudsRegions = Array( 49 | BitmapRegion(cloudsBitmap, 0, 0 , dp2px(152), dp2px(100)), 50 | BitmapRegion(cloudsBitmap, 0, dp2px(100), dp2px(152), dp2px(100)), 51 | BitmapRegion(cloudsBitmap, 0, dp2px(200), dp2px(152), dp2px(100)), 52 | BitmapRegion(cloudsBitmap, 0, dp2px(300), dp2px(152), dp2px(100)), 53 | BitmapRegion(cloudsBitmap, 0, dp2px(400), dp2px(152), dp2px(100))) 54 | 55 | 56 | //we use a hardcoded, repeating pattern of 50 spaces, storing all spaces 57 | //first cloud starts high enough to not overlap with the character 58 | private val spaces: Array[Int] = Array( 59 | dp2px(250), dp2px(175), dp2px(150), dp2px(140), dp2px(165), 60 | dp2px(180), dp2px(205), dp2px(155), dp2px(120), dp2px(175), 61 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 62 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 63 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 64 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 65 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 66 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 67 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165), 68 | dp2px(150), dp2px(165), dp2px(155), dp2px(150), dp2px(165) 69 | ) 70 | 71 | private val maxX = WindowWidth/2 72 | private def randomPosition(): Int = scala.util.Random.nextInt(maxX + dp2px(152)) - dp2px(152) 73 | 74 | //generate random positions for each of the 50 possible repeating offset 75 | private val positions: Array[Int] = Array.fill(50)(randomPosition()) 76 | 77 | //the current list of clouds, (x, y, cloud), with y pointing upwards to 78 | //the sky, meaning that it is reversed with regular coordinate system 79 | private var currentClouds: List[Cloud] = Nil 80 | 81 | private var cloudFrame = 0 82 | private var cloudIndex = 0 83 | private var cloudHeight = spaces(cloudIndex) 84 | 85 | private def popNewClouds(): Unit = { 86 | while(currentHeight + WindowHeight + dp2px(100) > cloudHeight) { 87 | currentClouds ::= (Cloud(positions(cloudIndex), cloudHeight, cloudFrame)) 88 | cloudIndex = (cloudIndex + 1) % spaces.length 89 | cloudHeight += spaces(cloudIndex) 90 | cloudFrame = (cloudFrame+1)%5 91 | } 92 | 93 | currentClouds = currentClouds.filter(c => c.y >= currentHeight) 94 | } 95 | 96 | popNewClouds() 97 | 98 | private val skyPaint = defaultPaint.withColor(Color.rgb(181, 242, 251)) 99 | 100 | def update(dt: Long): Unit = { 101 | currentClouds.foreach(cloud => { 102 | //clouds always float right, I guess it makes more sense due to wind? 103 | cloud.x = cloud.x + cloud.velocity*(dt/1000d) 104 | //and we circle back to left after a while (2*windowwidth) 105 | if(cloud.x > WindowWidth) { 106 | cloud.x = -cloudsRegions(cloud.cloudFrame).width 107 | } 108 | }) 109 | } 110 | 111 | 112 | def render(canvas: Canvas): Unit = { 113 | canvas.drawRect(0, 0, WindowWidth, WindowHeight, skyPaint) 114 | 115 | //var cloudHeight = 0 116 | //var cloudIndex = 0 117 | //while(cloudIndex < spaces.length) { 118 | // cloudHeight += spaces(i) 119 | // if(cloudHeight > currentHeight && cloudHeight < 120 | //for(space 121 | 122 | for(Cloud(x, y, cloudBitmap) <- currentClouds) { 123 | canvas.drawBitmap(cloudsRegions(cloudBitmap), x.toInt, WindowHeight - y + currentHeight.toInt) 124 | } 125 | 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /core/src/main/scala/LoadingScreen.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package core 3 | 4 | import sgl._ 5 | import scene._ 6 | import util._ 7 | 8 | trait LoadingScreenComponent { 9 | this: GraphicsProvider with InputProvider with GameStateComponent 10 | with WindowProvider with SystemProvider with LoggingProvider with MainScreenComponent => 11 | 12 | import Graphics._ 13 | 14 | private implicit val Tag = Logger.Tag("loading-screen") 15 | 16 | var characterIdle: Loader[Bitmap] = null 17 | var characterPrejump: Loader[Bitmap] = null 18 | var characterJump: Loader[Bitmap] = null 19 | var bug: Loader[Bitmap] = null 20 | var platform: Loader[Bitmap] = null 21 | var clouds: Loader[Bitmap] = null 22 | 23 | class ScalavatorLoadingScreen[A](loaders: Seq[Loader[A]]) extends LoadingScreen[A](loaders) { 24 | 25 | override def render(canvas: Canvas): Unit = { 26 | logger.debug("Loading...") 27 | } 28 | 29 | //not the best way to access a promise, but at least 30 | //we try to hide the complexity within the loading 31 | //screen, meaning that the MainScreen just works with 32 | //fully loaded bitmap 33 | override def nextScreen: GameScreen = new MainScreen( 34 | characterIdle.value.get.get, 35 | characterPrejump.value.get.get, 36 | characterJump.value.get.get, 37 | bug.value.get.get, 38 | platform.value.get.get, 39 | clouds.value.get.get 40 | ) 41 | } 42 | 43 | //we need to start loader only now, because it is risky to start 44 | //everything when the whole cake is initializing, as the framework 45 | //might not be quite ready yet 46 | override def startingScreen: GameScreen = { 47 | val pathPrefix = ResourcesPrefix / "drawable" 48 | characterIdle = Graphics.loadImage(pathPrefix / "character_idle.png") 49 | characterPrejump = Graphics.loadImage(pathPrefix / "character_prejump.png") 50 | characterJump = Graphics.loadImage(pathPrefix / "character_jump.png") 51 | bug = Graphics.loadImage(pathPrefix / "bug.png") 52 | platform = Graphics.loadImage(pathPrefix / "platform.png") 53 | clouds = Graphics.loadImage(pathPrefix / "clouds.png") 54 | val allResources = Array( 55 | characterIdle, 56 | characterPrejump, 57 | characterJump, 58 | bug, 59 | platform, 60 | clouds 61 | ) 62 | new ScalavatorLoadingScreen(allResources) 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/scala/MainScreen.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package core 3 | 4 | import sgl._ 5 | import geometry._ 6 | import scene._ 7 | import util._ 8 | 9 | trait MainScreenComponent extends BackgroundComponent { 10 | this: GraphicsProvider with InputProvider with GameStateComponent with WindowProvider 11 | with SystemProvider with AudioProvider with SceneComponent with LoggingProvider 12 | with SaveComponent with ViewportComponent => 13 | 14 | import Graphics._ 15 | 16 | private implicit val Tag = Logger.Tag("main") 17 | 18 | class MainScreen( 19 | characterIdleBitmap: Bitmap, 20 | characterPreJumpBitmap: Bitmap, 21 | characterJumpBitmap: Bitmap, 22 | bugBitmap: Bitmap, 23 | platformBitmap: Bitmap, 24 | cloudsBitmap: Bitmap 25 | ) extends GameScreen { 26 | 27 | override def name = "Scalavator Screen" 28 | 29 | private val Gravity = Vec(0, dp2px(430)) 30 | 31 | private def jumpChargeToImpulsion(jumpCharge: Long): Double = { 32 | //let's try 3 layers, for each 200 ms 33 | val level: Int = { 34 | val tmp = (jumpCharge / 200d).toInt //for each 200ms, we have 1 level 35 | tmp min 2 36 | } 37 | //level is from 0 to MaxLevel 38 | 39 | (1 + 0.3*level)*dp2px(320) 40 | } 41 | 42 | 43 | class Platform(var x: Double, var y: Double, val width: Int, var speed: Double) { 44 | private val region = BitmapRegion(platformBitmap, 0 max (platformBitmap.width-width)/2, 0, platformBitmap.width, platformBitmap.height) 45 | def update(dt: Long): Unit = { 46 | x = x + speed*(dt/1000d) 47 | if(x+width > WindowWidth) { 48 | x = WindowWidth-width 49 | speed = -speed 50 | } else if(x < 0) { 51 | x = 0 52 | speed = -speed 53 | } 54 | } 55 | def render(canvas: Canvas): Unit = { 56 | canvas.drawRepeatedBitmap(region, x.toInt, y.toInt, width, platformBitmap.height) 57 | } 58 | 59 | override def toString = s"Platform($x, $y) with speed $speed" 60 | } 61 | object Platform { 62 | def random(y: Double): Platform = { 63 | val width = dp2px(55 + scala.util.Random.nextInt(40)) 64 | val x = scala.util.Random.nextInt(WindowWidth - width) 65 | val speed = dp2px(80 + scala.util.Random.nextInt(60)) 66 | new Platform(x, y, width, speed) 67 | } 68 | } 69 | 70 | class Bug(var x: Double, var y: Double, var speed: Double) { 71 | private var age: Long = 0 72 | def update(dt: Long): Unit = { 73 | age += dt 74 | x = x + speed*(dt/1000d) 75 | if(x + Bug.Width > WindowWidth) { 76 | x = WindowWidth - Bug.Width 77 | speed = -speed 78 | } else if(x < 0) { 79 | x = 0 80 | speed = -speed 81 | } 82 | } 83 | def render(canvas: Canvas): Unit = { 84 | val frame = if(speed > 0) bugRightAnimation.currentFrame(age) else bugLeftAnimation.currentFrame(age) 85 | canvas.drawBitmap(frame, x.toInt, y.toInt) 86 | } 87 | 88 | // Character position (left-bottom of the hittable area) 89 | def hitCharacter(characterX: Double, characterY: Double): Boolean = { 90 | (characterX + CharacterHitboxStart >= x + dp2px(6) && characterX < x + Bug.Width - dp2px(6)) && 91 | (characterY >= y + dp2px(15) && characterY - CharacterIdleHeight < y + Bug.Height - dp2px(10)) 92 | } 93 | 94 | } 95 | object Bug { 96 | val Width = dp2px(64) 97 | val Height = dp2px(64) 98 | def random(y: Double): Bug = { 99 | val x = scala.util.Random.nextInt(WindowWidth - Width) 100 | val speed = dp2px(90 + scala.util.Random.nextInt(20)) 101 | new Bug(x, y, speed) 102 | } 103 | } 104 | 105 | 106 | private var randomNextPop: Int = 0 107 | // Distance to jump until we pop the next bug 108 | private var distanceToNextBug: Int = WindowHeight 109 | private def generateRandomNextPop: Int = dp2px(70 + scala.util.Random.nextInt(30)) 110 | private def generateRandomDistanceToBug: Int = dp2px(WindowHeight/2 + WindowHeight) 111 | 112 | private val startingPlatform = new Platform(0, WindowHeight-platformBitmap.height, WindowWidth, 0) 113 | private var platforms: List[Platform] = List(startingPlatform) 114 | 115 | { 116 | var h = WindowHeight - dp2px(100) 117 | while(h > 0) { 118 | platforms ::= Platform.random(h) 119 | h -= dp2px(100) 120 | } 121 | randomNextPop = -h 122 | } 123 | 124 | private var bugs: List[Bug] = List() 125 | // private var bugs: List[Bug] = List(new Bug(0, WindowHeight - 200, 100)) 126 | 127 | //character real height varies from sprite to sprite, and the value 128 | //refers to the sprite height (but when idle, it uses ony about 3/4 of 129 | //that height). The width only refers to the inner part that is collidable 130 | //and not the full sprite with the arms. 131 | // The pixel at which the character becomes collidable 132 | private val CharacterHitboxStart = dp2px(18) 133 | // The width of the hitbox from the starting point 134 | private val CharacterHitboxWidth = dp2px(24) 135 | private val CharacterHeight = characterIdleBitmap.height //dp2px(89) 136 | private val CharacterIdleHeight = dp2px(65) 137 | 138 | //character position is the bottom left corner of the hittable area. The actual visible sprite 139 | //expands slightly more to the left and the right of the CharacterWidth. 140 | private var characterPosition = Point(WindowWidth/2-characterIdleBitmap.width/2+CharacterHitboxStart, WindowHeight - platformBitmap.height) 141 | private var characterVelocity = Vec(0, 0) 142 | 143 | private val characterIdleFrames = Array( 144 | BitmapRegion(characterIdleBitmap, 0 , 0, dp2px(60), CharacterHeight) 145 | ) 146 | 147 | private val characterPreJumpFrames = Array( 148 | BitmapRegion(characterPreJumpBitmap, 0 , 0, dp2px(60), CharacterHeight), 149 | BitmapRegion(characterPreJumpBitmap, dp2px(60) , 0, dp2px(60), CharacterHeight), 150 | BitmapRegion(characterPreJumpBitmap, dp2px(120), 0, dp2px(60), CharacterHeight) 151 | ) 152 | private val characterJumpFrames = Array( 153 | BitmapRegion(characterJumpBitmap, 0 , 0, dp2px(60), CharacterHeight), 154 | BitmapRegion(characterJumpBitmap, dp2px(60) , 0, dp2px(60), CharacterHeight), 155 | BitmapRegion(characterJumpBitmap, dp2px(120), 0, dp2px(60), CharacterHeight), 156 | BitmapRegion(characterJumpBitmap, dp2px(180), 0, dp2px(60), CharacterHeight), 157 | BitmapRegion(characterJumpBitmap, dp2px(240), 0, dp2px(60), CharacterHeight), 158 | BitmapRegion(characterJumpBitmap, dp2px(300), 0, dp2px(60), CharacterHeight) 159 | ) 160 | 161 | private val characterLandingFrames = Array( 162 | characterJumpFrames(1), 163 | characterJumpFrames(0), 164 | characterJumpFrames(1), 165 | characterJumpFrames(2), 166 | characterIdleFrames(0) 167 | ) 168 | 169 | private val BugLeftFrames = Array( 170 | BitmapRegion(bugBitmap, 0 , 0, Bug.Width, Bug.Height), 171 | BitmapRegion(bugBitmap, dp2px(64) , 0, Bug.Width, Bug.Height), 172 | BitmapRegion(bugBitmap, dp2px(128), 0, Bug.Width, Bug.Height) 173 | ) 174 | private val BugRightFrames = Array( 175 | BitmapRegion(bugBitmap, 0 , dp2px(64), Bug.Width, Bug.Height), 176 | BitmapRegion(bugBitmap, dp2px(64) , dp2px(64), Bug.Width, Bug.Height), 177 | BitmapRegion(bugBitmap, dp2px(128), dp2px(64), Bug.Width, Bug.Height) 178 | ) 179 | 180 | private val CharacterIdleAnimation = new Animation(200, characterIdleFrames, Animation.Loop) 181 | private val CharacterPreJumpAnimation = new Animation(100, characterPreJumpFrames, Animation.Normal) 182 | private val CharacterStartJumpAnimation = new Animation(100, characterJumpFrames, Animation.Normal) 183 | private val CharacterTopJumpAnimation = new Animation(200, characterJumpFrames.reverse.take(5), Animation.Normal) 184 | private val CharacterLandingAnimation = new Animation(150, characterLandingFrames, Animation.Normal) 185 | private val bugLeftAnimation = new Animation(200, BugLeftFrames, Animation.LoopReversed) 186 | private val bugRightAnimation = new Animation(200, BugRightFrames, Animation.LoopReversed) 187 | 188 | 189 | //this looks like a standard wrapper technique for a character/sprite 190 | //that can have several state and thus several animation. It seems 191 | //more convenient to have an internal shared elapsed time, that 192 | //is reset each time the animation change, and properly updated 193 | //by a simple call to update, no matter what the current animation is. 194 | //The alternative being to store global variables in the game logic, 195 | //and tracking which current animation is going on to get the frame 196 | //with the proper elapsed time. 197 | //So maybe, this could be part of the library 198 | class CharacterAnimation { 199 | private var _currentAnimation: Animation = CharacterIdleAnimation 200 | private var elapsed: Long = 0 201 | 202 | def update(dt: Long) = elapsed += dt 203 | 204 | def currentAnimation_=(animation: Animation): Unit = { 205 | _currentAnimation = animation 206 | elapsed = 0 207 | } 208 | def currentAnimation = _currentAnimation 209 | 210 | def currentFrame = _currentAnimation.currentFrame(elapsed) 211 | } 212 | 213 | private var characterAnimation = new CharacterAnimation 214 | 215 | private var standingPlatform: Option[Platform] = Some(startingPlatform) 216 | 217 | // The score is how high we go, it's a long, just in case 218 | var currentScore: Double = 0 219 | private var highestScore: Long = 0 220 | 221 | private val hud = new Hud(this) 222 | 223 | private var totalTime: Long = 0 224 | 225 | private var chargeJumpStart: Long = 0 226 | 227 | private var hitByBug = false 228 | private var freeFalling = false 229 | private var scrollDownVelocity = 0d 230 | private var scrolledDown = 0d 231 | 232 | private var gameOver = false 233 | 234 | def handleInput(ev: Input.InputEvent): Unit = { 235 | ev match { 236 | case Input.TouchDownEvent(_, _, _) | Input.MouseDownEvent(_, _, Input.MouseButtons.Left) => 237 | if(gameOver) 238 | restart() 239 | 240 | if(standingPlatform.nonEmpty) { 241 | chargeJumpStart = totalTime 242 | characterAnimation.currentAnimation = CharacterPreJumpAnimation 243 | } 244 | case Input.TouchUpEvent(_, _, _) | Input.MouseUpEvent(_, _, Input.MouseButtons.Left) => 245 | if(chargeJumpStart != 0 && !hitByBug && !freeFalling) { 246 | val totalCharge = totalTime - chargeJumpStart 247 | chargeJumpStart = 0 248 | logger.info("Jump input from player detected. total charge: " + totalCharge) 249 | if(standingPlatform.nonEmpty) { 250 | standingPlatform = None 251 | characterVelocity = Vec(0, - jumpChargeToImpulsion(totalCharge)) 252 | characterAnimation.currentAnimation = CharacterStartJumpAnimation 253 | } 254 | } 255 | case _ => () 256 | } 257 | } 258 | 259 | private val background = new Background(cloudsBitmap) 260 | 261 | private var accumulatedDelta = 0l 262 | private val FixedDelta = 5l 263 | override def update(dt: Long): Unit = { 264 | Input.processEvents(handleInput) 265 | 266 | totalTime += dt 267 | 268 | accumulatedDelta += dt 269 | 270 | while(accumulatedDelta / FixedDelta != 0) { 271 | accumulatedDelta -= FixedDelta 272 | fixedUpdate(FixedDelta) 273 | } 274 | 275 | characterAnimation.update(dt) 276 | 277 | background.update(dt) 278 | } 279 | 280 | def fixedUpdate(dt: Long): Unit = { 281 | hud.sceneGraph.update(dt) 282 | 283 | val originalCharacterFeet = characterPosition.y 284 | platforms.foreach(_.update(dt)) 285 | bugs.foreach(_.update(dt)) 286 | 287 | if(gameOver) { 288 | // wait for a touch event to restart. 289 | } else if(freeFalling) { 290 | characterVelocity += Gravity*(dt/1000d) 291 | characterPosition += characterVelocity*(dt/1000d) 292 | if(characterPosition.y.toInt - WindowHeight > 0) { 293 | scrollDownVelocity = 2*characterVelocity.y 294 | } 295 | if(characterPosition.y > WindowHeight + CharacterHeight) { 296 | gameOver = true 297 | highestScore = save.getLongOrElse("highest_score", 0) 298 | if(currentScore.toInt > highestScore) { 299 | highestScore = currentScore.toInt 300 | save.putLong("highest_score", highestScore) 301 | } 302 | } else if(scrolledDown < WindowHeight) { 303 | val scrollDownDistance = scrollDownVelocity*(dt/1000d) 304 | scrollDown(scrollDownDistance) 305 | scrolledDown += scrollDownDistance 306 | } 307 | } else { 308 | standingPlatform match { 309 | case None => { 310 | val previousVelocity = characterVelocity 311 | 312 | characterVelocity += Gravity*(dt/1000d) 313 | characterPosition += characterVelocity*(dt/1000d) 314 | 315 | //TODO: maybe we should start an animation just before reaching the top, 316 | // but we need to create State within the character to properly handle 317 | // the different phase of the jump 318 | if(characterVelocity.y >= -dp2px(50)) { //trying to detect end of the jump 319 | } 320 | 321 | if(previousVelocity.y <= 0 && characterVelocity.y >= 0) { 322 | //if reached peak of the jump 323 | //characterAnimation.currentAnimation = CharacterEndJumpAnimation 324 | characterAnimation.currentAnimation = CharacterTopJumpAnimation 325 | } 326 | 327 | if(characterPosition.y.toInt < WindowHeight/2) 328 | scrollUp(WindowHeight/2 - characterPosition.y.toInt) 329 | } 330 | case Some(platform) => { 331 | characterPosition = characterPosition + Vec(1,0)*platform.speed*(dt/1000d) 332 | if(characterPosition.x < 0) 333 | characterPosition = characterPosition.copy(x = 0) 334 | if(characterPosition.x + CharacterHitboxWidth > WindowWidth) 335 | characterPosition = characterPosition.copy(x = WindowWidth-CharacterHitboxWidth) 336 | } 337 | } 338 | val newCharacterFeet = characterPosition.y 339 | if(newCharacterFeet > originalCharacterFeet) { //if falling 340 | platforms.find(p => p.y+1 > originalCharacterFeet && p.y+1 <= newCharacterFeet && 341 | p.x <= characterPosition.x + CharacterHitboxWidth && p.x + p.width >= characterPosition.x 342 | ).foreach(platform => { 343 | standingPlatform = Some(platform) 344 | characterAnimation.currentAnimation = CharacterLandingAnimation 345 | }) 346 | 347 | if(standingPlatform == None && characterPosition.y > WindowHeight) { 348 | freeFalling = true 349 | } 350 | } 351 | if(bugs.exists(_.hitCharacter(characterPosition.x, characterPosition.y))) { 352 | characterVelocity = Vec(0, dp2px(10)) 353 | freeFalling = true 354 | hitByBug = true 355 | } 356 | } 357 | 358 | } 359 | 360 | def restart(): Unit = { 361 | gameState.newScreen( 362 | new MainScreen( 363 | characterIdleBitmap, characterPreJumpBitmap, characterJumpBitmap, 364 | bugBitmap, platformBitmap, cloudsBitmap 365 | ) 366 | ) 367 | } 368 | 369 | private val gameOverPaint = defaultPaint.withColor(Color.Black).withFont(Font.Default.withSize(dp2px(20))).withAlignment(Alignments.Center) 370 | override def render(canvas: Canvas): Unit = { 371 | 372 | background.render(canvas) 373 | 374 | platforms.foreach(_.render(canvas)) 375 | bugs.foreach(_.render(canvas)) 376 | 377 | if(hitByBug) { 378 | canvas.withSave { 379 | canvas.translate((characterPosition.x.toInt - CharacterHitboxStart) + characterIdleBitmap.width/2, characterPosition.y.toInt - CharacterHeight/2) 380 | //-dp2px(9), characterPosition.y.toInt-CharacterHeight) 381 | canvas.rotate(math.Pi) 382 | canvas.drawBitmap(characterAnimation.currentFrame, -characterIdleBitmap.width/2, -CharacterHeight/2) 383 | } 384 | } else { 385 | canvas.drawBitmap(characterAnimation.currentFrame, 386 | characterPosition.x.toInt- CharacterHitboxStart, characterPosition.y.toInt-CharacterHeight) 387 | } 388 | 389 | hud.sceneGraph.render(canvas) 390 | 391 | if(gameOver) { 392 | canvas.drawString("Score: " + currentScore.toInt, WindowWidth/2, WindowHeight/2 - dp2px(14), gameOverPaint) 393 | canvas.drawString("Highest Score: " + highestScore, WindowWidth/2, WindowHeight/2 + dp2px(14), gameOverPaint) 394 | 395 | if((totalTime/700d).toInt % 2 == 0) 396 | canvas.drawString("Press to start a new game", WindowWidth/2, WindowHeight*3/4, gameOverPaint) 397 | } 398 | 399 | } 400 | 401 | /* 402 | * When jumping passed half the screen, we scroll up to maintain the character 403 | * in the middle. We never scroll down, as essentially everything outside of the 404 | * screen disappeared. 405 | * 406 | * We don't use a camera, we just shift everything down a bit, and drop the platform 407 | * when it goes off screen. 408 | */ 409 | private def scrollUp(distance: Int): Unit = { 410 | platforms.foreach(plat => plat.y += distance) 411 | bugs.foreach(bug => bug.y += distance) 412 | characterPosition = characterPosition + Vec(0, distance.toDouble) 413 | 414 | // Since we base the score on the distance (in actual pixels), we 415 | // need to convert that distance back to dp, otherwise people 416 | // with high density screens would get a much higher score while 417 | // jumping the same distance. 418 | currentScore += px2dp(distance) 419 | 420 | randomNextPop -= distance 421 | if(randomNextPop <= 0) { 422 | randomNextPop = generateRandomNextPop 423 | platforms ::= Platform.random(0) 424 | } 425 | 426 | distanceToNextBug -= distance 427 | if(distanceToNextBug <= 0) { 428 | distanceToNextBug = generateRandomDistanceToBug 429 | bugs ::= Bug.random(-Bug.Height) 430 | } 431 | 432 | platforms = platforms.filterNot(p => p.y > WindowHeight) 433 | bugs = bugs.filterNot(b => b.y > WindowHeight) 434 | 435 | //paralax scrolling with background 436 | background.scrollUp(distance/3d) 437 | } 438 | 439 | // scroll down is the inverse of scroll up, except that it will not generate 440 | // any new platforms or enemies. We use it for our game over animation. 441 | private def scrollDown(distance: Double): Unit = { 442 | platforms.foreach(plat => plat.y -= distance) 443 | bugs.foreach(bug => bug.y -= distance) 444 | characterPosition = characterPosition - Vec(0, distance) 445 | } 446 | } 447 | 448 | 449 | class Hud(mainScreen: MainScreen) { 450 | val viewport = new Viewport(WindowWidth, WindowHeight) 451 | val sceneGraph = new SceneGraph(WindowWidth, WindowHeight, viewport) 452 | 453 | private val group = new SceneGroup(0, 0, WindowWidth, dp2px(40)) 454 | private val groupBackground = new GroupBackground 455 | private val titleLabel = new TitleLabel 456 | val scoreLabel = new ScoreLabel 457 | group.addNode(groupBackground) 458 | group.addNode(titleLabel) 459 | group.addNode(scoreLabel) 460 | sceneGraph.addNode(group) 461 | 462 | private val textPaint = defaultPaint.withColor(Color.White).withFont(Font.Default.withSize(dp2px(18))) 463 | 464 | class GroupBackground extends SceneNode(0, 0, 0, 0) { 465 | override def update(dt: Long): Unit = {} 466 | override def render(canvas: Canvas): Unit = { 467 | canvas.drawColor(Color.Red) 468 | } 469 | } 470 | class TitleLabel extends SceneNode(dp2px(15), dp2px(25), 0, 0) { 471 | override def update(dt: Long): Unit = {} 472 | override def render(canvas: Canvas): Unit = { 473 | canvas.drawString("Scalavator", x.toInt, y.toInt, textPaint) 474 | } 475 | } 476 | class ScoreLabel extends SceneNode(WindowWidth-dp2px(15), dp2px(25), 0, 0) { 477 | override def update(dt: Long): Unit = {} 478 | override def render(canvas: Canvas): Unit = { 479 | canvas.drawString(mainScreen.currentScore.toInt.toString, x.toInt, y.toInt, textPaint.withAlignment(Alignments.Right)) 480 | } 481 | } 482 | 483 | } 484 | 485 | } 486 | -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/bug.png -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/character_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/character_idle.png -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/character_jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/character_jump.png -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/character_prejump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/character_prejump.png -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/clouds.png -------------------------------------------------------------------------------- /desktop/src/main/resources/drawable/platform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/desktop/src/main/resources/drawable/platform.png -------------------------------------------------------------------------------- /desktop/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package desktop 3 | 4 | import core._ 5 | 6 | import sgl._ 7 | import sgl.util._ 8 | import sgl.awt._ 9 | import sgl.awt.util._ 10 | import sgl.scene._ 11 | 12 | 13 | /** Wire backend to the App here */ 14 | object Main extends AbstractApp with AWTApp 15 | with SceneComponent with VerboseStdOutLoggingProvider with SaveComponent { 16 | 17 | override val TargetFps = Some(60) 18 | 19 | override val frameDimension = (400, 650) 20 | 21 | type Save = FileSave 22 | override val save: Save = new FileSave("./scalavator.save") 23 | 24 | } 25 | -------------------------------------------------------------------------------- /html5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /html5/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package com.regblanc.scalavator 2 | package html5 3 | 4 | import sgl._ 5 | import sgl.scene._ 6 | import sgl.html5._ 7 | import sgl.html5.themes._ 8 | import sgl.util._ 9 | import sgl.html5.util._ 10 | 11 | import scala.scalajs.js.annotation.JSExport 12 | 13 | @JSExport 14 | object Main extends core.AbstractApp with Html5App 15 | with Html5VerboseConsoleLoggingProvider with SceneComponent 16 | with InputHelpersComponent with LocalStorageSaveComponent { 17 | 18 | //We should not force the fps on Html5 and just let 19 | //requestAnimationFrame do its best 20 | override val TargetFps: Option[Int] = None 21 | 22 | override val theme = new DefaultTheme { 23 | override val maxFrame = (400, 650) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /html5/static: -------------------------------------------------------------------------------- 1 | ../desktop/src/main/resources -------------------------------------------------------------------------------- /marketing/feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/feature.png -------------------------------------------------------------------------------- /marketing/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/icon.png -------------------------------------------------------------------------------- /marketing/screenshots/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/screenshots/screenshot1.png -------------------------------------------------------------------------------- /marketing/screenshots/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/screenshots/screenshot2.png -------------------------------------------------------------------------------- /marketing/screenshots/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/screenshots/screenshot3.png -------------------------------------------------------------------------------- /marketing/screenshots/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/regb/scalavator/f29ba6e4b5e0c34e6a0f1047b79a3073b27d9021/marketing/screenshots/screenshot4.png -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.13 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.13") 2 | 3 | addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.1") 4 | --------------------------------------------------------------------------------