Action Adventure Kit
by Softleitner





read manual online at
https://adventure.softleitner.com/manual
for support please contact
softleitner@gmail.com

Overview


permalink: /manual title: “Overview” sidebar: title: “Manual” nav: manual —

Welcome

This is the manual for Action Adventure Kit by SoftLeitner. Action Adventure Kit is quite the mouthful so I will be referring to it using the shorthand AAK.

Action Adventures are a very wide, loose genre with all kinds of different games and sub genres. There are some things that many of them have in common though. Lots of them have characters that perform actions. These might have items that change hands and attributes that define their capabilities.

AAK was made to provide a solid foundation for these common ideas within the genre so you can focus on what makes your game special.

Since AAK goes for a broad base of functionality rather than something more specialized it generally tries to do anything in the most default, unity-built-in way possible. For example the movement in the souls demo uses the default unity character controller and the intro is done using timeline. To accommodate for the fact that more special systems may be required for movement, inventory, actions, … AAK was made from the ground up with expandability in mind.

Setup

AAK is separated into multiple projects. If you are using AAK for the first time I recommend importing everything, including settings, into a new project.

If the project you are importing into uses the old input system the editor will prompt you to restart and you will have to start the import again after that.

AdventureCore

The Core Framework of AAK, always import this one.

AdventureCore.Tests

Contains test scenes for various features, can be useful to try out those features in isolation. Import while exploring AAK but should not be included in production.

AdventureHero

Contains the hero demo, import if you want to start by adapting this demo.

Start up Scenes/HeroTitle to start the game from the title screen just like the demo does.

IMPORTANT, before playing the ‘SaveSlot’ app variable has to be added in visual scripting. Select the Logic Object in the Hierarchy and click ‘Edit Graph’ which opens the visual scripting graph. In the ‘App’ tab on the bottom left add a variable called ‘SaveSlot’ of type Integer with a default value of -1. This is needed to carry over the save slot between scenes.

Scenes/Debugging/HeroDebuggingGeneral is a useful scene for testing, some more specialized scenes can be found in the same folder

AdventureManual

Contains the getting started project. Recommended to learn about the various systems of AAK in a minimal environment before jumping into the more complex souls demo.

Also contains some Visual Scripting examples. Be sure to read the visual scripting manual page before jumping into any scenes using Visual Scripting as there is some additional setup required!

AdventureSouls

Contains the soulslike demo, import if you want to start by adapting this demo.

Start up Scenes/Title/SoulsTitle to start the game from the title screen just like the demo does.

IMPORTANT whenever starting the souls demo from the editor you need to click into the window to lock the cursor before some input is accepted. This is done to avoid performing actions on accident from outside.

To jump directly into the game open Scenes/Dungeon/SoulsDungeon for the level itself, add Scenes/Dungeon/SoulsDungeonTemp for the temporary parts like enemies and crates.

Scenes/Debugging/SoulsDebuggingGeneral is a useful scene for testing out all the actions and Scenes/Debugging/Enemies/SoulsDebuggingEnemies can be used to debug combat.

Scenes/Debugging/Interaction/SoulsDebuggingInteraction can be useful to synchronize the character with some object they are interacting with, this is done using timelines.

The models for the demos were made using blender and then exported to fbx for unity. You can find the original blend files and the used export settings in AdventureModels.zip.

Dependencies

AdventureCore

  • Input System allows binding character actions directly to inputs
  • Timeline used to provide a character action that waits for a timeline to finish
  • Cinemachine used in a helper that performs camera locking using two virtual cameras
  • Visual Scripting for the custom visual scripting units

AdventureManual

AdventureHero

AdventureSouls

Manual

This manual is meant to explain the concepts and ideas of AAK rather then any specific detailed API. For more detailed explanations of every class in the core framework and most of the demo please consult the code itself. I try to give a detailed explanation for the purpose of the class in the xml-doc of the class itself and explain every field of the behaviors in the tooltip.

The manual pages for the core systems of AAK are always split into a Core part that explains the idea behind the system and a Souls part that covers how the system was used in that demo.

The Souls section of the manual contains some additional information about how the AdventureSouls demo is set up and how it may be extended.

Integrations

All GitHub repositories related to my unity assets can be found in the Softleitner Extras list. I generally try to keep the main ones updated but some of the minor ones may be out of date.

Roadmap

The next update will continue to polish and build out the Hero Demo and its Documentation.

Feedback

The quickest channels to reach me are mail and discord. Please feel free to reach out with any problems and questions. Feedback regarding the general direction of AAK and particular future features are also always welcome. Though I might not immediately be able to incorporate your requests I very much take them into consideration when planning out future updates.

If you can spare the time please consider leaving a review in the asset store.

Release Notes


permalink: /manual/releases title: “Release Notes” sidebar: title: “Manual” nav: manual —

1.5.1

small hotfix for some smaller issues in the new demo

ADDED

  • SphereDamageSender that sends damages using a SphereOverlap
    used for bombs so damage only gets sent at the moment of impact

FIXED

  • exception when using bomb from menu outside of debug scenes
  • jump attacks only sent damage in the debug scenes
  • locking on without a target would sometimes unlock itself
  • duplicate persistence keys for chests in temple
  • sling projectile now uses dynamic collision detection to avoid passing
  • minor adjustments to level geometries

1.5.0

official release of the ‘Hero’ demo

ADDED

  • HeroDemo
    • Beach, Inland, Canyon and Temple levels
    • Wall Climbing
    • Block Pushing
    • Slingshot Gear Item
    • Skeleton Enemy
    • Various Passage(Door) Prefabs
      Scene Exit, Locked, Barred, Arena, …
    • Sword and Shield Items
    • Destructible Vines, Walls, Bushes
    • Random Loot Selection for Pots
    • Readable Signs
  • Visual Scripting Units
    • PlayAnimation
    • Ragdoll
    • VisualScriptingAction Bool/Int/Vector Received
    • Activate/Deactivate Damage
    • DamagedTriggerTotal/DamagedCharacterTotal
    • EffectAdded/Removed
    • Set/Reset/Override ManagerState
    • Manager State Changed/Entered/…
    • HasItem
    • SetDialogResult
    • AlignToPosition
    • ResetInput/Momentum
  • TimedAction
    simple action that ends after a set duration
  • GenericDamage damage type
    does nothing on its own, handling in Receivers

IMPROVED

  • TriggerArea handles overlapping colliders better
  • TriggerDamageSender setting SendOnce
    only sending damage the first time a receiver is added per activation
    can be useful to avoid double damage from things like sword slashes
  • MotionAction setting OnlyGrounded
    disables the action when the character is not grounded
  • Gravity for ProjectileDamageSender
  • ResetMomentum in MovementBase
  • ResetMain in LockableCameraBase
    resets the player camera to the main camera
  • LockableCameraBase can also lock the cursor in Build

CHANGED

  • CharacterControllerMovement now uses Physics.SyncTransforms() when teleporting
    disabling the collider prevented some trigger messages from occuring

1.4.1

second update focused on the new ‘Hero’ demo

ADDED

  • HeroDemo
    • Title Screen, Scene Transition and Death Action
    • First Person View
    • Gear and Equipment Menus, Health and Damage in HUD
    • Stunning Nut and exploding Bomb Equipment Items
    • Pouches that expand carrying capacity for Money, Nuts, Bombs
    • Carrying Blocks, Pots and Bombs
    • Heart, HeartContainer, HeartPiece, Coin, Nut and Bomb Pickups
    • …and much more, check out the HeroDebuggingGeneral and HeroTitle scenes!
  • InputBindingText
    displays current input binding in UI texts(Legacy, TMPro, UIToolkit)
    useful for texts like ‘Press {Confirm} to continue!’
  • StateManager
    simple state manager that acts through unity events
    used in Hero for game and HUD state
  • Simple UI Dialog Windows(OkCancel, YesNo, … decisions)
    usable from code and new custom visual scripting nodes
    used in hero demo for name input and delete save game check
  • Marching in MovementBase
    makes a character walk to or toward some target
    timeline track included so marching can be used in cutscenes used in Hero when entering and exiting scenes

IMPROVED

  • More Custom Visual Scripting Units
    Hero uses visual scripting for a large part of its logic(see AdventureHero/Graphs)
  • More Custom Editors
    many of the core components now have custom editors for improved debugging
    trigger areas show their current items, actions can be started, items added, …
  • Save Data Window for PlayerPrefSaver
    see Window/ActionAdventure/SaveData or ‘Edit Save Data’ on the PersistenceContainer
  • TMPro Support
    Hero uses TMPro, Souls is still UIToolkit
  • Actions can be started by name if they are children of the main actor
  • CharacterControllerMovement can now apply weight(WeightPower field in Inspector)

CHANGED

  • visual scripting now uses ICharacterAssociator instead of directly referring to characters
    ICharacterAssociator implemented by components that are directly owned by characters
    this allows units on actions to easily use the executing character
  • damage direction is stored in DamageEvent
    damage direction should be assigned to DamageEvent.Vector during IDamageSender.OnDamage
    TriggerDamageSender now has additional direction options(Up, SenderToReceiver, …)
  • force of damage and ragdolls is applied as ForceMode.Impulse
  • neutral layer tweaked to be more consistent with Player and Enemy
  • minimum recommended unity version increased to 2021.3.29

1.4.0

first in a series of 2-3 updates that focus on the second demo titled ‘Hero’
it is experimental for now and subject to major changes in the future

ADDED

  • AdventureHero Demo [Experimental]
    movement, attacking, lock on, item pickup and various other systems are already working you can check out the current state of the demo in the HeroDebuggingGeneral scene
  • AdventureCore.Tests Project
    contains test scenes for various features(lock on, timeline textbox)
  • Timeline TextBox
    can be used to display text using a timeline and pause it until confirmation
    examples can be found in AdventureHero and the AdventureCore.Tests Project
  • Timeline Instructions applies Character Instructions from a timeline track used in AdventureHero to suspend character movement while showing pickups
  • Additional Visual Scripting Nodes for Lock On, Acting, VectorDirection, …

IMPROVED

  • Additional sounds in Souls Demo for Bow, Pebble, ….
  • Particles Effect for healing in Souls Demo
  • added PropelCharacterLocal which is useful for motions like jumping
  • horizontal velocity in CharacterControllerMovement improved
  • LockOnManager now also manages and exposes the candidate lock on point
  • LockOnManager now checks the entire ray between camera and potential points
    this means models with lock on points can’t be on the default layer
    therefore target dummies in souls are now on the new neutral layer

FIXED

  • Sprinting not working in Souls demo until button released again
  • Bonfires did not suspend movement correctly
  • Projectiles not being destroyed in Souls Demo
  • E Key not working in Souls Demo Title

1.3.1

ADDED

  • Simple Third Person Prefab (AdventureManual/Prefabs/AAKThirdPerson)
    • Basic CameraController for Third Person
  • Resource System Manual Scene (AdventureManual/Systems/Resource/ManualResource)
    • Resource Bars for uGUI

1.3.0

ADDED

  • Visual Scripting Support
    • custom visual scripting units for all core systems
    • visual scripting version of the getting started scene
    • enemy behavior examples
      • simple examples based on getting started
      • advanced enemies for souls demo(archer, attacker)
  • Loot in Souls Demo
    • random state is persisted
    • define items and chances
    • loot drop is persisted until pickup
  • Item Manual Example Scene
    • equipment that is visible on the character
    • usables that add health and vitality

1.2.0

This update continues building out the souls demo while polishing up the core framework. It also adds a getting started tutorial project that should provide a smoother experience at the beginning. There is also a new integration available on GitHub for the Kinematic Character Controller asset!

ADDED

  • Step by step getting started tutorial
  • Pebble usable item in souls which can be thrown
  • Two handing weapons in souls
    • stances for two handed melee and bows
    • assignable actions for all 4 shoulder buttons
  • Bow and Arrow in souls
    • damage is summed between bow and arrow
    • easily customizable arrows

FIXED

  • Camera damping in souls demo
  • SoulsBossArea not properly resetting on player death
  • MotionAction without cost now works on characters without ResourcePool
  • Namespace of SoulsHideHUDInstruction which unfortunately breaks references so it has to be reassigned or fixed in text

IMPROVED

  • MovementBasePersisted does not error if no Persister is set
  • LockableCameraFreeLook can move a target group member instead of adding the lock point
  • LockOnManager can directly parent a visual to the locked point

Starting from this version I will no longer upload a dedicated version for the 2021.2 tech stream. I recommend using the latest 2021.3 LTS version.

1.1.1

Various smaller changes made for the AdventureSouls Scene Connector integration example. The example is called ConnectorSouls and can be found on GitHub!

IMPROVED

  • TimelineAction and SoulsBonfireAction auto bind CinemachineBrain to PlayableDirector
  • AnimationToggler can be moved instantly using SetA and SetB
  • SoulsLadderAction EndingTop and EndingBottom events
  • SoulsLoading can be configured to load additional scenes
  • SoulsPlayerCharacter multi scene support
    • saves current scene as checkpoint
    • goes to loading screen on death in a different scene
    • recovery is instantiated under SoulsCommons
  • EffectPool and ResourcePool persist immediately when reset

1.1.0

ADDED

  • Shrine - New Souls Demo Stage!
    • Trader and Talker NPCs
      can be interacted with or attacked
    • Sprout Enemy
      causes poison damage
    • Elevator
      one way elevator with levers
    • Teleporter
      moves character between scenes
  • Effects - New Core System!
    set of behaviors that can be added or removed from characters
    • Poisoned
      periodically causes damage, healed by the new moss item
    • Boosted
      doubles strength for a while, added by the new booster item
  • Inventory
    allows using items directly from the UI without equipping them
  • Loading Screen

IMPROVED

  • intro can now be skipped
  • NPCs can return home or patrol when idle
  • additional events for characters on GenericTriggerArea/Item
  • better gamepad support
  • movement and resources are not saved unless they actually change
  • picked up items are now displayed in a message box
  • persistence can now be exported and imported from PersistenceContainer
  • boss arena properly resets player position
  • camera now uses cinemachine free look

An integration for the ink narrative scripting language is available on GitHub!

–WARNING–
Depending on your depth of use this update will contain a varying amount of breaking changes! Using source control and removing any previous versions completely when upgrading is recommended.

1.0.1

ADDED

  • manual page for SoulsPlayer(includes step by step guide for replacing the model)

CHANGED

  • Animator is now part of CharacterBase, AnimatedCharacterBase is therefore obsolete

IMPROVED

  • simplified model replacement for SoulsPlayer
    • colliders moved to separate GameObject ‘Body’
    • TimelineAction and SoulsBonfireAction can pass character animator to the PlayableDirector which lets us remove the dependency between environment and player model
  • translation and rotation of model can be suppressed independently
    • SoulsAttackAction no longer locks rotation until damage is activated
  • MovementSaver can be overridden from outside
    • BossArena uses this to properly reset the player if the game is quit during a fight
  • intro sequence can now be skipped
  • 2021 LTS compatibility

FIXED

  • usable items not being hidden if action is interrupted
  • potential initialization order errors
    • AttributePool value/stat dictionaries
    • CharacterActionArea Bindings
  • armor no longer vanishes at certain camera angles

1.0.0

Initial Release

Getting Started


permalink: /howto/gettingstarted title: “Getting Started” sidebar: title: “Manual” nav: manual —

Be sure to import at least the AdventureCore and AdventureManual projects to check out the getting started project. The scenes are located directly in Assets/SoftLeitner/AdventureManual/GettingStarted and the different asset used are all found in the subfolders.

The GSFinished scene contains the fully finished project, GSStepByStep has a deactivated gameobject for the state after each step and the GSEmpty scene only contains the environment and can be used to start from scratch when following this page.

1 Movement

First off we’ll just get a character moving around the environment.

  • Create an Empty gameobject called ‘Character’ and reset its transform
  • Add a GenericCharacter character component
    Most games, including the souls demo, will probably require some custom logic in the character but in this example we can get away with using the generic one just to bind all the other parts together.
  • Drag the GSRobo prefab into the Character and assign the Character field in the AnimatorProxy and the animator field in the character
    The prefab just contains a little robot model with a couple animations set up. The AnimatorProxy will forward things like animation events and root motion to our character which allows us to have them on separate objects.
  • Add a CharacterController and a CharacterControllerMovement movement component to the Character
    • adjust the character controllers center and skin width
    • assign the movement to the Movement field in the GenericCharacter so other systems can access it
    • assign the GSRobo you created as the Pivot in the movement so it is rotated when moving
    • assign the MainCamera as the Camera of the movement so it can translate inputs relative to the camera
  • Add a PlayerInput component to the Character(Input System)
    • for Actions select GSInput which can be found in the Other subfolder
    • set its Behavior to ‘Invoke Unity Events’
    • under Events->Default->Move create a callback that calls CharacterControllerMovement.OnMove

You should now be able to push play and move the character around the scene.

2 Triggers

In this chapter we’ll set up a trigger mechanism that slows our character when it moves through a certain area.

  • Add a GenericTriggerItem and a CapsuleCollider component to the Character
    • Assign the Character field on the trigger so it is accessible to areas it enters
    • Adjust the colliders size and tick the IsTrigger field
  • Create a new Cube 3D Object and add a GenericTriggerArea
    • Adjust the cubes size and material and tick its colliders IsTrigger
    • Add the MovementSpeedMultiplier instruction to the area and set the Value to 0.5

Start the scene and move the character through the area to see that it is slowed down by the factor defined in the Instruction. The area adds its instructions to the character of any trigger item that enters it and removes them when it exits. The generic trigger items and areas are good for general purpose, when creating more specialized logic consider inheriting from TriggerArea and TriggerItem like, for example, the damage system does.

3 Effects

Here we will add the same kind of slow effect as in the last chapter with a status effect instead of directly applying it. This allows us to visualize the effect on the character and it would also be easier for other systems to check if the character is currently in one of these areas.

  • Create an empty child in the Character and name it ‘Status’, this is where we will organize all the behaviors that make up a characters status
  • Add an EffectPool component which will manage the characters active effects
    • Assign the Character to the EffectPool to each other
    • Set the Effects field on the Pool(used later when dealing with persistence)
  • Copy the slow area from the previous chapter and remove the instruction
    • Create handlers in the areas CharacterAdded and CharacterRemoved events
    • Drag the GSSlow effect into them and select the EffectType.AddExternal and EffectType.Remove methods
  • Create a new UI Text and add a EffectPoolText
    • Delete the EventSystem Unity creates when you first add a UI component
    • Adjust the Text size and position
    • Assign the fields in the effect pool text

The effect assets for this chapter have been created upfront to streamline the process, the effect set and effect type can be created from the context menu under Create/Adventure. The most important bit is the effect Prefab which contains the effect itself with the slow instruction as well as the visual.

When you start the game now and move into the new effect area you should be slowed and additionally see a small blue sphere above the character. You should also see the effects name in the UI text you created.

4 Pickups

Next up we’ll create some item pickups and allow the character to add them to its inventory. First up we’ll allow the character to start actions that it runs into.

  • Create an empty child in the Character and name it ‘Actor’, this is where we will put all the logic that lets the character perform actions
    • Add a MinimalCharacterActor which will manage the characters current action
      • assign it to the character and the character to it
    • Add a CharacterActionArea which lets the character start actions it collides with
      • assign the actor to it so it knows where to start its actions
    • Add a Trigger CapsuleCollider and a Kinematic Rigidbody for the action area
  • In the UI create a new Text, this time with a CharacterActionAreaText which will display the areas current action
  • In the PlayerInput where we previously bound the move input create a new handler for Action and bind it to CharacterActionArea.OnStartAction

The next step is to give the character an Inventory it can store items in.

  • Create an empty child in the Character and name it ‘Inventory’, this is where everything related to items will go
    • Add a ListedInventory and assign it to the characters Inventory field. Assign GSItems to its Items field which will be important for persistence later.
  • In the UI create a new Text, this time with a ListedInventoryText which will display the items in our inventory

Finally let’s create the pickups that we can place around the environment.

  • Create a cube with a trigger collider similar to the ones used for the trigger areas previously
  • Add a PickupAction component
    • Add a SuspendMovement instruction so the character can’t be moved while performing the action
    • Set the CharacterTriggerName to ‘Pickup’ which is the name of the parameter in the GSRobo Animator
    • In Items assign the GSItem and set Quantity to 1
  • Once you’re happy with the Pickup clone it a couple of times so you can test adding multiple items to the inventory

You should now be able to see ‘Pick up Item’ when you move the character over a pickup. If you start the action using E or A on a Gamepad the character should perform a little animation, the pickup should be destroyed and the item added to the characters inventory.

5 Gate

The Pickup we have created in the last chapter performs an animation on the character only. Here we will create a new action that plays synced animations on both the character and an object in the environment. We’ll also define an item cost so we can actually do something with the items we’ve picked up in the last chapter, this is fairly common behavior for things like keys.

  • Drag the GSGate prefab from the assets into the scene
  • Create a new child object under GSGate and name it ‘Action’
  • Position it in the same place and rotation you want the character to stand when performing the action, for example 1.4 in Z and rotated 180 around Y
  • Add an ObjectAction component
    • Add a SuspendMovement instruction
    • Set the following:
      Name: ‘Open’ (displayed by the Action Area)
      Cost: 1 x GSItem (can’t start action unless character has the item)
      CharacterTriggerName: ‘Open’ (can be checked in the GSRobo Animator)
      ObjectCharacterTarget: the action itself (character is moved here)
      ObjectTriggerName: ‘Open’ (like in the GSGate animator)
      ObjectStateName: ‘Opened’ (see GSGate animator, used in persistence later) ObjectAnimator: GSGate
  • Add a BoxCollider with IsTrigger and Size 2 so the action area can collide with and start the object action

If you want to test out the action without having to pick up an item first you can either remove the cost or add some items to the ListedInventory in the editor before you start the game. To see how the animations are synchronized check out the animation events on the Open animations. The START and END events are needed so the action knows its state, the ACT message in the character animation starts the object animation.

6 Health

In this chapter we’ll give the character a health resource.

  • As a child of Status create a new object named ‘Health’ with a ResourceValue component
    • Set the type to GSHealth which identifies the resource and gives it a name
    • Set Maximum and Value to 100 so the character starts with full health
  • In Status add a ResourcePool component
    • Add the health resource value to its values
    • Assign the pool to the characters Resource Pool field
  • In the UI create a new Text with a ResourcePoolText which will display all resources of the Pool and their values

Next we’ll create some areas that deal damage which can subtract from the health resource or add to it.

  • Create two new areas, probably easiest to just copy the slow areas and remove the trigger area
    • Add TriggerDamagerSender components to both
    • In the Damages field assign GSHealthDamage in one and GSHealDamage in the other with a value of 1
    • Adjust the timing settings, for example check SendTick and set TickRate to 0.1
  • On the character add a TriggerDamageReceiver and assign the character so it can actually receive the damage

The characters health should be reduced when moving into one area and increased in the other.

7 Vitality

The maximum Health was previously just statically assigned in the resource itself. In this chapter we’ll add a Vitality attribute and a HealthMaximum stat(which is just VIT*10) which will be assigned as the Health resource maximum.

  • In Status add an AttributePool component and assign it to the character
    • Add GSVitality to the Attributes and give it a starting value of 10
    • Add a new ResourceMaximum with the Health resource value and the GSHealthMaximum stat
  • In the UI create a new Text with a AttributePoolText which will display all attributes of the Pool and their values
  • Create two new areas by copying the slow areas but this time leave the trigger area and just empty the event handlers and instructions
    • Add a new handler to CharacterAdded and drag in the GSVitality attribute
    • Choose the AttributeType.Add method in one area and Remove in the other

When you move into the areas you should now receive or loose one point in the Vitality attribute which changes health max by 10.

8 Persistence

Finally we will take care of persisting the state of all the things we’ve created this far.

  • Create an empty gameobject named ‘Persistence’
    • Add a PersistenceContainer component, you’ll find some useful buttons in its inspector like ‘Delete Data’ which can be used to reset its save data
      • Set the Key to ‘GS’
      • Add GSPersistence to its Areas
    • Add a PlayerPrefSaver and assign it as the containers saver
  • Add a ManualPersister to the character and assign it to
    • GenericCharacter, CharacterControllerMovement
    • ListedInventory
    • Resource-, Attribute- and EffectPool
  • Add and assign a ManualPersister to the ObjectAction in the Gate
  • The PickupAction has its persister built in so you can assign PersistenceArea and Key directly to it instead of adding a ManualPersister
  • In every ManualPersister(and PickupAction) assign GSPersistence as the Area
  • Give every ManualPersister(and PickupAction) a Key to identify the piece of data

Instead of manually choosing keys you can also click ‘Generate Missing Keys’ on the persistence container which will generate keys if they are empty.

Since the container should be set to AutoSave and AutoLoad by default everything you do in play mode should now be saved. When you stop play mode and start it again the state will be loaded. You can reset the state by clearing player prefs or deleting data from the container. If you want suspend persistence and start from scratch every time you can unassign the Saver on the container.

Visual Scripting


permalink: /howto/visualscripting title: “Visual Scripting” sidebar: title: “Manual” nav: manual —

Setup

Visual Scripting is not automatically initialized when importing AAK. Before opening any of the visual scripting scenes be sure to go into Edit/ProjectSettings and click the ‘Initialize Visual Scripting’ button.

Initialize

This would be enough for the included scenes to work but to be able to use AAK types in variables and find the included nodes in the fuzzy finder we’ll have to add them to the visual scripting settings.

You can find a prepared settings file in …/AdventureManual/VisualScripting/VisualScriptingSettings.asset which you can replace your own settings in the ProjectSetting folder with. After doing that you’ll have to restart the editor.

Types

Add additional AAK types here so you can use them in variables or call their methods and properties directly. A lot of AAK functionality is exposed through the custom units but there will always be specific use cases where that is not enough.

Nodes

Finally make sure the AdventureCore(and AdventureSouls if you are exploring the demo) are added as Node Libraries and click ‘Regenerate Nodes’ so all the custom units that come with AAK can be found in the fuzzy finder.

Units

Getting Started

The Assets/SoftLeitner/AdventureManual/VisualScripting/VSGettingStarted.unity scene copies some of the behaviors from the getting started manual using visual scripting. In doing so it touches on how to use most of the included custom units.

Areas

The CharacterEnter and CharacterExit units found in Events/AdventureCore/Triggers fire whenever an item with an attached character enters/exits their area. Like many of the custom units when the area is not set explicitly the unit looks for the needed component on the GameObject of the machine and its parent. This has a small performance cost but it improves reusability and readability of the graph.

Triggers

Vitality

The SlowInstructionArea adds an instruction when a character enters and removes it on exit. Take note that the instruction that gets removed has to be the same unit as the one added and not just one of the same type. The character that this is done to is passed from the events to the other units.

SlowEffectArea work pretty much the same but instead of instructions is adds and removes an effect. Same thing for the vitality areas, just split into two different areas.

Damage

The DamageSenders work a little differently as they do things periodically instead of just on entry/exit. Every 0.5 seconds they request the characters currently inside the area using the AreaCharacters unit. It then loops through them and send each one a damage event. They could be improved to only run once a character has entered but for the sake of simplicity they run non-stop here.

Actions

To define actions using VisualScripting we will use the VisualScriptingAction behavior. This action does nothing on its own but provides some additional events and methods geared toward that. For example usually whether a specific character can start an action is checked in a method. VisualScriptingAction fires the CheckStartable event and then returns whatever was set in Startable by the graph.

Pickup

When started the pickup sets the action name which will be displayed in the UI. In this case that is unnecessary but, for example, if the item was changeable we could use the item name here. After that is checks in persistence if the action has already been performed and destroys the action if it has.

On Action Startable we check if the items can actually be added to the characters inventory.

When the action is actually started we do various things, whenever the character is concerned we pass the one that started the action which we get from the event parameter. We add an instruction that suspends the characters movement so the character can’t move during the event. We set a trigger on the characters animator to start the pickup animation. We also add the items to the characters inventory and set the persistence bool that saves if the action has been executed. On the action we set Available to false so the action can no longer be started in any way.

In the ActionMessageReceived we wait for the ‘END’ animation event which ends the action. In the coded PickupAction there are additional checks that make sure that the ‘END’ message is only accepted if a ‘START’ is sent first. This is meant for situations where a character is still in another action(a roll for example) when the pickup action is started.

Finally when we receive ActionEnding we let the player move again by removing the SuspendMovement instruction and destroying the GameObject.

Gate

When it starts and the action has already been executed the animator gets moved to the Opened state. The GameObject also gets destroy but as opposed to the pickup this only destroys the action here and not the whole object.

When checking for Startable the action on the gate checks if the character has at least one GSItem in its inventory.

In addition to things similar to the pickup action the gate action also moves the character into position and removes the items from its inventory.

There is an animation event called ‘ACT’ which starts an animation on the gates animator.

Lean

One deviation from the getting started manual is the character leaning forward a bit when moving. To achieve this an additional Transform called ‘Pivot’ has been inserted between the character and the GSRobo model. This is needed so the movement and the lean logic have separate transforms and don’t interfere with each other.

Lean

The implementation is relatively straightforward. We take the SpeedFactorForward from our character multiply is by 10 and set it as the x rotation of the pivot. Bear in mind that the context of the MovementInfo is a character and not a movement. This is the case for most of the units working with parts of the character like inventory, attributes and so on.

UI

The scripts that display the resources, attributes, … in the UI have also been replaced with visual scripting. For simplicity most of them simply rely on the ToString implementations of the units they display. Just like the scripts they replace this is more of a debugging solution than something suitable for a release.

Behavior

The following chapters show a couple different ways that NPC behavior may be controlled using Visual Scripting. The first two are found in the …/AdventureManual/VisualScripting/VSBehaviour.unity manual scene.

Patrol

Patrol

The red bot patrols between two points until the player enters its area. As long as the player is in the area it approaches it. If it catches the player it teleports it back to its spawn point.

This behavior is realized using a state graph which has separate states for patrolling and chasing. It switches between them when the player enters or exits the chase area which also sets the movement target so it know where to go. When either state is entered the appropriate action is started.

We don’t have to do any additional work for persistence here since patrol persists the current target on its own and the rest of the behavior is determined by the characters positions which gets saved by their movements.

Toggle

Toggle

This script machine shows a way to chain a couple different actions and persist the current state. This is what makes the yellow bot move between and toggle the gates.

When it is started it retrieves a persisted integer and starts the appropriate action. Whenever an action ends it increases the counter by one and starts that action.

AdventureSouls

Open up …/AdventureSouls/Scenes/Debugging/Enemies/SoulsDebuggingVisualScripting.unity to find two special enemy variants for the souls demo. They are not used in the regular levels as visual scripting is optional in that demo.

Actions in these enemies are not just started once like in the manual examples. They are kept active using the Perform NPC Action node which start their action whenever possible and just pass the flow if the action currently happening. This allows for a different kind of modeling behavior that is similar to classic behavior trees.

These enemies are also not entirely visually scripted. They still use the SoulsEnemyCharacter for things like death and loot. The graphs also use some special units that allow easily retrieving the state of the character or its current default or item actions.

Toggle

The attacker enemy is a slightly more advanced version of the default enemy. It varies its attacks depending on the stamina it currently has and also uses whatever weapon it has equipped in its right hand. When it can no longer perform even a light attack using its current stamina it performs the recover action.

Toggle

The archer is able to equip appropriate items depending on the players position. The distance at which it switches to melee can be configured using the ShootingDistance variable.

The SoulsWeaponSlot does not allow switching equipment while an attack is happening. So if the archer is currently aiming when the player enters its melee range it will wait until that action has been appropriately canceled. The EquipItemCategory units go to their False output in that case.

Also note that the action after equipping the bow and arrows goes to KeepAlignedToTarget when it is active. This is what makes the enemy actually turn to aim at the player. When it can’t be started, probably because of lacking stamina, it goes to the Recover action through its False port.

Character


permalink: /manual/character title: “Character” sidebar: title: “Manual” nav: manual —

Core

Characters are the brains of the operation for any entity in the world be it player or NPC. They tie all the other concepts together and implement any logic the belongs to a specific character but does not fit any of the other concepts.(input for players, AI for enemies)

CharacterBase has explicit fields for AttributePool and ResourcePool since these probably wont be inherited from. The actor, movement and inventory implementation can be freely defined when inheriting. CharacterBaseTyped/AnimatedCharacterBase define the fields for theses in a generic manner which makes them convenient to inherit from.

The character base implements a very simple messaging channel that can be used for events that may be used by every other system on the character. Typically messages are received from things like animators or timelines and then used by the current action(animation end>action end) or something like the AudioManager(play sound on animation foot down).

Instructions

A CharacterInstruction defines some type of state change for a character in a way that can be set and reset. This is done to deal with situation where multiple systems modify the same properties without knowing about each other. For example modifications to the movement speed or the visibility of an item slot. AdventureCore defines some useful general instructions like multipliers for attributes or stats and suspending damages, collisions or movement. See the header of the different instructions for a more detailed explanation of each one.

CharacterInstruction(or CharacterInstruction[]) can be used as an inspector field despite being abstract because AdventureCore comes with a PropertyDrawer that allows choosing the actual implementation in the inspector.

Souls

  • SoulsCharacterBase provides some common defense, stagger, guard and parry logic
    • SoulsPlayerCharacter manages input(movement, lock-on, menu, …), XP recovery and character reset(bonfire)
    • SoulsMorningstar character is a specific boss enemy that chooses its attacks based on the position of the player.
    • SoulsNonPlayerCharacter base that provides some default behavior for simple NPC characters
      • SoulsEnemyCharacter attacks when something enters its trigger area
      • SoulsFriendCharacter has some additional objects(talk, trade) when idle and only attacks when damaged

Hero

Most of the player logic is done through the HeroCharacter scripting graph. This includes things like Animation Parameters, Context Actions Selection, Damage and Death Handling. The graph subscribes to the main StateManager on the HeroSetup object to switch between its Play and Paused states.

The HeroPlayerCharacter class provides the sheathing behavior and keeps track of important transforms like ItemLeft or Sword. These are exposed to visual scripting in HeroSheathingUnit and HeroInfoUnit. It also manages presentation of newly found items and passing on confirmations to text boxes.

The HeroSkeleton character uses the GenericCharacter class from AdventureCore, its entire logic is driven by the HeroSkeleton visual scripting graph.

Acting


permalink: /manual/acting title: “Acting” sidebar: title: “Manual” nav: manual —

Core

Action

An action is basically anything a character can do that may occupy it for a while.

Where the action behavior is defined depends on its use. It could be defined directly on a character so it can be triggered by AI or input(attacks, moves, …). Alternatively it may also sit on some object in the environment and become available to the character by colliding with a CharacterActionArea(Door, Levers, Dialog, …).

The most important methods to override when implementing a new action are:

CanStart returns if all requirements to start are met(key for door, stamina for move).
OnStart is called by the actor when the action is actually started so this is the main point where things are kicked of.
CanEnd defines if an action can be ended by the outside even though it has not ended itself yet.
OnEnd is called by the actor when the action is ended. This is where things are reset for reusable actions or when actions are destroyed if they are one time.
OnMessage is where messages from the character will be directed when the action is active. For example events from the animator that are needed so the action knows when it has ended.

Actor

A character actors manages a stream of actions for a character. That means taking care of how and when they are started and ended as well as forwarding the appropriate messages to them.

CharacterActorBase can technically accommodate more than one active action at a time but the most common scenario is that actions are performed one after the other rather than parallel. SerialCharacterActor serves as a good base class for these scenarios. Both the MinimalCharacterActor(ai) and BufferedCharacterActor(player) inherit from it.

Souls

Attack

Attacks in AdventureSouls have a custom implementation called SoulsAttackAction.

Attacks CanStart when the character has enough stamina and either the action has not started or when it has and it CanCombo. OnStart it triggers an animation and adds an instruction that suspends the characters regular movement so that only the root motion from the animation is used. It also removes the used stamina from the character.

It responds to the following in OnMessage
START confirms that the animation has actually started, this makes sure we are not responding to messages from the wrong animation.
DMG_ON/DMG_OFF control whether the damage on the weapon is turned on. COMBO is sent when the animation is ready to transition to the next swing.
END means the animation has ended and the action should end.

CanEnd is true when the attack CanCombo and the next action is that same attack. OnEnd the attack reset some things including the combo counter if the next action is not itself.

Roll

The roll in AdventureSouls uses an action from AdventureCore called MotionAction. This action is useful for rolls, jumps and any other moves that are based on a character animation.

CanStart returns true when is has no cost or when the character has any of the demanded resources left. OnStart removes the cost from the character and sets the animation trigger. If AlignCharacter is checked(which is the case for a roll) the motion calls AlignToInput on the characters movement. Without this the player would not be able to change direction when rolling multiple times in a row because movement is always suspended.

CanEnd is true when RELEASE has been received by OnMessage so we can transition into the next roll before it has ended. OnEnd just resets things.

Gate

The gate in AdventureSouls uses an action from AdventureCore called ObjectAction. This is a good action for interacting with objects in the world. It supports one animation on an object you’re interacting with and another(optional) one on an object that should move in the end. It is useful for doors, levers and chests.

An ObjectAction CanStart always unless there is a cost item defined the character does not have. OnStart it moves the character into place and triggers its animation. When ACT is received in OnMessage it starts the animation on the lever and in OnEnd it starts the one on the gate.

Hero

Attacks

The different directional Attacks of the Hero use a VisualScriptingAction combined with the HeroAttackAction scripting graph. The kind of attack each action performs is determined by the AttackType integer variable on the object. This integer gets sent to the animator in the graph which triggers the attack animation. During the animation the graph reacts to messages defined in the animation like DMG_ON which activates the damage sender on the sword.

Which of the attacks is started is decided in the Actions Subgraph of the HeroCharacter. This graph checks if the hero actually has a sword equipped and then selects an action based on the input direction.

Throw

Throwing is another VisualScriptingAction that shows a visual(HeroNutVisual) in the characters hand while it winds up and that instantiates a projectile(HeroNutProjectile). It is started from an equipment item(HeroNut) and uses the HeroThrow graph.

In the actual levels this item only randomly drops from pots. To try it out imediately open the HeroDebuggingGeneral scene, the inventory of the player is overridden to have it equipped.

Carry

The HeroCarryAction is s custom action either started as a context action from the CharacterActionArea(Pots) or by an Equipment Item that uses the HeroSpawnEquipment graph(Bombs). The HeroDebuggingGeneral scene contains some examples of pots, bombs and other objects that can be carried.

It parents itself to the ItemParentOverhead transform defined on the HeroPlayerCharacter when started and sets its rigidbody to IsKinematic=true. It sets the CONTEXT_ACTION_OVERRIDE variable to receive the context action button while it is active. When it is ended by Throwing, Putting or some other action occuring it unparents itself again and sets isKinematic=false so it is affected by Physics again.

Climb

In contrast to most other actions the custom HeroClimbAction starts itself under the right circumstances. It uses a GenericTriggerArea to detect climbable surfaces and checks whether the player input points against those.

During climbing it checks points in four directions to determine if the climb can continue, these points are visualized as blue spheres when the action is selected. When it cannot climb upwards is performs Raycasts forward and down to check if the character can pull itself up. These are visualized as green lines.

To define a surface as climbable simply add a GenericTriggerItem and set the Key to CLIMB. Check out the HeroDebuggingClimbing scene for examples of climbable surfaces.

Block

The HeroBlockAction lets characters pull and push on rigidbodies. It is defined on the respective side of the objects that needs to be pushed and gets started through the CharacterActionArea.

The rigidbody needs to be set to isKinematic=true when not used so the character can’t simple push it away by running against it. When pushed isKinematic is set to false to allow the object to fall when it is pushed over a ledge. The action then ends itself and waits for the rigidbody to stand still before it resets isKinematic.

The HeroDebuggingBlock scene contains various examples on how block pushing may be used.

Item


permalink: /manual/item title: “Item” sidebar: title: “Manual” nav: manual —

Core

The getting started tutorial shows how an inventory can be added to a character and how to create item pickups. An example of usable and equippable items and slot can be found in …/AdventureManual/Systems/Item/ManualItem.unity.

Item

Items are a type of ScriptableObject that defines whether an item can be equipped or used and how it acts when it is. Derive from ItemBase and override the appropriate methods to do so and declare fields for relevant for items of that type(for example visuals). For items that do not have any behavior of their own(key items, currency) the GenericItem can be used. ItemBase also defines some basic common properties that most item systems will use like name, image and categories.

The EquipmentItem acts as a attribute- and stat modifier while it is equipped and whether is can be equipped is also determined by the attributes and stats of the character. The UsableItem can add Attributes, Resources and Effects to the character when used. Both of them derive from PrefabItem which can define a prefab that is instantiated when equipped to a InstantiatingItemSlot.

Inventory

An Inventory is where items are stored, mostly in combination with a character. InventoryBase defines some common ways an Inventory may be interacted with like adding, removing or using. AdventureCore comes with a simple implementation called ListedInventory which just stores items in a list without any limitations to item quantity or stack size. To implement a more specialized inventory, for example a re4 style case, just inherit from InventoryBase and implement the needed methods.

One important distinction to make when working with Inventory is between an ItemQuantity and an InventoryItem. While both of these have an Item and a Quantity they are used very differently. An InventoryItem represents an entry in an Inventory and can be used to react to the quantity of an item a character has and whether it is equipped. An ItemQuantity is not bound to any Inventory or Character and can be used to configure amounts of items that are gained or used when performing some action like picking up items or using a key.

The Inventory of a Character also acts as the access point for its ItemSlots.

ItemSlot

ItemSlots enable Items to be equipped to and interact with a character. Deriving from ItemSlot and overriding equip and unequip is the recommended way to create a new kind of ItemSlot. Since they are in scene and exist by character ItemSlots can take care of any runtime data the item may produce when equipped(visuals, effects, …).

Souls

Armor

The SoulsArmorItem defines a prefab for the visual of the armor when it is equipped. The instancing of that prefab is done by the SoulsArmorSlot which also makes sure the bones on the renderer are set and manages the instance(as in destroys it when unequipped).

When armor is equipped it can also modify some stats, adding the modifier is done in the slot but could also be done in the item since it has no side effects. Lastly the armor defines attribute requirements which are checked by the item itself in its CanEquip.

Weapon

In addition to the slot-item combination that armor uses, a weapon also has a SoulsWeapon behavior which serves as the access point for a weapons actions and damages in the weapons prefab. It can be used to check the damage through the prefab when the item is stored in the inventory and for the actions when it has been instanced by the slot.

The SoulsWeaponSlot allows binding input to its weapons light and heavy actions. It then rebinds these whenever the equipped weapon changes.

Hero

All the items in the hero demo have a visual prefab which is used when the hero presents the item when it is first picked up.

Gear

Sword and Shield are simple PrefabItem from AdventureCore. They are instantiated on the respective InstantiatingItemSlot when equipped. Their actual functionality in defined in the attack and guard actions which themselves check that an item is equipped.

The hero demo also has various items that expand the inventory. For example the HeroBombPouch that lets the character carry more bombs. These did not make it into the levels yet but can be tried out in the HeroDebuggingGeneral scene(chests).

Equipment

Equipment items hold an action prefab and can be assigned to on of three equipment slots to use them. When the HeroEquipmentItem is equipped to a HeroEquipmentSlot its action prefab is instantiated and when the slot is used that action gets started. When an item is used directly from the inventory it gets equipped to a hidden EquipmentX slot.

Usable

HeroHeart is a UsableItem which restores one HeroHealth resource.

The HeroHeartTriple is a placeholder for three HeroHeart items. Therefore it is presented as a seperate item but when added to the inventory three hearts are added instead.

HeroHeartEssence adds one HeroHearts attribute which expands the characters health pool. HeroHeartShard is placeholder for the essence but has a prerequisite count of four before it is replaced.

Attribute


permalink: /manual/attribute title: “Attribute” sidebar: title: “Manual” nav: manual —

Core

Attributes are whole number metrics that, roughly speaking, define a characters capabilities(strength, vitality, ….) but can also be used for any other number that should be persisted and does not fit any other concept(xp). Attributes do not define any behavior of their own, instead other things that depend on them will check them when needed. To add an attribute simply create an Adventure/AttributeType from the context menu in your assets.

In contrast to attributes an AttributeStat does not have a value of its own. Is is usually calculated based on one or multiple attributes or even just a constant value that just gets changed by modifiers. How a stat is calculated is defined by in its GetValue method, derive from AttributeStat to define your own calculations. Check the stats already defined in AdventureCore for examples.(MultipliedStat, TieredStat, …)

The AttributePool manages the available attributes and stats and their value in the scene. It is usually attached and assigned to a character. This is also where modifiers can register to change exposed values and where observers can register to be notified of changes of attributes and stats.

Souls

The SoulsVitality attribute can be leveled up at bonfires and influences the SoulsHealthMaximum stat which defines how many hit points the player has. On enemies the hit point maximum is set directly without using a stat.

SoulsEndurance is pretty much the same but for SoulsStaminaMaximum.

SoulsStrength can be leveled at the bonfire and influences the SoulsPhysicalAttack which is used by weapons to calculate their damage.

SoulsLevel and SoulsExperience are counters that are used to check if the player can level up and how many times they have done so.

Lastly SoulsFlask defines how many flasks are restored to the player when they sit at a bonfire. There is no way to change it currently but it has been added so increasing it with a pickup or some other action can be implemented later.

The stats SoulsPhysicalDefense and SoulsPoiseMaximum are not influenced by any attribute, they just have some base value and are only increased by modifiers like armor.

Hero

AdventureHero only defines a single Attribute called HeroHearts. It is used in the HeroHeartsMaximum stat to define the maximum health in the AttributePool of the player character. The Attribute starts at 3 and gets raised by collecting HeroHeartEssence.

Resource


permalink: /manual/resource title: “Resource” sidebar: title: “Manual” nav: manual —

Core

The getting started tutorial shows how resources can be added to a character and how to create damaging trigger areas. An example of different resource types and bars can be found in …/AdventureManual/Systems/Resource/ManualResource.unity.

Resources are floating point metrics that can change on a frame to frame basis. A resource has a resource type which is mostly used to identify it and a ResourceValue behavior which actually holds and changes the value. The default ResourceValue does not change on its own. For more specialized resource values that change their value over time or by some other means you can inherit from ResourceValue(look at ChangingResourceValue for an example)

Resources are managed by a ResourcePool which can be used to check, change and monitor resource values.

Souls

SoulsHealth is a simple ResourceValue that does no change on its own. It is checked after a character receives damage to see if they die.

SoulsEndurance is a custom behavior in AdventureSouls called SoulsStamina. This is similar to a ChangingResourceValue but also subtracts from its value when the player is sprinting.

SoulsPoise is a resource value that defines whether the character gets staggered by a hit. It gets damaged by attacks and then refills on its own.

Hero

The player only has the HeroHealth resource which gets reduced by HeroHealthDamage and recovered by picking up HeroHeart pickups.

The Damage subgraph in HeroCharacter checks if the health has been reduced to zero and starts the Die TimelineAction with the HeroDeath timeline.

In addition to health skeletons also have the HeroStun resource. This EffectResourceValue adds the HeroStun effect to the character which stops movement and animations. The HeroStunResource visual graph is in charge of visualizing the effect by tinting the model materials in a blue shade.

Effect


permalink: /manual/effect title: “Effect” sidebar: title: “Manual” nav: manual —

Core

Effects are temporary behaviors that can be added or removed from a character. Every EffectType scriptable object assets describes an effect and points to a prefab that will be instantiated when the effect is added to a character. These prefabs have some implementation of EffectBase at its root which, through its Pool property, gets access to the character it was added to.

The GenericEffect implementation of EffectBase can be used when an effect is only visual or if the effects logic can be expressed using character instructions. Attribute- and StateEffect modify a characters attributes and stats and can be useful for buffs and de-buffs. DamageEffect periodically sends a damage event to the character and may be used for a burning or poisoned status for example. Lastly the TimeoutEffect is an expanded version of GenericEffect that removes itself after some time instead of having to be removed be some outside logic.

One built in way to add an effect to a character is the EffectResourceValue which adds the effect when its value is full. Another would be using a GenericTriggerArea which has events for when a character enters or leaves its area. Since EffectType has an Add method that takes a character as its parameter it is possible to link it directly in the inspector.

Souls

The SoulsPoisoned DamageEffect gets added when the players SoulsPoison EffectResourceValue reaches its maximum. The resource is increased when something deals SoulsPoisoning ResourceDamage to the player. This kind of damage is currently only dealt by the sprout enemy which has a ticking DamageSender and is either reduces by just waiting or by using the SoulsMoss ConsumableItem.

SoulsBuffed is a simple TimeoutEffect buff that doubles the characters strength for a set time and then removes itself which is added by the SoulsBooster consumable item. SoulsSlowed reduces a characters movement speed. Both of theses use character instructions for their actual effect.

Hero

The HeroStunEffect gets added to enemies like the skeleton whenever they are stunned. The effect adds some instructions to the character that stop movement and animations. The rest of the logic is found on the resource value.

Movement


permalink: /manual/movement title: “Movement” sidebar: title: “Manual” nav: manual —

Core

The movement is where all the traversal logic for a character is located. Movements will probably vary quite a bit depending on if it is meant for the player or some NPC and also depending on the game. This is why MovementBase defines mostly ways for other components to interact and interfere with movement in some general ways. For example it gives other components a way to slow movement down, suspend it when some animation plays or force it to move somewhere. Any more specialized logic will have to lie in the implementation.

Conveniently the generic base classes for characters(CharacterBaseTyped, AnimatedCharacterBase) allow us to declare the type in the class header which lets us use the implementation directly from inside the character or any other component that references the character implementation rather than CharacterBase.

Souls

AdventureSouls uses the CharacterControllerMovement for its player, the SoulsPlayerCharacter send all the necessary input to it.

The zombie enemies use the NavMeshAgentMovement implementation in combination with NavApproachAction so that they can follow the player around the level.

Lastly the Morningstar boss uses ManualMovement since it does not really do any traditional movement. Even if the Morningstar had more movement of its own a nav mesh would not really be necessary. The boss arena is a simple rectangle and there is no way for the boss to leave to as long as it aligns itself to the player and walks it will always get to them.

Hero

Similar to the souls demo the player in hero uses CharacterControllerMovement and the skeleton enemies use NavMeshAgentMovement.

Looking at the component you may find that the movement speeds are both set to 0. This is done because movement in hero is driven by root motion.

Persistence


permalink: /manual/persistence title: “Persistence” sidebar: title: “Manual” nav: manual —

Core

Persistence in AAK is built as simple as possible for the consuming components. Therefore we will start exploring persistence from this side and work our way towards the persistence management.

Most built in components of AAK simply reference a PersisterBase to keep their own persistence logic as slim as possible. That PersisterBase has methods for checking if a value has ever been set, retrieving it, setting it and clearing it out. For example the ObjectAction just calls Set(true) when it is triggered and whenever it is started in the future(scene/game reload) it calls Get() to check if it has been triggered already.

Persisters can come in different forms but there is one called ManualPersister that is meant specifically for the case above where some other component needs to save its state. The persister has a field for a key which is needed to identify its data and one for the area. A persister is convenient to keep persistence logic out of game logic but not necessarily needed, the PickupAction for example just deals with persistence itself without using a separate persister.

Another kind of persister is the DestructionPersister which simply sets a bool when it gets destroyed and destroys its GameObject if that bool has been set in the past. This means you can just add this persister to any destructible object to persist the destruction without any other scripting needed.

A PersistenceArea is a ScriptableObject found in Adventure/PersistenceArea that groups together data that will be saved together. This can be useful to separate smaller data that is saved frequently from larger data that is saved less frequently. The PersistenceArea also has the IsGlobal flag which is meant for data that is saved independent of save slot(settings for example).

The PersistenceContainer is the central singleton manager for persistence that is needed in every scene that uses any kind of persistence. This is where all the persistence data is managed and eventually sent to the saver that actually saves it to a storage medium. As you might have guessed from the above ObjectAction example state persistence in AAK is pushed rather than pulled. This means that state is not collected when a save game is created, instead state is sent to the container whenever it changes and the container decides when it actually saves the state. The container can be set to AutoSave so that is saves at the end of the frame whenever anything has changed, otherwise a Save() has to be called by some outside component like a save button.

In the inspector for PersistenceContainer you can find some useful helper buttons that can be used to deleted data when debugging or to check the keys of the persisters in the scene for duplicates. It can also generate random GUID keys for any persisters with an empty key field. So if you have objects that do not necessarily need a readable key just leave the key in their prefab empty and generate them all before you use the persistence.

As mentioned above the container does not save to disk itself, instead it references a PersistenceSaverBase for that purpose. This is done so that the ‘device-facing’ part of persistence is easily replaceable as I anticipate it might have to be customized depending on the built platform and other factors.

Souls

State in AdventureSouls is split into a couple different areas. SoulsSystemPersistence is a global area independent of the save slot that is used for settings like the sound effects volume but also to save a short info structure for every save index that is displayed in the title screen. SoulsPlayerPersistence is just used by the player character, the player gets its own area because it is saved quite frequently. The SoulsPermanentPersistence is used to save permanent world state like collected items or pulled levers. Lastly SoulsTemporaryPersistence is where any state resides that is discarded whenever the player changes the level or sits at a bonfire. It contains the state of the enemies and destructible environment objects.

The PersistenceContainer is part of the SoulsSetup prefab which contains all the necessary setup to run the game. Basically if you create a new scene and add the setup as well as a plane for the player to stand on you can start the game and it’ll work. If you check the container on the setups in the debugging scenes like Scenes/Debugging/SoulsDebuggingGeneral you may notice some overrides. For one the key is overridden so that data saved for that debug scene does not override data from the actual game scenes. Also the saver has been removed for most of them, this completely disables persistence because for most debug scenes a complete reset is more useful. If you want to debug the persistence logic you have to reassign or revert that saver.

Hero

Persistence in the hero demo is a bit more straightforward than in souls. There is only one PersistenceArea called HeroGamePersistence which saves all the data per save slot.

Movement is not persisted, the current scene is set in the ‘Scene’ variable and the desired entry is saved in ‘Parameter’. The game is saved automatically in scene transitions(HeroExit) which also write the current scene or when the game is ended from the menu. When the game is loaded the scene set in the variable is opened.

Whether, for example a heart shard has been picked up or a cracked wall bombed is persisted using a DestructionPersister.

Damage


permalink: /manual/damage title: “Damage” sidebar: title: “Manual” nav: manual —

Core

Damage in AAK is a multi stage process that lets a sender, receiver and kind of damage interact. In PreDamage the sender and receiver are informed that a damage is about to occur between them, they both have the chance to completely cancel the damage here. During OnDamage each kind of damage that the sender has is packaged into a damage event that gets send to all the participants. This is where a senders and receivers can modify the damage value and where the DamageKind applies whatever effect the damage has. Finally in PostDamage the sender and receiver have a chance to react to the combined changes of the applied damages.

An IDamageSender is any component that causes damage occurences. It can be implemented by any class that sends damage. You can Use the static DamageEvent.Send to send your damages. AAK comes with a TriggerDamageSender that uses that regular Unity trigger messages(OnTriggerEnter, …). By default it only sends damage when a TriggerDamageReceiver first enters but it can also be configured to send damage in intervals or at the end.

AAK also comes with the DestructibleDamageReceiver which destroys(or replaces) its gameobject when it gets damaged. This is ideal for destructible environment object or any other object that needs to get damages but does not warrant a full character.

Both the TriggerDamageSender and the TriggerDamageReceiver forward all their damage calls to the character they are attached to(if any). Therefore if we want to react to any kind of damage done to a character we can conveniently do that in the character implementation. The SuspendSendDamage and SuspendReceiveDamage character instructions hook in here.

Damages can theoretically be anything, what exactly a damage does is decided by the DamageKind implementation. The most common kind of damage is removing a resource(HP) from a characters ResourcePool. AAK comes with the ResourceDamage damage kind which does exactly this.(ContextMenu>Adventure/ResourceDamage)

Souls

Somewhat more complicated than a lot of other games damage in the souls demo is split into physical and poise damage. Poise damage is generally used to signify the brunt of an attack rather then the pure health malus.

The base character defines a lot of the damage handling in the demo. Parries are checked in PreDamageReceive so that the damage can be cancelled if a parry succeeds. In OnDamageReceive the damage value is reduced by defense or set to 0 when a character is guarding. In PostDamageReceive it checks if the character dies or is staggered as a result of the damages applied.

One thing we need to make sure is that a player does not damage itself. We could theoretically just cancel any damage that a player weapon might do to the player in PreDamage but using Layers is far more convenient and performant.

The AdventureSouls demo has various Layers that control which objects can interact with each other. Check the Physics settings to see how exactly they are configured.

The player has a general Player Layer that is used for any kind of logical interaction like entering areas or object interactions. The PlayerBody Layer is used to receive damage and interact with physics objects like destroyed crates. In the current demo the body, just like the logic, uses a simple capsule collider. To get more exact hitboxes and more interesting physics interactions the PlayerBody is the one that should be changed to have more detail. Lastly PlayerDamage is used on any weapons the player uses and interacts mostly with EnemyBody.

The Enemy has a very similar setup to the player with the Enemy, EnemyBody and EnemyDamage Layers. There is also a NeutralDamage Layer that is used by traps so they can send damage to either character. The Trigger Layer is used for logical area to make sure these do not interact with damages or physics at all.

Hero

Damage in Hero fairly straightforward. The HeroHealthDamage is used by player and enemy attacks as well as environment hazards like the boulder to reduce the health resource of the characters.

The Layer is also not split within the character, the entire player including the sword and its damage is on the Player layer. In the same way the skeleton lies on the enemy layer.

HeroStunDamage is a special kind of damage caused by the HeroNut throwable that can stun enemies. It does so by raising the HeroStun resource which ultimately adds the HeroStunEffect to the character.

The HeroBombDamage does nothing on its but is used as a damage filter on the HeroCrackedWall so that the DestructibleDamageReceiver can only be affected by bombs.

Souls Player


permalink: /manual/souls-player title: “Souls Player” sidebar: title: “Manual” nav: manual —

The following is an overview of the SoulsPlayer prefab in the AdventureSouls demo.

SoulsPlayer

The main transform contains the following components.

  • SoulsPlayerCharacter
  • ManualPersister used to save any state related to the player
  • CharacterControllerMovement and the CharacterController it uses
  • MovementSaver automatically saves the movement data
  • Rigidbody and CapsuleCollider which are used by
    • GenericTriggerItem with Key PLY which triggers GenericTriggerAreas in the environment(for example the traps in the dungeon)
    • CharacterActionArea which is used by the character to detect and start actions in the environment(doors, chests, …)

Pivot

This transform gets rotated by the movement to face the direction the player is moving.

Body

Contains the TriggerDamageReceiver that receives damage. It also has a second CapsuleCollider with IsTrigger unchecked which pushes around decorators like smashed crates. The components of the Body GameObject were originally located on the Model but have been moved into a separate GameObject to make model replacement easier.

For the sake of simplicity and performance the collision model for the player only consists of a simple capsule. If a more detailed model is required do the following:

  • Move TriggerDamageReceiver and Rigidbody to your Model they use any collider in the players model
  • Delete the Body GameObject
  • Change the Layer of Model to PlayerBody
  • Create detailed colliders in the Armature of the model, skip if your model already comes with colliders

Model

This is where the visuals of the player are located. It contains the Animator along with an AnimatorProxy that passes along events and root motion from the animator to the character.

Some other components of AdventureSouls depend on the players model and have to be reassigned if the model is replaced. The following steps show how the player model could be replaced by the zombie model that is usually used by the enemies.

  • Open up the SoulsPlayer prefab
  • Drag the Models/Characters/SoulsZombie into Pivot
  • Add an Animator and an AnimatorProxy to it
  • Assign the SoulsPlayer Controller and SoulsPlayerAvatar Avatar to the Animator
  • Assign the SoulsPlayer as the Character for AnimatorProxy
  • Completely expand the old and new models
  • the original armature will contain Left/RightHand transforms under its Hand.L/R transform, drag those over to the new models Hand.L/R(these are used to instantiate items by the weapon and usable item slots)
  • Delete the original Model
  • In the ArmorTop/Bottom item slots assign CharacterBody to the now missing Template field, it is needed so the armor moves properly with the character model
  • On the SoulsPlayerCharacter script reassign the missing Animator to the one you just created and set SoulsZombie as the Model that is replaced by the ragdoll
  • At the bottom of SoulsPlayerCharacter you can empty out the HeadRenderer field, it is used for the character customization sliders but the zombie model does not have the needed blend shapes

When you now open up SoulsDebuggingGeneral you should be able to move around and perform actions with the Zombie. If you start the game through the title you’ll see that things there are mostly unchanged but when you start a game the model is in fact changed.

The process to replace the model may be different depending on the model used but generally, as we have seen above, the following dependencies have to be satisfied.

  • animator and model on SoulsPlayerCharacter
  • hand transforms and body renderer for the item slots

Critical

The Critical GameObject holds the actions and triggers necessary for ripostes and backstabs.

The SoulsCriticalAction is the one performed by the player, it uses a GenericTriggerArea to find potential enemies that may be critted. When it is started it forces the enemy to perform the SoulsCrittedAction which disrupts anything it was doing. This action moves the enemy to the Front(riposte) or Back(backstab) transform in order for the animations to line up.

Actor

All the actions a player can perform on its own as well as the actor are found here.

  • Sit is not really in use currently but serves as an example of a gesture that is performed until some other input arrives
  • Turn is started by the movement when the character makes a sharp turn
  • Roll is started by the SoulsPlayerCharacter when dodge is pressed and the input direction is not neutral, in the Starting/Ending events in the inspector you can see that it activates a damage while it is active.(the damage has value 0 so it destroys boxes but does not hurt enemies)
  • Dodge is started by the SoulsPlayerCharacter when dodge is pressed and the input direction is neutral
  • Jump is directly bound to the Act.Jump input by SoulsPlayerCharacter
  • Stagger is started by the SoulsCharacterBase base class that the player has in common with the enemies when the character runs out of poise from being hit
  • GuardBreak is started by the SoulsCharacterBase when a character guards and runs out of stamina or it is parried while attacking
  • Death is started by SoulsPlayerCharacter when it gets hit and health runs out, notice the SignalReceiver which calls the ResetDeath method

Items

Holds items, item slots and inventory. You can add entries to the Items field on the ListedInventory to make the character start out with certain items which is quite useful for debugging.

  • Usable is a special item slot that is just a placeholder for the three slots it has as its children. This lets us bind input and UI to that single item slot. Which one is currently active can be switched through using the F key.
  • ArmorTop/Bottom are slots for SoulsArmorItem, they need a reference to the CharacterBody in order to copy the bones over to the armor they instantiate which makes the armor mesh move with the character
  • WeaponLeft/Right are slots for SoulsWeaponItem, they have a target transform inside the character models armature which they parent their meshes to, the weapons then move because they are children of the characters hand bone. Also notice that the weapon slots have fists as their fallback item, otherwise the character would have no actions to perform when no weapons are equipped

Metrics

Holds the resource and attribute pools. Attributes are defined directly in the pool while resources are separate scripts found in the children.

Souls Scenes


permalink: /manual/souls-scenes title: “Souls Scenes” sidebar: title: “Manual” nav: manual —

Title

The Title scene is where the player should first arrive, this is the case as long as it is at the first place in the build settings. It is where the player can create new games or load existing ones.

  • Title
    UI for the different save slots that are used to create new games and load them
  • NewGame
    Dialog that is used to customize and name new characters
    the model and camera that are used for the render texture in the UI are its children
  • Commons
    Scripts that are common to every scene like persistence and the SoulsCommons singleton
  • Background
    Holds all the 3d models in the background which are purely cosmetic

Loading

This scene gets loaded when a player loads a save from the title or when using a teleport action. The SoulsLoading script starts loading the target main scene asynchronously. After that it can be configured to wait for player confirmation by setting a UIDocument or to wait for a playable director if that is set. When those are done it additively loads the Temp scene and activates the loaded Main scene.

In the demo loading is always done pretty much immediately because it uses very little assets. The scene is meant as an example for games that grow large enough to warrant it. If you don’t need it simply use SwitchScene instead of LoadScene from the title screen and empty the loading scene parameter in teleport actions.

As mentioned above the actual levels in the demo are split into a main and a temp scene. This is done to simplify the reset process when the player sits down at a bonfire which reloads the temp scene. Therefore the environment, bonfires, managers, lighting and things like that belong in main while enemies and destructibles should be placed in temp. It may make sense to put bigger assets from the temp scene into main too just so it is part of the loading process.

Dungeon

The dungeon stage, in classic games fashion, starts the player off in a prison cell. After freeing themselves by using a key that has dropped into the cell in the intro they collect some basic equipment while going through the stage and finally defeat a boss which unlocks a teleport to the next stage.

The SoulsDungeon script on the Logic object is meant to hold any custom logic for this scene. It uses the persister assigned to it to check if the intro has been played already or if this is the first time the player enters the dungeon. If so it plays the intro and sets the players reset point to the cell.

The intro is a simple TimelineAction which suspends player control and the HUD using character instructions.

Lower

First off the player can pick up the key that dropped into the cell in the intro. The pickup has a PickupAction which can be found by the players CharacterActionArea because of the trigger collider on the same object. The model and particles of the pickup are disabled in the actions started event. The item the player gets when interacting is set in the actions Items field.

The door that the key unlocks has an ObjectAction which has the key set in its Cost field. The action has trigger names set for the character(OpenDoor) and the object(Open) which are set on the respective animators when the action is started. It also has the name of the state(Opened) after the action so it can restore it if the game is reloaded.

In the following corridor the player encounters the first enemies. These will not attack on their own because the trigger area which would aggro them has been disabled and unset on the character. They will still attack when hit however but otherwise they just remain in their idle action.

The ladder at the end of the corridor uses the special SoulsLadderAction which responds to up and down input to move the player up or down the ladder. To know if the character started the action from the bottom or top there are SoulsLadderEnterAction at both ends that lead into the ladder action. The actual movement on the ladder is actually done by the root motion of the characters animation. In addition to hiding the weapons and suspending movement the ladder action also has a SuspendMovementCollision instruction which prevents the character from colliding with the ladder during the action.

Courtyard

At the top of the stairs the player finds a chest which works very similarly to a door. The only differences are that this chest does not have a cost and activates its content, which contains a pickup, when used. The LeatherBottom inside the chest can be equipped to increase defense(reduces damage) and poise(harder to stagger). It does this because these stats have been set on the SoulsArmorItem which acts as an IStatModifier.

Another pickup in the courtyard contains the SoulsFlask item which restores the health resource when used and is refilled when the player rests at a bonfire.

The bonfire in the center of the yard contains a very special action that uses three different playables to enter, sit at and leave the bonfire. The binding of director to the characters animator is done at the start of the action so that the object does not need a direct link to the player. When the enter playable is done the bonfire resets the world and player and shows the level up menu.

The crates on the yard have a DestructibleDamageReceiver which destroy the object when it takes damage and replaces it with the destroyed prefab. When the crate is destroyed the DestructionPersister persists that and when the scene is loaded again after quitting the game it immediately destroys the object to restore the previous state. This is different from the player sitting at a bonfire which clears the persistence area that contains the crate and reloads the scene which makes the crate show up again.

Finally there are two doors at the end of the yard. These can only be opened from one side simply because the trigger collider of the action is on one side of the door.

Hallways

The end of the western hallway contains several traps which periodically spawn a projectile that contains a damage sender that deals physical and poise damage. If the player were to simply walk into the hallway these would damage them and stop their movement due to the stagger caused by the poise damage. The shield found in the chest has a guard action which, while active, nullifies physical damage and turns poise damage into stamina damage.

The lever in the northern hallways, just like doors and chests, uses an ObjectAction. The only difference is that it incorporates an additional animator(Gate) which gets triggered at the end of the action.

The western hallways contain two more zombies which do have their trigger areas set up and will attack once the player gets too close. One detail to note here is that these two carry sword despite enemies not really having an inventory. The swords have just been dragged onto their hands in the editor. The layers of the weapon has also been changed to EnemyBody/EnemyDamage. The light or heavy attack action has then been set on the enemy character to make it use that action when attacking.

Boss Arena

After using the lever located in the northern hallways the gate has dropped and the boss arena is accessible.

There is a box collider located a bit into the area that start the fight by calling StartFight on the SoulsBossArena script which does a couple things.

  • Persistence
    First off it sets the player movements position to outside the arena and suspends persistence completely. This makes it so that quitting and reloading the game during the fight resets the player to outside the arena
  • Boss Character
    Sets the target to the player and starts the configured action
  • UI
    Shows the big boss HP bar
  • Fog
    Activates the fog at the gate so the player can’t leave during the fight

The boss character itself has a couple attacks which are all defined by timelines. Which ones it uses depends on the players location and each one uses a certain amount of energy. When the energy is used up the boss uses the wait action which gives the player a chance to deal damage.

When the boss is defeated the boss area resumes persistence and sets a flag so the state can be restored later. It also activates the teleporter that can be used to move to the shrine stage.

Shrine

Bottom

There are two friendly NPCs at the bottom of the shrine stage. What each one does is defined in the action in its Interaction object.

SoulsTalker has a SoulsTalkAction which will display the configured lines of dialog in the general message box when used.

SoulsTrader has a SoulsTradeAction that opens the trading dialog that lets the player exchange experience for the items configured there. This action uses a different persister than the main character which has the permanent persistence area set. Therefore when the player sits at a bonfire the NPC will reset but items that have been bought will still be missing.

The trader sells moss which can be used to reduce the poison resource and boosters which double the characters strength.

Levels

On the first level the player encounters a couple sprout enemies. These will approach the player and attack by activating a spherical damage sender that deals poisoning damage. Just like fists or swords this attack still uses a SoulsAttackAction but instead of setting a weapon that deals the damage the activation of the damage and particles is done by creating events for the DMG_ON and DMG_OFF message in the inspector. Another difference from enemies previously encountered is that these have GoHome as their idle action which will make them return to their home if they loose aggro. When they have arrived at their home point they will continue with the action configured in Next so this is where idle poses and such can go.

The second level holds a single zombie enemy which has a PatrolAction as its idle. This action makes it move between the configured transforms. Which one it is currently moving towards is persisted so it will keep its current path even when the game is reloaded.

Top

The top of the shrine stage lets the player activate an elevator to the bottom by walking on a pressure plate.

The main script at work here is AnimationToggler which is used to toggle an animator between two states and persist which one it is at. The current position of the elevator is persisted to Shrine_Elevator_Down.

When the elevator starts moving it activates a CharacterCarrierArea which reparents the player to itself and suspends its movement persistence. It also writes to Shrine_Elevator_Enabled which deactivates the NPCs at the bottom and activates the ones on the top on the next reload.

The NPCs at the Top use the same persistence key as the ones at the bottom so their values will carry over. The only exception is movement which uses a different persister on the ones at the top so the position does no carry over.

Finally the top also contains a teleported that leads back to the dungeon stage. This teleporter has TeleportTarget set so the player will be moved to that transform in the dungeon stage.