-
-
Notifications
You must be signed in to change notification settings - Fork 31
For Developers
Here is where you'll find tutorials and information about various development-related things for Counters+. Whether you just want to get the project set up, or you want to expand upon the project, this page should have the information you need.
Because this page might be long, here are some quick links to the various ways you can contribute to Counters+.
- Heck Integration
- Setting Up Your Project
- Custom Counter System
- Adding New Options to Existing Counters
As of Counters+ 2.2.3
, there is now an optional integration with Heck, the mod that powers Noodle Extensions and Chroma. Counters+ can now utilize its Settings Setter feature to allow mappers to control Counters+ on a map-per-map basis.
Unfortunately, Counters+ has way too many settings to document them all here. We even generate them dynamically; the list will change with every addition and removal from Counters+.
Category ID: _countersPlus
Setting Names: _<counterName><PropertyName>
{
"_version": "2.0.0",
"_songName": "NULCTRL MEISO FLIP",
// ...
"_difficultyBeatmapSets": [
{
"_beatmapCharacteristicName": "Standard",
"_difficultyBeatmaps": [
{
"_difficulty": "ExpertPlus",
"_difficultyRank": 9,
"_beatmapFilename": "ExpertPlusStandard.dat",
"_noteJumpMovementSpeed": 19,
"_customData": {
"_settings": {
"_countersPlus":{
// Main Settings
"_mainEnabled" : true,
"_mainHideCombo" : true,
"_mainHideMultiplier" : true,
"_mainParentedToBaseGameHUD" : false,
"_mainSize" : 10,
"_mainPositionScale" : 3,
"_mainPosX" : 0,
"_mainPosY" : 0,
"_mainPosZ" : 2,
"_mainRotX" : 45,
"_mainRotY" : 0,
"_mainRotZ" : 0,
"_mainCurveRadius" : 15,
"_mainDistanceModifier" : 1,
// Missed settings
"_missedEnabled" : true,
"_missedPosition" : "AboveCombo",
"_missedDistance" : -1,
"_missedCountBadCuts" : true,
"_missedCanvasID" : -1,
// Progress settings
"_progressEnabled" : true,
"_progressPosition" : "AboveMultiplier",
"_progressDistance" : -1,
"_progressMode" : "Original",
"_progressCanvasID" : -1,
// Score settings
"_scoreEnabled" : true,
"_scorePosition" : "AboveHighway",
"_scoreDistance" : -1,
"_scoreMode" : "Original",
"_scoreDecimalPrecision" : 4,
"_scoreCustomRankColors" : true,
"_scoreSSColor" : "#FFFFFF",
"_scoreSColor" : "#FFFF00",
"_scoreAColor" : "#FF00FF",
"_scoreBColor" : "#FF0000",
"_scoreCColor" : "#00FFFF",
"_scoreDColor" : "#00FF00",
"_scoreEColor" : "#0000FF",
"_scoreCanvasID" : -1,
// PB settings
"_personalBestEnabled" : true,
"_personalBestCanvasID" : -1,
"_personalBestUnderScore" : true,
"_personalBestDecimalPrecision" : 4,
// We don't want additional counters, disable the rest
"_speedEnabled" : false,
"_cutEnabled" : false,
"_spinometerEnabled" : false,
"_notesLeftEnabled" : false,
"_failEnabled" : false
}
}
}
}
]
}
]
}
After forking or cloning the Counters+ source, there are a few extra steps you need to accomplish to get the plugin building.
Counters+ was made using Zinga's Beat Saber Modding Tools. It is highly recommended you install this plugin, as it makes mod development much easier.
To set the project up, simply right click the Counters+
project in the Solution Explorer, and navigate to Beat Saber Modding Tools >>> Set Beat Saber Directory...
. Click that, and the references should automatically be set up for you.
If that does not work for whatever reason, right click the References
dropdown underneath the Counters+ project in the Solution Explorer, then click Beat Saber Reference Manager
. Simply uncheck a reference, then hit Apply
. After that, re-check that reference, then click Apply
again.
Have you ever wanted to add a counter of your own to Counters+, but you want it to also be its own standalone mod? Counters+ has a solution that allows you to easily implement any Counter of choice into the Counters+ system.
Developers who create UI Enhancement mods should look into Counters+ integration, as users may have setups that will collide with your mod.
While the Custom Counter system will always be designed such that a Counters+ dependency is optional, it will be easier on the side of the developer to have a hard Counters+ dependency. With a hard dependency in mind, you do not have to worry about maintaining two separate parts of code; one for if Counters+ is installed, and one where it's not.
To get started, go to your plugin's manifest.json
file.
Counters+ 2.0.0's Custom Counter system uses the new BSIPA Feature's system, introduced in BSIPA versions after 4.0.5
. If needed, you can grab the latest BSIPA builds here.
Here is an example Custom Counter defined in manifest.json
:
"features": {
"CountersPlus.CustomCounter": {
"Name": "Example Custom Counter",
"CounterLocation": "ExampleCustomCounterMod.CustomCounter",
"MultiplayerReady": true,
"ConfigDefaults": {
"Enabled": true,
"Position": "AboveCombo",
"Distance": 0
},
"BSML": {
"Resource": "ExampleCustomCounterMod.TestCustomUI.bsml",
"Host": "ExampleCustomCounterMod.TestCustomUIHost",
"Icon": "ExampleCustomCounterMod.william_gay.png"
}
}
}
If not defined already, add a features
object to the manifest. Unlike previously, this new Features system uses an object, not an array. After this, you want to create a new child object under this, called CountersPlus.CustomCounter
.
The name of your Custom Counter. This displays in the Counters+ Settings Menu, as well as being used as an identifier for various internal components. Be warned! You should come up with a good name, because I do not recommend changing it after it's first public release!
A description for your Custom Counter. Currently unused.
Introduced in Counters+ 2.3.0, this is an opt-in flag which allows Custom Counters to load in Multiplayer.
By default, all Custom Counters are disabled in Multiplayer. This is to preserve compatibility with older Custom Counters, which might not play nicely when thrown into Multiplayer. When ready, developers can enable their Custom Counters in Multiplayer by setting this flag to true
.
The namespace location to the Custom Counter you want to implement. This can be a MonoBehaviour
, a class that inherits a Counters+ Custom Counter type, or neither of those.
DO NOT WORRY ABOUT CREATING AN INSTANCE! Counters+ uses Zenject to new up an instance of your Custom Counter when entering a song. This also means you can take full advantage of Zenject in your Custom Counters!
If you wish to provide your own defaults on where the counter will go, override this object. This is a ConfigModel
, and is comprised of 3 parts:
- Enabled which controls, well, whether or not it's enabled in the first place.
-
Position is an Enum of string values that act as a "relative" point to go off of. Valid options are:
-
AboveCombo
andBelowCombo
-
AboveMultiplier
andBelowMultiplier
BelowEnergy
AboveHighway
-
- Distance is steps taken away from these relative points to determine the final position.
This is an optional object which describes behavior in the Counters+ Settings Menu. You can skip this object if you do not plan on offering config options, or a custom icon.
This is a namespace location to a .bsml
file, which will be appended underneath the ConfigModel
options.
For this BSML file, it is recommended to base your UI off of this template to keep consistent with Counters+:
<vertical xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:schemaLocation='https://monkeymanboy.github.io/BSML-Docs/ https://raw.githubusercontent.com/monkeymanboy/BSML-Docs/gh-pages/BSMLSchema.xsd' spacing='1' horizontal-fit='PreferredSize' vertical-fit='PreferredSize'>
<!-- BSML goes here -->
</vertical>
You may safely leave this blank; if so, no additional content (or host objects) will be appended.
This is a namespace location to a Type, which will handle all UIValue
s, UIAction
s, and UIEvent
s for your provided BSML file.
Like with CounterLocation
, Counters+ uses Zenject magic to automatically create this object when it is needed. You can also take full advantage of Zenject inside this object.
This is a namespace location to an Image, which will override the default Custom Counter icon in the list of all Counters.
I'm unsure of exactly all the supported file formats, but I'm 100% certain .png
works, and about 85% certain .jpg
works. I wouldn't test your luck with anything else.
If you already have a MonoBehaviour
that you wish to reuse, then sure, you can plug that into the CounterLocation
and Counters+ will gladly pick it up and load it in game. However, I really recommend that you create the Custom Counter from scratch to fully take advantage of all that Counters+ 2.0.0 has to offer.
Counters+ provides a few public classes, in the CountersPlus.Counters.Custom
namespace, which you can inherit to speed up your custom counting.
This is a barebones Custom Counter class that only provides you with a few utility objects (Injected with Zenject), as well as some methods to help get you started.
CanvasUtility
is a field inside this class that provides you with quick access to functions related to Counters+ canvases, as well as quick and easy text generation.
This is the CustomConfigModel
that is used for your Custom Counter. You can use this, combined with CanvasUtility
, to easily create custom text in just one line of code.
CounterInit is an abstract method (Meaning you have to override it) which is called when Counters+ loads the object. This is where you create your counter text, and subscribe to events.
CounterDestroy is an abstract method (Meaning you have to override it) which is called when the object is destroyed. This is where you clean up after yourself; unsubscribe from events, and destroy anything that might not automatically be picked up by Unity.
If you really want to reuse an existing Canvas, then you can let Counters+ reposition it for you with a few additional lines of code, and a Custom Counter.
This CanvasCustomCounter
class inherits BasicCustomCounter
, so helpers like the Settings
object and CanvasUtility
will also make its way over to CanvasCustomCounter
.
The CanvasCustomCounter
class is designed to look for a defined Canvas object, whether by name or by direct reference, and re-parent and reposition it into the Counters+ system.
This is a virtual property (Meaning overriding it is optional) which points to a direct Canvas
object. You should try using this first. Remember: The Custom Counter you will be using can take full advantage of Zenject. If your mod happens to use Zenject for its own components, you can easily inject your main Canvas into the Custom Counter type.
This is a virtual property (Meaning overriding it is optional) which gives the exact, case-sensitive name to a GameObject with an attached Canvas
component. This should be your Plan B. Counters+ will give your Custom Counter 10 shots to find this Canvas object before giving up.
This is a virtual method (Meaning overriding it is optional) that is triggered before Counters+ attempts to search for your Canvas. This is essentially the same as CounterInit
, in case you still need to do event subscribing.
This is a virtual method (Meaning overriding it is optional) that is triggered after Counters+ has successfully re-parented your Canvas into the Counters+ system. If there is any repositioning or manual work that still needs to be done, you can do so in the PostReparent
function.
But wait, there's more!
If you need to subscribe to basic game events, such as note cutting/missing and score updating, Counters+ has interfaces that expose these events for you, and will automatically trigger them without you needing to go out, find an object reference, and subscribing to these events yourself. One more thing, these interfaces are designed so that, in the case of a breaking game update, Counters+ will only break in one spot. This means that I would only have to fix code in one place, and these interfaces will be fully operational.
These helper interfaces are located in the CountersPlus.Counters.Interfaces
namespace.
This interface exposes basic note cut and missing events.
This interface exposes basic score updating, and max score updating events. Both methods only give out modified scores (As in, score when taking into account the user's modifier bonus).
If you feel that an existing Counters+ counter needs a new feature or option, then it might be worth forking Counters+ for yourself and looking into adding this yourself. This short section will cover the 3 main components you need to cover in order to add a new option to Counters+, complete with UI support.
We'll assume that you've already forked Counters+, got the project set up, and fixed any reference issues.
The first thing you should do when adding a new Counters+ option is to add it to an existing ConfigModel.
A ConfigModel
is an abstract class located in CountersPlus.ConfigModels
which holds configuration data for every counter in Counters+. Each Counter in Counters+ have an inherited ConfigModel class of their own, also located in the bottom of Config.cs
. Find the ConfigModel class of the Counter you wish to add an option to, and add it, along with the default value you wish to assign. Do not add to the ConfigModel
class itself, as you'll find that option for every single Counter in Counters+. Unless you want to.
The ConfigModel
objects are also used as hosts in BSML, so add any BSML-related attributes and information here too.
With your new Option in hand, go into the Counters
folder of the Counters+ solution and modify the counter class you want with that new option. All of the Counters have a Settings
variable, which you can easily use to access your new setting.
Now it's time to add your setting to the Counters+ Settings UI. For every counter, their BSML files are located in CountersPlus.UI.BSML.Config
.
You can simply copy and paste code from the other BSML files to add your option to the Counters+ menu.
Once you're done, shoot a pull request to the master branch, and I'll look over it and see if it's good enough to merge into master.