A short game in the style of classic 2.5D raycasters
The short game being, a psychological horror one.
It is best to use IntelliJ IDEA to open this project, as there are some run configurations specific to the development workflow.
To run the project, in IDEA simply run the JavaGame [run] configuration. In addition to this being
able to run the project, this also runs the TexturePacker which compiles all the textures in the /textures/
directory by supplying the argument -pt (or --packTextures). This is necessary before compiling binaries
for the project.
IDEA should be able to automatically set the correct JDK versions, both for the project and for Gradle, which is the build system used by the project. If this is not the case:
To set the correct project SDK, head to Project Structure -> Project -> SDK and make sure it is on JDK 21
(the distribution used in particular is JBR 21, by JetBrains)
To set the correct SDK for Gradle, head over to Build, Execution, Deployment -> Build Tools -> Gradle -> Gradle JVM criteria -> Version and change to version 21.
It is possible to compile the project to produce standalone binaries. This is through a Gradle plugin,
Construo, which gdx-liftoff scaffolds the project with. The run configuration JavaGame [packageWinX64]
creates an executable (for Windows) that runs the .jar of the game with a bundled JRE. It is also
possible to compile to other targets (although only the Windows one has been tested so far). Refer to the
commands for Construo in Gradle.
It would be best to play the game first! The playtime is roughly 20 minutes, based on playtesting.
This entire section will be marked as a spoiler (or at least, it should render as such in the markdown), as the game is best experienced going in blind, before diving into specifics. Otherwise, this contains most information about the game itself.
The game takes lots of inspiration from analog horror, an emergent genre of videos primarily on YouTube.
This genre of horror features heavy use of the "analog" rendering effect and the artifacts they produce,
such as old VHS and CRT monitors.
The interior structure of the mazes the player explores is directly inspired by the interior of the CIC
building itself. The hallways of the building had this liminal space quality to it that sparked the idea
of an infinite maze crawler with its architechture in mind.
On the more technical side of things, the project uses the libGDX library, which is built on top of
LWJGL3 (Lightweight Java Game Library 3) which provides useful tools to interface with the OpenGL API.
The raycast renderer of the game itself went two iterations. The first implementation was drawn entirely
on the main CPU thread, which did not scale very well with high pixel density.
I opted to write the renderer (with lot's of help from Lode's raycasting tutorial) as a shader in GLSL.
This resulted in much better frame times in terms of rendering. The renderer by far took the most time
in terms of development. This also involves the entire post-processing pipeline, which is responsible for
the look of the game itself, featuring color quantization and dithering and the CRT screen monitor.
In terms of applications of OOP, this can be most particularly seen in the Entity hiearchy. Entity is
an abstract class that superclass of all related objects. The StaticEntity class is responsible for
instantiating non-moving, non-animated sprites in the game, such as the plants and the sprinklers.
AnimatedEntity inherits Entity to feature being able to swap betweeen sprite "frames" to create an
animated sprite. SmilingOne entity inherits this, swapping between two sets of frames itself, (the
normal frames and the frames where it blinks).
The Entity class itself also features lots of public methods to aid in pathfinding and distance checking
between each other. While for the game at the moment, this really only applies between the player and
the "monster", designing it this way can help futureproof a game.
It's also been a pleasure to find some use for Discrete Math concepts in the development of this game,
particularly graph theory as it is particularly important for the randomized level generation algorithms
as well the pathfinding/avoidance algorithms for Entities.
With that said, there's still lots of things about the codebase that are not as organized I would've liked
them to be. While time constraints certainly played a part in such, the state of the codebase at the moment,
in particular keeping track of the game state is especially tedious and error-prone.
With all that said, I hope the game can be at least enjoyable. While the purpose of this project is primarily for a CS project submission, many non-Computer Science related things were heavily involved in the creation of this project. The songs in particular (apart from the public domain one) have been composed by myself, and all the sprite assets are original. This has been such a huge undertaking and I learned so much just from the month that I spent developing this game. I'm glad at the very least that while it's still fairly short, I'm satisfied with how it ended up with the time I've been allotted. I would love to write a much more detailed write up, but this is all for now.
Below is information on the template used to scaffold the project, along with useful information on the build system.
A libGDX project generated with gdx-liftoff.
This project was generated with a template including simple application launchers and an ApplicationAdapter extension that draws libGDX logo.
core: Main module with the application logic shared by all platforms.lwjgl3: Primary desktop platform using LWJGL3; was called 'desktop' in older docs.
This project uses Gradle to manage dependencies.
The Gradle wrapper was included, so you can run Gradle tasks using gradlew.bat or ./gradlew commands.
Useful Gradle tasks and flags:
--continue: when using this flag, errors will not stop the tasks from running.--daemon: thanks to this flag, Gradle daemon will be used to run chosen tasks.--offline: when using this flag, cached dependency archives will be used.--refresh-dependencies: this flag forces validation of all dependencies. Useful for snapshot versions.build: builds sources and archives of every project.cleanEclipse: removes Eclipse project data.cleanIdea: removes IntelliJ project data.clean: removesbuildfolders, which store compiled classes and built archives.eclipse: generates Eclipse project data.idea: generates IntelliJ project data.lwjgl3:jar: builds application's runnable jar, which can be found atlwjgl3/build/libs.lwjgl3:run: starts the application.test: runs unit tests (if any).
Note that most tasks that are not specific to a single project can be run with name: prefix, where the name should be replaced with the ID of a specific project.
For example, core:clean removes build folder only from the core project.