From 5926abee0155a9d309c00d042cb124e9aa245abe Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Tue, 18 Apr 2023 22:58:07 -0500 Subject: [PATCH] Add first layer page --- docs/.vitepress/config.js | 1 + docs/guide/getting-started/first-layer.md | 122 +++++++++++++++++++ docs/guide/important-concepts/persistence.md | 8 +- 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 docs/guide/getting-started/first-layer.md diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index 646e8d56..e9adb4c2 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -42,6 +42,7 @@ module.exports = { { text: "Introduction", link: "/guide/" }, { text: "Setting Up", link: "/guide/getting-started/setup" }, { text: "Updating Profectus", link: "/guide/getting-started/updating" }, + { text: "Your First Layer", link: "/guide/getting-started/first-layer" }, { text: "Example Projects", link: "/guide/getting-started/examples" }, { text: "Profectus Changelog", link: "https://github.com/profectus-engine/Profectus/blob/main/CHANGELOG.md" } ] diff --git a/docs/guide/getting-started/first-layer.md b/docs/guide/getting-started/first-layer.md new file mode 100644 index 00000000..1b996d24 --- /dev/null +++ b/docs/guide/getting-started/first-layer.md @@ -0,0 +1,122 @@ +# Your First Layer + +This page is a guide on where to start with creating your very first layer. It is intended for developers who are first learning to use the engine. + +## Creating the layer + +::: info +The template comes with a layer in `projEntry.tsx` and another in `prestige.tsx`. You can use those as a base instead of creating a new one from scratch. +::: + +To add a new layer, first create it via the [createLayer](../../api/modules/game/layers#createlayer) function. You typically create a single layer per file, the first being in `projEntry`. After it is created, you'll need to add it to the returned array in [getInitialLayers](../../api/modules/data/projEntry#getinitiallayers). + +The `createLayer` function will need a unique ID for your layer and a function that constructs the layer. At a minimum, it needs a `display` property so Profectus knows what to render. It will look something like this: + +```ts +const id = "p"; +const layer = createLayer(id, function (this: BaseLayer) { + return { + display: jsx(() => <>My layer) + }; +}); +``` + +## Adding a resource + +Game elements in Profectus are called "features". You'll create them inside the layer's constructor function and return them in the object at the end. Many features can also be rendered in the layer's display. + +In the file tree, there's a folder called `src/features`, which contains all the different features that are included in Profectus (and any others you've created or downloaded from others!). You can browse the folder to see all the features and learn what each one does. Some features also have dedicated guide pages on how to use them. + +Let's add one of these features to our layer: a Resource. As with most features, there's a [createResource](../../api/modules/features/resource#createresource) constructor for creating this feature. These constructors typically take a function that returns an object with all the options for that feature. However, resources are simple features, so they just take the options as parameters. Creating a resource will look like this: + +```ts +const points = createResource(0, "prestige points"); +``` + +In your IDE you'll be able to see the documentation for each parameter - in this case, the first one is the default value for this resource, and the second is the display name for the resource. Now we can make sure to add the points to our layer's object and display it using the [MainDisplay](../../api/modules/features/resource#maindisplay-component) Vue component. All in all, our layer should look like this now: + +```ts +const id = "p"; +const layer = createLayer(id, function (this: BaseLayer) { + const points = createResource(0, "prestige points"); + + return { + points, + display: jsx(() => ( + <> + + + )) + }; +}); +``` + +## Update loop + +Some things happen every tick, such as passive resource generation. You can hook into the update loop using an event bus. There's a global one and one for each layer. For example, within the layer function, you can add this code to our example to increase our points at a rate of 1 per second: + +```ts +this.on("update", diff => { + points.value = Decimal.add(points.value, diff); +}); +``` + +Note that within the `createLayer`'s function, `this` refers to the layer you're actively creating, and the `diff` parameter represents the seconds that have passed since the last update - which will typically be around 0.05 unless throttling is disabled in the settings. If we wanted to generate an amount other than 1/s, we could multiply diff by the per-second rate. + +## Adding an upgrade + +Let's add a more complex feature to the layer now - an upgrade. Upgrades represent one-time purchases. This time the [createUpgrade](../../api/modules/features/upgrade#createupgrade) function requires an options function. We can create a lambda that returns the options object. We'll need to give it a cost requirement and display. Afterwards, it should look like this: + +```ts +const myUpgrade = createUpgrade(() => ({ + requirements: createCostRequirement(() => ({ + resource: noPersist(points), + cost: 10 + })), + display: { + description: "Double points generation" + } +})); +``` + +We'll add this upgrade to our returned object and our display. Upgrades are a renderable feature, which means we can use the [render](../../api/modules/util/vue#render) function to display them. The returned layer object will now look like this: + +```ts +return { + points, + myUpgrade, + display: jsx(() => ( + <> + + {render(myUpgrade)} + + )) +} +``` + +The last thing to do to implement our upgrade is to give it an effect! If `myUpgrade.bought.value` is true, then the points generation should double. We'll represent that by creating a points gain modifier, which will make it easy to handle many things affecting points gain. The modifier should look like this: + +```ts +const myModifier = createSequentialModifier(() => [ + createMultiplicativeModifier(() => ({ + multiplier: 2, + enabled: myUpgrade.bought + })) +]); +``` + +Now to make the points gain use the modifier, we'll update the line that adds points to this: + +```ts +points.value = Decimal.add(points.value, Decimal.times(myModifier.apply(1), diff)); +``` + +You now have a functioning upgrade, and are prepared to create many more upgrades and other features! + +## Next Steps + +Since Profectus is an engine, what to do with it is a fairly open-ended question. If you're not quite ready to go out on your own, there are more guides to help prepare you. A good next step would be the [Prestige Mechanic](../recipes/prestige) recipe page. + +The "Important Concepts" section of the guide goes further into various parts of the engine that are prudent to understand. If any of those look interesting or like something you need help with, they can be a great help. + +Beyond that, the best way to learn is to just continue using the engine and exploring its various features. Have fun! diff --git a/docs/guide/important-concepts/persistence.md b/docs/guide/important-concepts/persistence.md index 06992851..5dbb9b70 100644 --- a/docs/guide/important-concepts/persistence.md +++ b/docs/guide/important-concepts/persistence.md @@ -1,11 +1,13 @@ # Persistence -Persistence refers to data that is saved, so that it _persists_ when the user closes the tab and opens it again in the future. +Persistence refers to data that is saved so that it _persists_ when the user closes the tab and opens it again in the future. In Profectus, this is handled by creating "persistent refs", which act like [refs](./reactivity.md) but whose value is stored in an object that gets saved to localStorage. Other than that you can treat them like any other ref - when adding the layer, any persistent refs will automatically have their values updated to the ones saved in localStorage. If there isn't a saved value, it'll use the default value passed to the persistent ref constructor. -Many features in Profectus, such as upgrades, milestones, and challenges, internally have persistent refs to save things like whether the upgrade has been purchased, the milestone achieved, or the challenge completed. Creators can also create their own custom persistent refs to store any arbitrary (but serializable) data they need - that means Numbers (including big numbers), strings, booleans, or objects containing only serializable values. Another notable function is the resource constructor. If you pass a default value into its contructor, it will automatically create a persistent ref for that resource. If you pass in a ref, it will NOT make the ref persistent. +Many features in Profectus, such as upgrades, milestones, and challenges, internally have persistent refs to save things like whether the upgrade has been purchased, the milestone achieved, or the challenge completed. Creators can also create their own custom persistent refs to store any arbitrary (but serializable) data they need - that means Numbers (including big numbers), strings, booleans, or objects containing only serializable values. Another notable function is the resource constructor. If you pass a default value into its constructor, it will automatically create a persistent ref for that resource. If you pass in a ref, it will NOT make the ref persistent. -It's important for saving and loading these properties for these refs to be in a well known location. This is implemented based on the location of the persistent ref within a layer. That means its important that **all persistent refs are located within the object returned by the createLayer options function**. If a persistent ref is not within that object, it will NOT be saved and loaded - regardless of whether its a persistent ref within a feature, one you manually created, or otherwise. +It's important for saving and loading these properties for these refs to be in a well-known location. This is implemented based on the location of the persistent ref within a layer. That means it's important that **all persistent refs are located within the object returned by the createLayer options function**. If a persistent ref is not within that object, it will NOT be saved and loaded - regardless of whether it's a persistent ref within a feature, one you manually created, or otherwise. Additionally, this structure should typically remain consistent between project versions. If a value is in a new location, it will not load the value from localStorage correctly. This is exacerbated if two values swap places, such as when an array is re-ordered. In the event a creator changes this structure anyways, the [fixOldSave](../creating-your-project/project-entry.md#fixoldsave) function can be used to migrate the old player save data to the new structure expected by the current version of the project. + +As of Profectus 0.6, save data will now report warnings whenever there is redundancy - two locations for the same persistent data, which creates larger saves that can cause issues when loading after updates. To fix redundancies, wrap all but one location for the data in [noPersist](../../api/modules/game/persistence#nopersist).