-
Notifications
You must be signed in to change notification settings - Fork 4
LevelLock on LevelSelectScreen
The LevelSelectScreen class in the game is responsible for implementation of level locking mechanism. Level locking mechanism restricts players from accessing levels they haven't reached or unlocked yet.
As you can see here, DESERT and LAVA levels are locked initially, symbolised by their black and white texture, and hence can't be accessed by players as soon as they enter the Game.
The value of currentLevel
is passed as a parameter when an instance of LevelSelectScreen
is created. This happens in the constructor:
public LevelSelectScreen(GdxGame game, int currentLevel) {
this.currentLevel = currentLevel;
// ... rest of the constructor code ...
}
When creating an instance of LevelSelectScreen
, the caller provides the value of currentLevel
, and it's stored in the instance variable currentLevel
for that specific LevelSelectScreen
object. There is no default value set for currentLevel
within the LevelSelectScreen
class.
In the game, each level (planet) has a level. But as the game follows an unconventional numbering, i.e. ICE (1) -> DESERT (default i.e. =0) -> LAVA (2)
. To solve this issue, and make the logic of code easier to understand and implement, the LevelSelectScreen
uses a mapToConventional()
method.
public int mapToConventional(int unconventionalNumber) {
switch (unconventionalNumber) {
case -1:
return -1;
case 1:
return 0;
case 0:
return 1;
case 2:
return 2;
default:
throw new IllegalArgumentException("Invalid planet number");
}
}
The logic for this method is:
- When unconventional level of planets is passed onto
LevelSelectScreen
, it modifies them to make them more along the lines of0 -> 1 -> 2
. This is a more conventional numbering that makes it easy to apply logic. - For -1, the
LevelSelectScreen
is initialised to -1, whenever it is accessed from StoryScreen.
Level-locking mechanism is mainly achieved through the use of two main methods: spawnPlanets
and spawnPlanetBorders
.
The spawnPlanets
method is responsible for rendering planets on the level selection screen. Here's how the mechanism works:
- The method checks the current game state using the
currentLevel
variable. ThiscurrentLevel
is then set ashighestLevelReached
in the method. - As the game follows an unconventional approach of numbering each level, the method uses
mapToConventional()
to convert it into a more conventional numbering to make it easy for loading correct images. - The
spawnPlanet
method is invoked for each planet type (ICE, DESERT, LAVA) with specific parameters including their positions, visuals, and associated level numbers. - Inside the
spawnPlanet
method, the code decides which image to use for each planet based on thecurrentLevel
and the planet's level. - If a player has reached or surpassed a planet's level requirement, the planet is rendered in its colorful, unlocked state.
- If a player hasn't reached a planet's level requirement, the planet is rendered in a black & white state, indicating it's locked.
private void spawnPlanet(int width, int height, int posx, int posy, String planetName, int version, int frame, int levelNumber) {
int highestLevelReached = currentLevel;
levelNumber = mapToConventional(levelNumber);
if (levelNumber == 0 && highestLevelReached >= -1) { // ICE planet, which is always unlocked initially
planet = new Texture(String.format("planets/%s/%d/%d.png", planetName, version, frame));
} else if (levelNumber == 1 && highestLevelReached >= 0) { // DESERT planet, unlocked only when highestLevelReached is 0
planet = new Texture(String.format("planets/%s/%d/%d.png", planetName, version, frame));
} else if (levelNumber == 2 && highestLevelReached >= 1) { // LAVA planet, unlocked after DESERT
planet = new Texture(String.format("planets/%s/%d/%d.png", planetName, version, frame));
} else {
// Display the planet in b&w if it's locked
planet = new Texture(String.format("planets/%s_bw/%d/%d.png", planetName, version, frame));
}
// other code
}
The spawnPlanetsBorders
method is responsible for rendering borders around the planets, and checking if the level being hovered over is unlocked. Here's how the mechanism works:
- If the mouse is hovering over a planet, a border is drawn around the planet.
- The method checks if the planet's level is unlocked based on the
currentLevel
value. - If a player clicks on an unlocked planet, the game proceeds to that level. However, clicking on a locked planet generates a log statement indicating an attempt to load a locked level.
private void spawnPlanetBorders() {
int highestLevelReached = currentLevel;
....
for (int[] planet : Planets.PLANETS) {
Rectangle planetRect = new Rectangle(planet[0], planet[1], planet[2], planet[3]);
if (planetRect.contains(mousePos.x, (float) Gdx.graphics.getHeight() - mousePos.y)) {
// If the mouse is over a planet, draw the planet border
Sprite planetBorder = new Sprite(new Texture("planets/planetBorder.png"));
batch.draw(planetBorder, planet[0] - 2.0f, planet[1] - 2.0f, planet[2] + 3.0f, planet[3] + 3.0f);
int conventionalPlanetLevel = mapToConventional(planet[4]);
// Check if planet level is unlocked before allowing click
...
if (Gdx.input.justTouched()) {
if (conventionalPlanetLevel == 0 && highestLevelReached >= -1) { // ICE planet, which is always unlocked initially
loadPlanetLevel(planet);
} else if (conventionalPlanetLevel == 1 && highestLevelReached >= 0) { // DESERT planet, unlocked only when highestLevelReached is 0
loadPlanetLevel(planet);
} else if (conventionalPlanetLevel == 2 && highestLevelReached >= 1) { // LAVA planet, unlocked after DESERT
loadPlanetLevel(planet);
} else {
logger.info("Attempted to load locked level {}", planet[4]);
// Add feedback for the player here if necessary
}
}
}
}
This sequence diagram tells how level locking mechanism is designed to work work with different screens across the game.
Since, unit testing of the mechanism was nearly impossible. I undertook a visual approach. To save time, this was done by modifying the win & lose condition in MainGameScreen, to trigger one of these conditions, and therefore trigger one of WinningScreen
, NextLevelScreen
and LoosingScreen
, as soon as the level was clicked.