fractural / fracturalvisualnovelengine Goto Github PK
View Code? Open in Web Editor NEWA flexible text-based visual novel engine for Godot written in GDScript. ๐
License: MIT License
A flexible text-based visual novel engine for Godot written in GDScript. ๐
License: MIT License
E 0:00:04.097 get_current_version: Condition "!cc" is true. Returned: __null
<C++ Source> drivers/gles2/shader_gles2.cpp:221 @ get_current_version()
I'm not to sure what the root cause is but it seems like it does no harm. This may be a Godot engine issue
This issue was mentioned in this Q&A question but it was never solved. uniform
variables in the shader seem to be causing the problem however we need uniform
variables in order to adjust the shader at runtime.
A solution to stopping the story would be to force all nodes to check themselves with the StoryDirector before executing. I could also instead set up a pause bool in the ProgramNode that every node checks before executing, although this would permanently terminate execution as there is no way to figure out which node was last executed. But permanently terminating execution might be a good thing -- this feature could be reserved only for throwing errors and would act as a panic button to halt all operations.
Store a variable in .story files called "version" which will be an integer. Developers will be able to set the version
to whatever they want, and the intended workflow is to increment version
every time developers release a modified version of the same story file. Then when loading a save slot, StorySaveManager
will first check to see if the version
from both the save slot and the slot's save file are the same before attempting to load the save file.
A feature like this would allow developers to modify their stories after shipping their game by letting FracturalVNE detect if a save file is loadable and avoids simply crashing the game if the user loads an unloadable save slot.
Building off of refactoring the codebase to use dependency injection (#5), it would be nice to have a method of injecting dependencies for a story tree. A story tree is comprised of many different nodes, therefore it seems like it's impossible to inject dependencies due to each node requiring a different set of dependencies. Currently, I use a service locator that is local to each story tree, therefore there is some semblance of dependency injection as each tree can be injected with a different set of services. For now, I think the local service locator strategy is the best way to handle dependencies in story trees for now, but I'll be on the lookout for better ways.
dependency.gd
should only fetch dependencies when operating outside of the "Scene" tab or in-game. Find some way to detect if the Dependency node is added underneath the "Scene" tab of Godot in order to stop this from happening.
This should just be a matter of creating a C# story runner.
Add an "init" block that will run the code inside of it before the entire story starts running. This allows variables to be initialized and assets to be loaded beforehand in case these operations take a long time to do. You should be able to specify the init order using a number literal placed after "init".
A sample init
block would look like
# Runs second
init 50:
define bob = Character("Bob", "#FFFFFF")
# Runs first
init 39:
define joe = Character("Joe", "#FFFFFF")
Note that this will require a custom implementation of a Block
node since we must pass the variable declaration from the init block into the block above it (the block that "init" is currently located in). We should be creating a custom Block
node since the behavior of using a block solely for the organization and not for scoping may be used in other parts of the StoryScript language. This custom block could be called "UnscopedBlock" or "OrganizationalBlock".
Add support for transitioning an entire scene (Filled with BGScenes, Visuals, etc) to another entirely new scene (Filled with different BGScenes, Visuals, etc).
Many visual novel engines (Naninovel, StoryScript) parse plain text as dialogue. This cuts down on having to escape characters.
Additionally, most engines use a common pattern to denote nondialogue statements ( @
in NaniNovel, []
in StoryScript). The benefit of this is that it makes writing long passages easier (fewer characters typed). A potential downside could be readability, but then again, if you're editing in a normal text editor, it would be more readable since the pattern would be easily identifiable as a nondialogue statement.
Add support for playing audio clips and music
DefaultStoryScript
can be optionally loaded by developers at the start of StoryScript which would then set up defaults (Like the default audio channels, etc).
When typing out some functions their parameters may get too long to keep on a single line.
Add support in StoryScript editor for setting a folder as a project and easily switching between files in the project. When compiling, all the files in the project will be compiled as well.
We are using WAT since we will likely build a C# wrapper around the codebase sometime to enable C# support.
This would likely require the creation of global variables in order to bring data across files. Additionally implementing such a feature could reduce the memory usage of the game since only a single file's AST is loaded into RAM at any time.
filesystem_changed
signal in EditorFileSystem
to detect changes in the file system.EditorFileSystem.scan()
to update the filesystem after saving a new file.The CanvasGroup node is a Godot 4.0 feature that will group the draw calls of its children. This makes it easier to fade in and out a group of sprites.
Currently, much of the plugin code relies on the Service Locator pattern to fetch dependencies, which seems to be an anti-pattern as it makes code harder to test.
Before refactoring, we would have to consider how to perform dependency injection in the first place.
Coming from Unity, I found the best way to achieve composition within prefabs is through a base class script on the main prefab object (the prefab object that you can edit when dragging the prefab into the scene). The base class exposes events that allow for other classes to hook into the behavior of the base class.
For example, consider creating prefabs that may have different variants (such as an enemy prefab having different enemy types). The base class here would be Enemy
and would contain only events and variables that are present in all enemy types. This could include things like a health_controller
since all enemies can be damaged. These variables are then manually assigned by the developer using the inspector.
If a prefab variant ever needed more external dependencies, I could add another script to the main prefab object that requested for the external dependencies, and then I could manually assign them.
However, due to Godot using a 100% node-based system, I cannot add multiple scripts to a single node. One way of exposing dependencies is to replace the base class script of the prefab variants with a subclass script that extends from the base class script. However, I want to avoid using inheritance, as features like scene inheritance stop working due to replacing the base class script with a new script (which ultimately leads to more boilerplate work done to assign the base class script's dependencies for every new prefab variant made).
My plan so far is to use this format for each prefab scene,
PrefabRootNode <- [main_prefab_script.gd]
- Dependencies
- Dependency1 <- [prefab_dependency1.gd]
- Dependency2 <- [prefab_dependency2.gd]
main_prefab_script.gd
would have a reference to the Dependencies
node by incorporating this snippet of code:
export var dependencies_node_path: NodePath
onready var dependencies = get_node(dependencies_node_path)
Then all scripts that need to set the external dependencies of the prefab would have to look into the children of Dependencies
.
This allows for easier extension of the prefab as new external dependency requests can simply be added as another node under Dependencies
.
However, to actually adjust the external dependencies of the prefabs, you have to enable editable children
on the prefab node, which makes it slightly inconvenient to use as you must check editable children
every time you add the prefab to the scene. But adding dependencies from code would not be any different due to having a reference to Dependencies
in the base class script. that can be accessed with base_class.dependencies
.
But the biggest drawback to implementing dependency injection is the loss of quick customizability. For example, every time a developer wants to switch out a GUI they would have to manually reassign the dependencies. I think this works against the quick drag and drop nature of customizing the GUI.
I could try to automate this by having a custom dependency resolver but that would mean for each variant of the GUI that implements or removes certain external dependencies I would have to create a custom dependency resolver for them. This feels really unwieldy as now the developer would have to swap out the appropriate dependency resolver if they happen to add a new GUI that the current dependency resolver cannot resolve.
Then again, dependency injection is explicit in the dependencies needed by the prefab. With Service Location, a developer would only find out about missing dependencies after they hit a runtime error complaining about it.
I think in the end, manually assigning external dependencies for different variants of a prefab is inevitable. At most I can automate the assigning of the base class's dependencies but every other external dependency must be assigned by hand when the prefab is added to a scene. But I think the tradeoff of losing quick-and-drag and gaining explicit external dependency requests is worth it in the end, since developers would know exactly what external dependencies are needed by every prefab variant.
This makes them visible to the editor filesystem, which should speed up development.
editor/editor_node.cpp:947 - Condition "!res.is_valid()" is true. Returned: ERR_CANT_OPEN
Probably something wrong with the import plugin.
Make labels have a unique color. This would likely require custom code for the script editor's TextEdit that may be possible with this PR. We'll have to do some more research though to know for sure.
Seems like the PR is only for 4.0 since it breaks compatibility, so we'll have to wait until then.
The unskippable block will prevent users from skipping any stepped statements within it. A possible implementation would be to first move stepping outside of the story director and into its own service (maybe "StepManager"). Then add the ability for BlockNodes to hold services, and finally make the UnskippableNode attach itself on startup to its Block as a service called "StepManager". This essentially lets the unskippable block intercept calls from statements in a block.
Such a design pattern could help keep code extensible without having to modify existing code. This actually is a form of abstraction similar to how interfaces work since the callers of get_service() only care about getting a service that works -- they do not care about the actual implementation of the service.
Currently, in order to get variables that are accessible from everywhere, you must declare a variable on the outermost scope. Adding global variables will make it easier for developers to not have to worry about scoping if they do not need it.
Add basic visuals and animations related statements. I will likely implement visuals and animations together in a single script called "VisualManager"
with
statement
with
statement
with
statement
SceneManager
to load scenes.
Destructors should remove any step actions the controller has assigned and perform other cleanup if necessary.
Maybe add a type safety check for call object calls?
Or just throw an error in the story tree. However throwing an error would not be able to interrupt the story, since the story is only interruptable by the currently executing node atm (see #18).
Inside of the construct classes instead of chaining expect functions like
var label = parser.expect_token("keyword", "label")
if parser.is_success(label):
var identifier = parser.expect("identifier")
if parser.is_success(identifier):
var expression = parser.expect("expression")
if parser.is_success(expression):
...
else:
return expression
else:
return identifier
else:
return label
they can be replaced with
var label = parser.expect_token("keyword", "label")
if not parser.is_success(label):
return label
var identifier = parser.expect("identifier")
if not parser.is_success(identifier):
return identifier
var expression = parser.expect("expression")
if not parser.is_success(expression):
return expression
...
Notice how just by switching the is_success()
to a not is_success()
check we can break apart the "Pyramid of Doom"
Add a way of saving and loading a player's progress in a certain story.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.