Coder Social home page Coder Social logo

fractural / fracturalvisualnovelengine Goto Github PK

View Code? Open in Web Editor NEW
7.0 1.0 0.0 70.31 MB

A flexible text-based visual novel engine for Godot written in GDScript. 📖

License: MIT License

GDScript 89.23% GLSL 0.09% C# 10.55% Makefile 0.13%
csharp plugin game-development godot gdscript hacktoberfest2021 hacktoberfest

fracturalvisualnovelengine's Introduction

Fractural Visual Novel Engine 📖

Deploy Unit Tests

A flexible text-based visual novel engine for Godot written in GDScript. Partly inspired by Ren'Py.

The goal of this engine is to be an extensible visual novel system capable of integrating itself into any project. With extensibility in mind, many features of the engine can be added onto with ease.

Features

  • StoryScript, a custom language made to write visual novels in. It has syntax that is similar to Ren'Py's language.
  • Saving and loading
  • Skipping and auto stepping
  • Pausing
  • Actors, which are movable, animatable, and transitionable onscreen objects.
    • Visuals
    • Background scenes
    • Text printers
  • Transitions
  • Animations
  • Movement

TODO:

Visit our development project board to see our current goals.

fracturalvisualnovelengine's People

Contributors

atlinx avatar

Stargazers

 avatar  avatar mohamad hani janaty avatar  avatar Mauro Risonho de Paula Assumpção avatar  avatar  avatar

Watchers

James Cloos avatar

fracturalvisualnovelengine's Issues

Add a project mode to the StoryScript editor

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.

Add basic in-game story features

  • Stepping
  • Auto-stepping
  • Skipping
  • History
  • Saving and loading save slots (related to #4)
  • Informing users whenever an error occurs during a story's execution

Add method to terminate execution of story tree immediately

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.

Refactor entire codebase to use dependency injection

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.

Add support for splitting a story across multiple files

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.

Create versioning system for .story files

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.

Add audio

Add support for playing audio clips and music

  • AudioManager
  • Play statement
    • Skipping (No need to serialize state)
  • Music statement (Loop the music by default)
    • State serialization (No need to skip)

Add full scene transitions

Add support for transitioning an entire scene (Filled with BGScenes, Visuals, etc) to another entirely new scene (Filled with different BGScenes, Visuals, etc).

Add story text parser

  • Determine rules of the VNE's language
  • Lexes the custom language
  • Parse custom language and generate an abstract syntax tree
  • Make the AST call actual GDScript nodes
  • Add serialization

Add story saving and loading

Add a way of saving and loading a player's progress in a certain story.

  • Serialize the save state of the current story's AST
  • Save slots to store save states
  • Save thumbnails for each save state
  • Save and load each save state into its own file

.story & .storyscript file error

editor/editor_node.cpp:947 - Condition "!res.is_valid()" is true. Returned: ERR_CANT_OPEN

Probably something wrong with the import plugin.

Refactor the "Pyramid of Doom" out of all the AST node construct classes.

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 DefaultStoryScript resource

DefaultStoryScript can be optionally loaded by developers at the start of StoryScript which would then set up defaults (Like the default audio channels, etc).

Make text edit syntax highlighting more readable.

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.

Add unit testing using WAT

We are using WAT since we will likely build a C# wrapper around the codebase sometime to enable C# support.

Find way to inject dependencies for story trees

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.

Add C# support

This should just be a matter of creating a C# story runner.

Make an unskippable block

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.

Add basic visuals and animations

Add basic visuals and animations related statements. I will likely implement visuals and animations together in a single script called "VisualManager"

  • VisualManager - Manages the showing, hiding, and animating of visuals, including scenes.
  • Show statement - Makes a Visual visible. Can optionally animate this using the with statement
    • Skipping
    • State serialization
  • Hide statement - Makes a Visual invisible. Can optionally animate this using the with statement
    • Skipping
    • State serialization
  • Move statement - Moves a Visual to a position and can optionally interpolate the movement using the with statement
    • Skipping
    • State serialization
  • Animate statement - Animates a Visual relative to its current position.
    • Skipping
      • (Use AnimationPlayer.seek() to play the end of the animation)
    • State serialization
  • Scene statement - Loads a scene. If the argument is an actual Godot scene, the game will attempt to load that. Otherwise, if the argument is a file path, the game will load the image in that file path as the background. This uses SceneManager to load scenes.
    • State serialization
    • Skipping
  • With statement - Used as a keyword by other statements to add extra behaviour to them
  • StorySceneManager - Manages the loading of scenes.

Add "init" block to the StoryScript language

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".

Shader related bug when using shader-based transitions

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.

Maybe overhaul storyscript language to be more writer friendly

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 global variables

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.