Search
I have been experimenting with game development for the past a year and a half. This blog is used to post my game development and any kind of project I am working on.
Featured
- Get link
- X
- Other Apps
Creating RPG hack & slash controller combat system in Unity | Game development journey
This is how the whole project works by the way |
First of all, I’ll assume you already got the basic, and familiar with Unity. If you're here to learn, note that you don't have to perfectly follow the things I did, I won't cover every part of it, and don't be perfectionist for this time. I created this about 8 months ago, so I might not be able to explain it clearly.
SETTING UP THE BASIC CONTROLLER
Third person camera
I watched Filmstorm's tutorial for this (I knew him from MixAndJam, and his tutorial are complete) Just watch it and you'll know what to do with the camera. that's all for the camera controller.
Basic movement
Animation Controller |
For the movement, I look through some of the JRPG game, and they pretty much got walk, jump, and dash, for their main movement.
For locomotor movement, I used the standard asset third person controller, then I just straightly retarget the animator. The standard asset's TPC has the solid dynamic movement transition, and most rotation mechanism they did in the script is what I need the most (if you have dealt with quaternion, you'll know what I mean). I also did some changes though, like disabling the crouch, and adjusting the animation controller. For disabling crouch, I believe you could do that yourself. As for adjusting the animator controller, I basically simplify the ground blend tree animation, and change all the animation with the animation I have selected.
Simplified Animator Controller |
Original Standard asset animator controller |
If you're curious how blend tree work, I suggest you watch this video first 2 dimensional blend trees (iHeartGameDev), it's an awesome way to make dynamic animation transition. The key for this blend tree to work smoothly is by using root motion, and disabling the bake pose, explanation incase you need it Root Motion Explained (Roundbeargames) or maybe Should You Use Root Motion?. I'm using this animation package Oriental Sword Animation Asset Package, it was free for a limited time, and I managed to get some. After dealing with these both, you should have a functional locomotor movement.
For jump, I want to be able to adjust the jump height, but the Oriental Sword jump animation is not separated. I had to separate each part from the start of jump, when floating in the air, and when landing (Just copy and paste the animation keys from the original jump animation). When jumping, the root motion should be disabled, and the jump should be fully controlled through script.
Animation when start jumping |
Animation when floating |
Jump sub-state machine |
If you're wondering why I have 2 floating animation, I tried to vary the jump by having a spin float and normal float.
One of the problem when doing this is that, Oriental Sword jump animation's root is not fixed, and because the root motion is disabled when jumping, the body will jump higher than the actual game object. To fix this, I took from start jump animation root animation key, and replace it on the floating animation. Incase you're having problem to play with the animation transition read this https://docs.unity3d.com/Manual/class-Transition.html
Dash |
A new straight line that makes a right triangle |
Formula to calculate the distance |
public void Dash(float DashLength, float DashTimer, Vector3 Dir)
{
float OriginalLength = DashLength;
float CenterLength = CalculateCenterLength();
if (DashLength > CenterLength && Cast)
{
DashLength = CenterLength - 0.5f;
CenterLength = CenterLength < 0f? 0.1f: CenterLength;
}
DashTimer *= DashLength/OriginalLength;
m_DashTimerRef = DashTimer;
transform.DOMove(transform.position + (Dir * DashLength), DashTimer);
}
This is an additional utility function to calculate center length easier. The function is a bit confusing, but it works.
// If you found it confusing, so I am. Don't worry, you can create your own func for this
float PhytagorasFunc(float x, float y, float r){
if (r == 0) return Mathf.Sqrt(Mathf.Pow(x, 2) + Mathf.Pow(y, 2));
if (x == 0) return Mathf.Sqrt(Mathf.Pow(r, 2) - Mathf.Pow(y, 2));
if (y == 0) return Mathf.Sqrt(Mathf.Pow(r, 2) - Mathf.Pow(x, 2));
return 0;
}
Code for calculating the center length
float CalculateCenterLength()
{
// I'm from 8 months in the future, and I don't why this code works lol
Vector3 ForwardLine = transform.position + transform.forward * m_CastMaxDistance;
Vector3 A = transform.position + transform.forward * 0.5f;
Vector3 B = ForwardLine + transform.forward * 0.5f;
Vector3 C = Vector3.Scale(hit.point, new Vector3(1, 0, 1)) + transform.forward * 0.5f; // y is not used, so it's okay
float AC = PhytagorasFunc((C - A).x, (C - A).z, 0);
float CB = Mathf.Abs((C.x - A.x) * (-B.z + A.z) + (C.z - A.z) * (B.x - A.x)) / PhytagorasFunc(-B.z + A.z, B.x - A.x, 0);
return PhytagorasFunc(0, CB, AC);
}
Again... This is how I made this whole part |
Combat system
Again, read this first https://docs.unity3d.com/Manual/class-Transition.html if you're not familiar with animation transition, we'll be working with transition a lot in here.
After observing some JRPG games, I can conclude that mostly combat systems need these things.
- a chain of combo attack
- slight dash every attack
- sphere cast to detect enemy
- collision to damage enemy
Combo System
At first, I thought attaching event on the animation, and play the attack using trigger parameter, are the most reasonable things to do, but it doesn't work for me. I tried many approaches to make this work, logically it should works perfectly, but it always end up looks unnatural, and unextendable also spaghettiful. So in the end I use 3D Game Kit as a reference, and I found out that the key is in state time.Combat System Paper |
void Update()
{
if (m_Attack)
{
// If in transition, there would be 2 animation overlapping
// State time should be the second animation state time when transition, or else
// it'll consider the second animation had been running for a while
if (!m_Animator.IsInTransition(0))
m_Animator.SetFloat(
"State Time",
Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f));
else m_Animator.SetFloat(
"State Time",
Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(1).normalizedTime, 1f));
if (Input.GetMouseButtonDown(0) && m_ThirdPersonCharacter.m_IsGrounded && !m_Controller.m_isDash){
m_Animator.SetTrigger("Meele Combat");
}
// This script should be attach to the animation, not game object!
public class CombatRunTimeManager : StateMachineBehaviour
{
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// When the current animation is transitioning to the basic locomotor animation (grounded)
if (animator.GetAnimatorTransitionInfo(0).IsName(animator.GetNextAnimatorClipInfo(0)[0].clip.name + " -> Grounded")) animator.ResetTrigger("Meele Combat");
}
}
2. Attack animation took too long to end before I can move again
To fix this, I created two separate transition that goes to the same animation which is basic locomotor (grounded). You can see in these pictures below on the right top side there's two transition that led to the same animation, one is the one that has conditions to move and has no exit time, and the other is just a normal transition that has exit time.
With condition but no exit time |
No condition but with exit time |
if (move != Vector3.zero) m_Animator.SetBool("Skip Close", true);
else m_Animator.SetBool("Skip Close", false);
Using this method, whenever you're not walking after you stop combo, your attack animation will play until the end, but if you try to walk, the animation will transition to the locomotor animation halfway.
If you're having a trouble trying to calls a function or run a code if the attack is running, try this code:
void CheckCombat()
{
if (m_Animator.GetCurrentAnimatorStateInfo(0).IsTag("Combat"))
{
// is attacking
}
else {
is not attacking
}
}
Dash attack
It would enhance the feeling if the combo attack got a slight dash, so I took the previous Dash function, and calls it every time attack animation is running. What's cool about this code below is that, it is adjustable, so you could have a normal attack that only has a slight dash, and you could also have a dash attack (singleattack06 in the image) seperately.
Combat sub-state machine
// This code is ugly, but it works(again...), try to write it your own if you have a better code!
public class CombatRunTimeManager : StateMachineBehaviour
{
CombatManager m_CombatManager;
public float DashSpeed;
public float DashTimer;
public float DashStart;
public bool Dash;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
m_CombatManager = animator.GetComponent();
Dash = false;
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
float animationLength = Mathf.Repeat(animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f);
if (animationLength > DashStart && !Dash)
{
m_CombatManager.Dash(DashSpeed, DashTimer, Vector3.Scale(animator.transform.forward, new Vector3(1, 0, 1)).normalized);
Dash = true;
}
}
}
Detecting enemies
Collider[] Enemyhit = Physics.OverlapSphere(transform.position, m_EnemyCastDistance, EnemyLayerMask);
if (Enemyhit.Length > 0){
foreach(Collider hit in Enemyhit) {
if (shortestEnemyHit == null) shortestEnemyHit = hit;
float shortestLength = (shortestEnemyHit.transform.position - transform.position).magnitude;
float currentLength = (hit.transform.position - transform.position).magnitude;
if (shortestLength > currentLength) shortestEnemyHit = hit;
}
m_Target = shortestEnemyHit.transform;
Debug.DrawLine(transform.position, shortestEnemyHit.transform.position, Color.cyan);
}else {
shortestEnemyHit = null;
m_Target = null;
}
Take it or leave it.
Attacking enemies
By far the simplest part that I made. You should use interface for a tidy code. Clearly there are other better approaches to attack the enemy, but for me, this should work for now.
public class Weapon : MonoBehaviour
{
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Enemy")
{
IDamageable damageable = col.gameObject.GetComponent>();
damageable.Damage(m_WeaponDamage);
}
}
}
FINISHING
Adding VFX slash
I'm obsessed with VFX slash in JRPG game, and I want to make it as well. I've watch several tutorials to make VFX slash, but most of them are not the one I'm looking for. Some of them need Amplify shader editor to make, and I haven't bought it. But fortunately I manage to find one tutorial that is quite easy and actually the one I'm looking for, thanks to this man Hovl Studio VFX slash. I followed his tutorial, and after doing a bit of adjustment, here's the result:
VFX slash |
Shake effect
Must have if you want more feeling on your slash. Since I used cinemachine to make the tpc, therefore I could just use cinemachine impulse to shake the camera.
[SerializeField] CinemachineImpulseSource Impulse;
public void Shake()
{
Impulse.GenerateImpulse();
}
Since I'm using OnTriggerEnter to attack enemy, I could simply add a few line of code to make the camera shake every time it hits enemy or obstacle.
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "Obstacle")
{
if (m_Animator.GetCurrentAnimatorStateInfo(0).IsTag("Combat") &&
(Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f) > 0.2f ||
Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f) < 0.8f ))
{
m_CombatManager.Shake();
}
}
if (col.gameObject.tag == "Enemy")
{
if (m_Animator.GetCurrentAnimatorStateInfo(0).IsTag("Combat") &&
(Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f) > 0.2f ||
Mathf.Repeat(m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime, 1f) < 0.8f ))
{
IDamageable damageable = col.gameObject.GetComponent>();
damageable.Damage(m_WeaponDamage);
m_CombatManager.Shake();
}
}
}
That's pretty much I could explain for this project. In the end, many of the part I worked on, are from either tutorial or asset, and I believe it's perfectly fine for developer to watch tutorial for most of their time or use asset on their game, as long as it's not considered illegal.
Make sure post your comment below if you have any question or just want to discuss something, I'll gladly answer it!
adventurer developer youtube, subscribe to it incase you want to follow my development journey, I won't be posting frequently, but your subscribe will means a lot to me!
Thanks for reading.
- Get link
- X
- Other Apps
Popular Posts
Dynamic First Person Controller Mobile Kit Asset - First Release
- Get link
- X
- Other Apps
Comments
Post a Comment