Custom Hit Object Collecting Rules
You can add your own rules on whether the player is allowed to collect a Hit Object or not after clicking on it, or changing what sound and visual effects are used upon collecting a Hit Object based on some rule.
using System.Collections.Generic;
using MouseDance.Runtime;
using UnityEngine;
public class BasicUsage : UnityEngine.MonoBehaviour, Callbacks.IBeatmapInput, Callbacks.ICustomCollectRule
{
public BeatmapRunner _beatmapRunner;
public UnityEngine.TextAsset _osuFile;
public UnityEngine.AudioClip _song;
void Start()
{
_beatmapRunner.SetInput(this);
_beatmapRunner.SetCustomCollectRule(this);
_beatmapRunner.LoadAndStart(_osuFile, _song);
}
// Note: We're using the old/legacy Input System in this example,
// but you can change this to whatever you want.
//
// Allow either left or right click.
public bool IsPlayerInputDown => Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1);
public bool IsPlayerInputUp => Input.GetMouseButtonUp(0) || Input.GetMouseButtonUp(1);
public Vector2 CursorPosition => Input.mousePosition;
public HitObjectReaction AllowCollect(IHitObject hitObject, IReadOnlyList<IHitObject> allPressed)
{
// This struct will be our return value.
// By default, the values in it will permit the Hit Object to be clicked on,
// but we can disallow it instead, based on conditions.
var result = HitObjectReaction.Default;
// For example, we can make it that even-numbered Hit Objects
// require to be right-clicked (not left-clicked)
if ((hitObject.ComboCounter % 2) == 0)
{
// even-numbered, require right-click
if (Input.GetMouseButtonDown(1))
{
// ReactType.Proceed will permit the Hit Object to be clicked on,
// but that is not a guarantee that it will be collected,
// since the player is still subject to the usual rules
// (needing to click within the proper time window,
// needing to click on Hit Objects in the proper order).
// It only means we're allowing it to proceed.
//
// This is already the default value of our ReactType,
// we're just assigning it here for
// illustrative purposes.
result.ReactType = ReactType.Proceed;
// we can also tell MouseDance to use a different sound and visual effect
result.EffectsId = "EvenHit";
}
else // player didn't right-click
{
// ReactType.Shake will prevent the Hit Object from being collected,
// even if the player clicked within the proper time window.
// It will also do a shaking animation.
result.ReactType = ReactType.Shake;
// we'll also use a different sound and visual effect on miss
result.EffectsId = "EvenMiss";
}
}
else
{
// odd-numbered, require left-click
if (Input.GetMouseButtonDown(0))
{
result.ReactType = ReactType.Proceed;
}
else // player didn't left-click
{
result.ReactType = ReactType.Shake;
}
}
return result;
}
public bool AllowCollectSliderPart(IHitObject slider)
{
if (Input.GetMouseButton(0) && Input.GetMouseButton(1))
{
// player is holding both left and right mouse button, disallow this
return false;
}
if ((slider.ComboCounter % 2) == 0)
{
// even-numbered, require right mouse button held
return Input.GetMouseButton(1);
}
else
{
// odd-numbered, require left mouse button held
return Input.GetMouseButton(0);
}
}
}
This example will require even-numbered Hit Objects to be right-clicked, and odd-numbered Hit Objects to be left-clicked.
Caution
Note that to make this work properly, we implement two interfaces: IBeatmapInput
(to allow both left and right click in the controls) and ICustomCollectRule
(to change how Hit Objects are collected, based on the input).
We also correspondingly need to call SetInput
and SetCustomCollectRule
from the BeatmapRunner, so our methods will be called.
The AllowCollect
method is used for adding custom rules on whether the player is allowed to collect the Hit Object they've clicked on.
The HitObjectReaction.ReactType in the return value changes how the game reacts to the clicked Hit Object:
React Type | Effect |
---|---|
None | Prevent Hit Object from being collected. No sound/visual effects will be played. The Hit Object will still be there (and the player can retry). |
Shake | Prevent Hit Object from being collected, and make it do a shaking animation (with accompanying visual and sound effect). The Hit Object will still be there (and the player can retry). |
FadeOut | Prevent Hit Object from being collected, and fade it out (with an accompanying sound effect). This will count as a miss. Since the Hit Object will disappear, the player will no longer be able to retry collecting the Hit Object. |
Proceed | Allow the Hit Object to proceed as usual. This is not a guarantee that the Hit Object is collected, since that is still subject to the usual rules of needing to press within the timing window, and needing to click on the Hit Objects in the proper order. |
If you don't need to add custom rules, just return HitObjectReaction.Default
.
The AllowCollect
method works on top of the usual rules that are already being followed (should be pressing Hit Objects in correct order, should press within the time window, etc.) so there is no need to repeat those basic rules in your AllowCollect
method.
AllowCollect
is called during the same frame that IsPlayerInputDown
returns true, provided that the code did find Hit Objects under the mouse cursor upon clicking.
The allPressed List
Furthermore, AllowCollect
is called for each Hit Object under the mouse, in case the player clicked on overlapping Hit Objects. You can check the allPressed
argument for the list of all Hit Objects pressed. This list is sorted by z-order, with the topmost Hit Object coming in first. That means you can check if the Hit Object currently being checked is the topmost, by doing hitObject == allPressed[0]
. That also means if the player didn't click on overlapping Hit Objects, then the allPressed
list will really have just one Hit Object inside it.
Collecting Slider Parts
The AllowCollectSliderPart
method is used when the player has their input down on a Slider and is about to collect a Slider Tick or Tail/Head. You can return false to prevent it from happening.
Custom Hit/Miss Visual and Sound Effect
The EffectsId
in the return value can be used to make the game use a custom visual and sound effect.
If you check the Hit Effects Set and Sound Effect Set, they allow specifying a custom visual effect and sound effect respectively. The ID assigned from there can be used for the EffectsId
.
So when you change the EffectsId
to a value like "EvenHit"
, it will look for that ID in both the Custom Visual Effect and Custom Sound Effect
result.EffectsId = "EvenHit";