Added more to the guide

This commit is contained in:
thepaperpilot 2022-03-05 22:34:42 -06:00
parent e997c0d199
commit ce3adae873
9 changed files with 135 additions and 6 deletions

View file

@ -3,7 +3,7 @@ module.exports = {
title: 'Profectus',
description: 'A game engine that grows with you.',
head: [
['link', { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto+Mono' }],
['link', { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Roboto+Mono:ital@0;1' }],
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }],
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }],
['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }],
@ -41,14 +41,25 @@ module.exports = {
children: [
{ text: "Project Info", link: "/guide/project-info" },
{ text: "Project Entry", link: "/guide/project-entry" },
{ text: "Layers", link: "/guide/layers" },
{ text: "Changelog", link: "/guide/changelog" },
{ text: "Themes", link: "/guide/themes" }
{ text: "Themes", link: "/guide/themes" },
{ text: "Utilities", link: "/guide/utils" }
]
},
{
text: "Creating Features",
children: []
text: "Important Concepts",
children: [
{ text: "Layers", link: "/guide/layers" },
{ text: "Features", link: "/guide/features" },
{ text: "Coercable Components", link: "/guide/coercable" },
{ text: "Reactivity", link: "/guide/reactivity" }
]
},
{
text: "Advanced Concepts",
children: [
{ text: "Creating Features", link: "/guide/creating-features" }
]
}
],
}

35
guide/coercable.md Normal file
View file

@ -0,0 +1,35 @@
# Coercable Components
Most times a feature has some sort of dynamic display, it'll allow you to pass a "Coercable Component", or rather, something that can be coerced into a Vue component. This page goes over the different types of values you can use
## Template Strings
If you provide a string, it will be wrapped in a component using it as the template. This is the simplest method, although not suitable for complex displays, and realistically cannot use Vue components as none are registered globally (by default). Recommended for static or simple dynamic displays, such as displays on features.
Template strings need to be wrapped in some HTML element. By default, they'll be wrapped in a `<span>` element, although certain features may wrap things in div or header elements instead, as appropriate.
## Render Functions (JSX)
You can provide a render function and it will be wrapped in a component as well. The intended use for this is to write JSX inside a function, which will get automatically converted into a render function. You can read more about that process on the Vue docs on [Render Functions & JSX](https://v2.vuejs.org/v2/guide/render-function.html). Note that JSX must be returned in a function - it does not work "standalone". The CoercableComponent type will enforce this for you.
JSX can use imported components, making this suited for writing the display properties on things like Tabs or Layers. There are also built-in functions to `render` features (either as their own or in a layout via `renderRow` and `renderCol`), so you don't need to import the Vue component for every feature you plan on using.
Typically a feature will accept a `Computable<CoercableComponent>`, which means functions would (normally) be wrapped in a computed (see [Computable](./reactivity#computable) for more details). This would break render functions, so when passing a render function as a CoercableComponent it must be specially marked that it shouldn't be cached. You can use the built-in `jsx` function to mark a function for you.
#### Example
```ts
{
display: jsx(() => (
<>
<MainDisplay resource={points} color={color} />
{render(resetButton)}
{renderRow(upgrade1, upgrade2, upgrade3)}
</>
)),
}
```
## Components
This one might be the most obvious, but you can also just give it a Vue component to display outright. Keep in mind it will not be passed any props, so it should not depend on any. You can read more about creating Vue components on [Components Basics](https://vuejs.org/guide/essentials/component-basics.html).

View file

@ -0,0 +1,5 @@
# Creating Features
\# TODO
Because typescript does not emit JS, if a value is supposed to be a function it is impossible to determine if a given function is the intended value or a function that returns the actual value. For this reason it is not recommended for any feature types to include properties that are `Computable<Function>`s, and all functions _will_ be wrapped in `computed`. The notable exception to this is [JSX](./coercable#http://localhost:3000/guide/coercable.html#render-functions-jsx), which uses a utility function to mark that a function should not be wrapped.

35
guide/features.md Normal file
View file

@ -0,0 +1,35 @@
# Features
A [layer](./layers) is made up of features. There are many types of features included in Profectus, and more can be created once you become familiar with the engine.
To create a feature, the feature type will have one or more functions to help you. They'll typically look something like this:
```ts
const addGainUpgrade = createUpgrade(() => ({
display: {
title: "Generator of Genericness",
description: "Gain 1 point every second"
},
cost: 1,
resource: points
}));
```
The result will be a [lazy proxy](./layers#lazy-proxies) of the feature being created. The feature can then be used throughout the rest of the layer. The main thing to keep in mind when creating features is that they should typically be included in the layer object that gets returned. If a feature has any [persistent refs](./reactivity#persistent) they must be included or else they will not have their values saved and loaded correctly.
While the structure of layers is intentionally left up to the creator, it is recommended to avoid storing them in arrays. If you ever remove an upgrade from an array, or add an upgrade in between others, then it will interfere with the save data of existing users. You can manually fix these issues in [fixOldSave](./project-entry.md#fixoldsave), but it's recommended to avoid the error entirely.
Since usually you want to access a specific feature, storing them in an object is much more reliable and makes the code easier to read. For things like checking number of upgrades bought, for example, you can still use `Object.values` to get an array of the objects instead.
#### Example
```ts
const upgrades = { addGainUpgrade, gainMultUpgrade, upgMultUpgrade };
const numUpgrades = computed(() => Object.values(upgrades).length);
```
## Tree Shaking
Since Profectus takes advantage of [tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking), and type of feature that is not used will not be included in the output of the project. That means users have less code to download, a slight performance boost, and you don't need to worry about feature type-specific settings appearing (such as whether to show maxed challenges).
It should be noted that a couple features depend on each other, such as Buyables depending on Clickables. That means you may see features included in the output despite not directly using them. Some features, such as Links and Tooltips, are used by the engine itself and will always be included in the output.

View file

@ -7,4 +7,15 @@ Profectus is a web-based game engine. You can write your content using many buil
The purpose of creating profectus was to create an easy to use engine that does not create a ceiling for a programmer's personal growth. This engine will grow in complexity with you, empowering you to create increasingly complex designs and mechanics.
## Design Philosophies
#### Design Philosophies
While absolute purity is impossible, design decisions have been and will continue to be made using these principles in mind.
- An engine should be intuitive, and code readable without context
- An engine should not constrain a creator
- An engine should be consistent
- An engine should be extensible, and those extensions sharable
- An engine should be efficient
- An engine should report issues early and thoroughly
- Passing by reference is one honking great idea -- [let's do more of that](https://www.python.org/dev/peps/pep-0020/)!
- [Refs are better than reactive objects](https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c)

View file

@ -5,3 +5,7 @@ Profectus content is organized into units called "Layers". When display content
Each layer is ultimately a collection of different features, and a display function. While there are a couple reserved properties for layers, most of its structure is fully up to the creator.
Layers can be dynamically added or removed at any time, which also allows for effectively disabling or enabling content based on arbitrary conditions. Just make sure [getInitialLayers](./project-entry.md#getinitiallayers) can process the player save data object and determine which layers should be currently active.
## Lazy Proxies
Layers (and features) are not actually created immediately. Instead, their options are gotten through a function which is then run the first time something _inside_ the layer is accessed. This is a concept called lazy evaluation, which is also used for things like `computed`, and allows for features to reference each other without worrying about cyclical dependencies.

19
guide/reactivity.md Normal file
View file

@ -0,0 +1,19 @@
# Reactivity
Profectus takes large advantage of Vue's reactivity system. It's recommended to read up on how [refs](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive-variables-with-ref) and [computed](https://vuejs.org/guide/essentials/computed.html) refs work. Ultimately this means that sometimes you'll need to type `.value` to get the actual value of something, but also you are able to pass things around by reference instead of by value. Indeed, it is recommended to only unwrap the actual value when you actually need it. `.value` is guaranteed to be correct and up to date only on the exact moment it is accessed.
With a proper IDE, such as [Visual Studio Code](./setup#visual-studio-code-setup), you should be able to see whether or not something is a ref or not from type hints. If in doubt, you can always wrap the property in an `unref` call.
Vue's reactivity is probably the "quirkiest" part of Profectus, and not even the documentation makes all of those quirks clear. It is recommend to read [this thread](https://github.com/vuejs/docs/issues/849) of common misconceptions around Vue reactivity.
## Persistent
Some refs are "persistent" refs. Most notably, any time you use the `persistent()` function it will be a persistent ref. You access these the same way you would any other ref, but keep in mind its value will be saved and loaded automatically.
All persistent refs _must_ be included in a layer object in order for persistence to work. Since many [features](./features) will create persistent refs internally its recommended to include all of them just to be safe.
## Computable
Most properties on features will accept `Computable` values. Computable values can either be a raw value, a ref to the value, or a function that returns the value. In the lattermost case it will be wrapped in `computed`, turning it into a ref. The feature type will handle it being a ref or a raw value by using `unref` when accessing those values. With type hints, your IDE should correctly identify these values as refs or raw values so you can treat them as the types they actually are.
Because functions are automatically wrapped in `computed` for many properties, it might be expected to happen to custom properties you add to a feature that isn't defined by the feature type. These functions will _not_ be wrapped, and if you want it cached you should wrap it in a `computed` yourself. This does, however, allow you to include custom methods on a feature without worry.

View file

@ -33,6 +33,10 @@ Before github knows to actually host the generated site, you'll have to enable g
Once the action completes, your project should be available at `https://<YOUR_GITHUB_USERNAME>.github.io/<YOUR_REPO_NAME>/`. For example, the TMT Demo project hosted at https://github.com/profectus-engine/TMT-Demo is available at https://profectus-engine.github.io/TMT-Demo/.
### Visual Studio Code Setup
If you don't already have a preferred IDE, Profectus is currently developed in [Visual Studio Code](https://code.visualstudio.com) and is known to work well with that IDE in particular. It's recommend to use [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471) for proper type analysis, and consider turning off `.value` autocomplete by running the `Preferences: Open Settings` command and setting `volar.autoCompleteRefs` to `false`.
## Replit
As an alternative to local development, you may use [replit](https://replit.com), which will automatically set up your development environment for you and also host your project itself.

5
guide/utils.md Normal file
View file

@ -0,0 +1,5 @@
# Utilities
There are often concepts that aren't inherent to a single feature, but rather work with joining different features together. For example, a reset clickable that activates a conversion and resets a tree, which happens to be a common use case but isn't inherent to clickables, conversions, or trees.
These are perfect situations for utilities, and so to encourage creators to learn to identify and take advantage of these situations, a file called `src/data/common.tsx` has been created to demo some of the more common utility functions a project might use. Adding new utilities to this file is encouraged, as is creating utils in general. It also works as a good stepping stone to creating your own features.