├── .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 |
614 |
--------------------------------------------------------------------------------
/art/clouds.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
311 |
--------------------------------------------------------------------------------
/art/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
435 |
--------------------------------------------------------------------------------
/art/platform.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------