Hit objects are the core gameplay elements that players interact with. They represent timing points, positions, and patterns in a beatmap that require player input or attention.Every hit object consists of two parts:
Data representation - The HitObject class containing timing and properties
Visual representation - The DrawableHitObject class for rendering and interaction
using osu.Game.Rulesets.Judgements;using osu.Game.Rulesets.Objects;using osu.Game.Rulesets.Objects.Types;using osuTK;namespace osu.Game.Rulesets.MyRuleset.Objects{ public class MyHitObject : HitObject, IHasPosition { // Position property from IHasPosition public Vector2 Position { get; set; } public float X { get => Position.X; set => Position = new Vector2(value, Y); } public float Y { get => Position.Y; set => Position = new Vector2(X, value); } // Define how this object is judged public override Judgement CreateJudgement() => new Judgement(); // Optionally customize hit windows protected override HitWindows CreateHitWindows() => new HitWindows(); }}
public class MyLongNote : HitObject, IHasPosition, IHasDuration{ public Vector2 Position { get; set; } public double EndTime { get => StartTime + Duration; set => Duration = value - StartTime; } public double Duration { get; set; } // X, Y properties...}
using osu.Game.Rulesets.Judgements;using osu.Game.Rulesets.Scoring;public class MyJudgement : Judgement{ // Define the maximum result this object can achieve public override HitResult MaxResult => HitResult.Perfect; // Optional: Override health increase/decrease protected override double HealthIncreaseFor(HitResult result) { switch (result) { case HitResult.Perfect: return 0.01; case HitResult.Miss: return -0.05; default: return 0; } }}public class MyHitObject : HitObject{ public override Judgement CreateJudgement() => new MyJudgement();}
public enum HitResult{ None, // No result Miss, // Missed completely Meh, // 50 in osu!standard Ok, // 100 in osu!standard Good, // 200 in osu!mania Great, // 300 in osu!standard Perfect, // Rainbow 300 in osu!mania SmallBonus, // Small bonus score (spinner ticks) LargeBonus, // Large bonus score (spinner bonus) IgnoreHit, // Hit but not judged IgnoreMiss, // Miss but not judged // ... more specialized types}
Use HitResult.Perfect as the maximum for most objects. Reserve HitResult.Great for when you have both “perfect” and “great” timing windows.
using osu.Game.Rulesets.Scoring;public class MyHitWindows : HitWindows{ // Base timing windows at OD 5 private static readonly DifficultyRange[] my_ranges = { new DifficultyRange(HitResult.Perfect, 40, 30, 20), new DifficultyRange(HitResult.Great, 80, 60, 40), new DifficultyRange(HitResult.Ok, 120, 90, 60), new DifficultyRange(HitResult.Meh, 160, 120, 80), new DifficultyRange(HitResult.Miss, 200, 150, 100), }; public override bool IsHitResultAllowed(HitResult result) { switch (result) { case HitResult.Perfect: case HitResult.Great: case HitResult.Ok: case HitResult.Meh: case HitResult.Miss: return true; default: return false; } } protected override DifficultyRange[] GetRanges() => my_ranges;}
For performance, register hit object pools in your playfield:
public partial class MyPlayfield : Playfield{ protected override void LoadComplete() { base.LoadComplete(); // Register pools for each hit object type RegisterPool<MyHitObject, DrawableMyHitObject>(50); RegisterPool<MyLongNote, DrawableMyLongNote>(20); }}
Pool sizes should accommodate the maximum number of visible objects. Too small = allocations during gameplay. Too large = wasted memory.