Hello! I’m happy to show my game advances since previous updates! I almost finish my 5 minigame in my game the goal is to have 47 :.
U can see a demo of it working here:
Notice, when access the game Demo u will be in my university campus, so u must walk and talk with the NPC to access the minigame.
My Developer Experience with Playcanvas
I’m a web developer for 1-2 years, and i’m working with playcanvas for 5 months, i felt like i growth a lot! The Playcanvas api docs is just amazing when u start to read and understend it more!
I have not experience with anoter engines or tools, but i fell like playcanvas is just complete. Has some issues. Like boring problems with script creations needing reload to show , or the AmmoJs as a not too good physics engine.or something like that. But these problems are just details.
The Project
The project started 5 months ago, and is being updated every day since then.
I really think this is a BIG Playcanvas Project, in the current state has a lot of scripts and things that i would be happy to help and teach people to implement in their own games, like Inventory System. Game Saves MiniGames, Conquest System, Dialog. And so on.
I’m from Brazil. And the game is specific for my university so all the texts obviously is in PT-BR, but would not be a great problem to make it translatable for another languages like english with the project structure.
My dificulties.
So i will make this post like a kind of Devlog, so let’s move on and see some code!
In the first moment is good to talk about what is this minigame? This minigame is a Scape Room in the Wright course, so the player must enter the room collect evidences in 5 minutes (in the demo i reduced to 1) and use it in the tribunal.
Has some cool features like locked door, item management and so on!
But let’s talk about code now!
The major problem of making it is THE SAVE!! In my opinion is kind of dificult to decide what must be saved to next session or what must not, or what is session specific so in the next session should not being “remembed”, i’ll describe it better now!
Like the player inventory or position or the conquests this player has, it is to be saved for every user session and it’s simple and easy to see.
BUT, things like the minigame state (the items he collected) doors he unlocked, at the first time i’ve think to not save it because is minigame specific, but it causes problems when the player moves to the starting scene. The thinking room where he can read the items descriptions to use it in the tribunal. Because when he moves back must unlock the doors unlocked it again, and repick the items, but the inventory is saved so it duplicate the items… And these problems:
And here is the sollution.
/**
* Manages the state and rendering logic for items in the game.
* This class handles both stateful and stateless items, determining
* whether an item should be rendered and handling item collection.
*/
class ItemStateManager {
/**
* Retrieves the items for the current scene from the player's state.
* It distinguishes between stateful and stateless items.
*
* @returns {Object} An object containing `statefull` and `stateless` items.
*/
static get sceneItems() {
const sceneName = State.player.currentScene;
State.player.items = State.player.items || {};
return {
statefull: State.player.items[sceneName] || {
},
stateless: window.game.items.stateless[sceneName] || {
}
};
}
/**
* Determines whether a given item should be rendered.
* It checks if the item is in the player's inventory or the current scene's items.
*
* @param {string} guid - The unique identifier of the item.
* @param {boolean} [isStateless=false] - Indicates if the item is stateless.
* @returns {boolean} True if the item should be rendered, otherwise false.
*/
static shouldRenderItem(
guid,
isStateless = false
) {
let item = undefined;
if(!State.player.inventory.backgroundItems) return true;
State.player.inventory.backgroundItems.forEach((line) => {
line.forEach((_item) => {
if(_item && _item.guid === guid) item = _item;
});
});
if(item) return !item.isUnique;
const manager = ItemStateManager.sceneItems;
const itemManager = isStateless
? manager.stateless
: manager.statefull;
item = itemManager[guid];
if(item) return !item.isUnique;
return true;
}
/**
* Collects an item and updates the appropriate item manager.
* The item is added to the `statefull` or `stateless` item manager
* based on its type.
*
* @param {Object} item - The item object to be collected.
* @param {string} item.guid - The unique identifier of the item.
* @param {boolean} item.isStateless - Indicates if the item is stateless.
* @param {boolean} item.isUnique - Indicates if the item is unique.
*/
static collectItem(
item,
){
const manager = ItemStateManager.sceneItems;
const itemManager = item.isStateless
? manager.stateless
: manager.statefull;
itemManager[item.guid] = item;
}
/**
* Drops an item and updates the appropriate item manager.
* The item is added to the `statefull` or `stateless` item manager
* based on its type.
*
* @param {Object} item - The item object to be collected.
* @param {string} item.guid - The unique identifier of the item.
* @param {boolean} item.isStateless - Indicates if the item is stateless.
* @param {boolean} item.isUnique - Indicates if the item is unique.
*/
static dropItem (
item
){
const manager = ItemStateManager.sceneItems;
const itemManager = item.isStateless
? manager.stateless
: manager.statefull;
delete itemManager[item.guid];
}
/**
* Configures the initial item state for the game, setting up the
* player's items and the global stateless items for the current scene.
*
* @param {Object} app - The application instance (or other context object).
*/
static configure(app){
window.game = window.game || {};
window.game.items = window.game.items || {};
window.game.items.stateless = window.game.items.stateless || {};
const sceneName = State.player.currentScene;
const items = ItemStateManager.sceneItems;
State.player.items = State.player.items || {};
State.player.items[sceneName] = items.statefull;
window.game.items.stateless[sceneName] = items.stateless;
}
}
// Add the ItemStateManager as a dependency to the application
application.addDependency(ItemStateManager);
The application part is the app initializer of my code! And i inject a lot of configurators like the parsed above.
Okay! This solves the item part right ??? Like i have stateless save (session specific) and statefulll (global to all sessions).Well problem solved!!!
BUT NOT! Because in the part of doors. Lets simulate, the player unlock a door using a key so this key is consumed right? Okay, but and if the player after that back to the inspection scene ? Well i did not save the door state, so the door would be locked again in my code, so yes… I NEED TO SAVE DOOR STATE. And that was kind of WTF for me… But is that, i really need… So let’s move on in the code:
/**
* Manages the state and rendering logic for doors in the game.
* This class handles both stateful and stateless doors, determining
* whether a door should be open, closed, or locked based on the player's state.
*/
class DoorStateManager {
/**
* Retrieves the doors for the current scene from the player's state.
* It distinguishes between stateful and stateless doors.
*
* @returns {Object} An object containing `statefull` and `stateless` doors.
*/
static get sceneDoors() {
const sceneName = State.player.currentScene;
State.player.doors = State.player.doors || {};
return {
statefull: State.player.doors[sceneName] || {},
stateless: window.game.doors.stateless[sceneName] || {}
};
}
/**
* Determines whether a given door is locked.
* It checks the player's state or the default scene configuration.
*
* @param {string} guid - The unique identifier of the door.
* @param {boolean} defaultIsLocked - if has no state this should follow the base door config
* @param {boolean} [isStateless=false] - Indicates if the door is stateless.
* @returns {boolean} True if the door is locked, otherwise false.
*/
static isDoorLocked(
guid,
defaultIsLocked,
isStateless = false
) {
const manager = DoorStateManager.sceneDoors;
const doorManager = isStateless
? manager.stateless
: manager.statefull;
const door = doorManager[guid];
return door ? door.isLocked : defaultIsLocked;
}
/**
* Locks or unlocks a door based on the given state.
* It updates the player's door state accordingly.
*
* @param {string} doorGuid - The unique identifier of the door.
* @param {boolean} isStateless - Indicates if the door is stateless.
* @param {boolean} isLocked - True if the door should be locked, otherwise false.
*/
static setDoorLockState(
doorGuid,
isStateless,
isLocked
) {
const manager = DoorStateManager.sceneDoors;
const doorManager = isStateless
? manager.stateless
: manager.statefull;
console.log(manager)
doorManager[doorGuid] = { isLocked };
}
/**
* Configures the initial door state for the game, setting up the
* player's doors and the global stateless doors for the current scene.
*
* @param {Object} app - The application instance (or other context object).
*/
static configure(app) {
window.game = window.game || {};
window.game.doors = window.game.doors || {};
window.game.doors.stateless = window.game.doors.stateless || {};
const sceneName = State.player.currentScene;
const doors = DoorStateManager.sceneDoors;
State.player.doors = State.player.doors || {};
State.player.doors[sceneName] = doors.statefull;
window.game.doors.stateless[sceneName] = doors.stateless;
}
}
// Add the DoorStateManager as a dependency to the application
application.addDependency(DoorStateManager);
And probabbly i’ll have more and more things to save in the game so in my configurator i created folders to state managers
And i already write a lot and did not tell about some playcanvas bugs i need to bypass like that.
Conclusions
Work with Playcanvas is just great for me, what you guys have done is just incredible! And i want to contribute with this community.And for my game i’ll keep moving on doing that untill i have the 47 minigames done and the campus complete functional!.
Please if u read this complete post enter the demos and tell me things to enhance the code, or if u see that and liked some feature, ask me and i’ll give u all the code and support to implement that.