Beatborn

Unity / C#

Combat & Controller

Dynamic Combat System

In Beatborn we had a couple of tricky challenges ahead of us, we needed to create a intriguing combat system that would not distract to much from the rhythm gameplay but still keep the player engaged in the fighting. We went with a sort of hack and slash base for the combat, inspired by games such as Bayonetta and Dynasty warriors. We knew that we had a very talent animator on the team and therefor that wanted the animations to play a big part the combat, but the question was how.


Animation Based Approach

With this approach I created a combat system that was strictly based on the animations in them self, all combat moves were strictly root motion based and the collision detection was based on the swords trajectory in the animation, this was simply done by checking the delta movement between frames. This unfortunately created gameplay that was more similar to Dark souls, with hard to hit attacks and a pretty small windows of attack. In a game where you want fast action and a lot of enemies dying this does not really fit the bill, so we had go back and look to our inspirations.

In games such as dynasty warriors the animations where more a suggestion of what a attack was supposed to do more then a pixel perfect visualisation. Inspired by this I went with a more forgiving animated cone of attack for attack collision detection.
In this attack iteration I created a tool for the animator to set the rotation of a cone of damage based on time. By this method we still had damage that was timed to the animations and followed the sword animations but they were much more forgiving. The player could now keep more focus on the beat and the crushing of robots.

{ Attack Struct } C#

[Serializable]
public struct AttackStruct
{


    [Header("Animation"), Tooltip("This is the name of the animation state in the animator and the name of the trigger as well")]
    public string AttackTag;
    [Tooltip("This clip determines the time for a animation")]
    public AnimationClip Clip;
    public bool IgnoreThisData;
    [Header("Attack")]
    [Tooltip("These are the frames the attack does damage")]
    public Vector2Int attackWindow;
    public Vector2Int VfxWindow;
    public float InputBuffer;
    public int Damage;
    [Range(0, 10)] public float GravityFactor;

    [Range(-180f, 180f)] public float AngleOffset;
    [Range(0f, 360f)] public float Angle;

    public float CloseAttackRange;
    [Range(0f, 360f)] public float CloseAttackAngle;
    [Range(-180f, 180f)] public float CloseAttackAngleOffset;

    public float Radius;
    public InputButton Input;

    [Header("Movement")]
    [Tooltip("If rootmotion is applied player will take no input during that attack")]
    public bool RootMotion;
    public bool ShouldNotRotateInAttack;

    [Header("Feedback")]
    public VFXStruct HitParticle;
    public Material VFXSwingMaterial;

    [Header("Sound")]
    [EventRef] public string AttackSFX;
    [EventRef] public string HitSFX;

    public enum InputButton
    {
        Light, Heavy, Dash
    }
}
Animated attack-cone

Collision Detection

Because I wanted to have full control of the characters movement I opted for creating my own velocity based controller. This came with its own set of challenges though, mainly the collision management. The velocity had to be managed so that it would not make the character go through solid surfaces.
This is not hard to accomplished when the normal of the colliding surface is aligned in the opposed direction of the velocity. Then all you have to do is reflect the velocity and decrease the magnitude based on the reflective factor of the surface. As long as there is nothing behind the charter there will be no clipping. But as soon as you have a normal at an angle you will get problems where the projected velocity will still move your character inside of the colliders you are trying to avoid. My solution for defusing this was by repeatedly moving the character based on the magnitude of the vector until it is no longer inside a collider.
Later on when I worked with the root motion animations of the players attacks I could reuse this collision algorithm by calculate the delta movement from the animations and set the characters velocity to this.

                    public static List PreventCollision(Func raycastFunction, ref Vector3 velocity, Transform transform, float DeltaTime, float skinWidth = 0.0f, float bounciness = 0.0f)
                    {
                        RaycastHit hit;
                        List raycastHits = new List();
                        int saftyCounter = 0;
                        while ((hit = raycastFunction()).collider != null && saftyCounter++ < 100)
                        {
                            float distanceToCorrect = skinWidth / Vector3.Dot(velocity.normalized, hit.normal);
                            float distanceToMove = hit.distance + distanceToCorrect;

                            if (distanceToMove <= velocity.magnitude * DeltaTime)
                            {
                                raycastHits.Add(hit);
                                if (distanceToMove > 0.0f)
                                    transform.position += velocity.normalized * distanceToMove;

                                float dot = Vector3.Dot(velocity, hit.normal);
                                velocity += (-normal.normalized * (dot > 0.0f ? 0 : dot)) * (1.0f + bounciness);
                            }
                            else
                                break;
                        }
                        return raycastHits;
                    }