Basic fighting game.
Need kenney art could do a lot of other shit, combos, jumping, crouching. Attacking while jumping/crouuching Balance stuff Special moves
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class AttackAnimationState : StateMachineBehaviour
|
||||
{
|
||||
private enum HitBoxState : byte
|
||||
{
|
||||
Startup,
|
||||
Active,
|
||||
Recovery
|
||||
}
|
||||
|
||||
private float _startTime;
|
||||
private HitBox _hitBox;
|
||||
private HitBoxState _currentHitBoxState;
|
||||
|
||||
public float activeStartTime;
|
||||
public float activeEndTime;
|
||||
public AttackState attack;
|
||||
|
||||
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
|
||||
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
{
|
||||
_currentHitBoxState = HitBoxState.Startup;
|
||||
_startTime = Time.time;
|
||||
|
||||
var player = animator.GetComponent<Player>();
|
||||
switch (attack)
|
||||
{
|
||||
case AttackState.Idle:
|
||||
Debug.LogError("AttackAnimationState should not be associated to idle!");
|
||||
break;
|
||||
case AttackState.Light:
|
||||
_hitBox = player.lightHitBox;
|
||||
break;
|
||||
case AttackState.Medium:
|
||||
_hitBox = player.mediumHitBox;
|
||||
break;
|
||||
case AttackState.Heavy:
|
||||
_hitBox = player.heavyHitBox;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
|
||||
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
{
|
||||
if (Time.time >= activeStartTime + _startTime && _currentHitBoxState == HitBoxState.Startup)
|
||||
{
|
||||
_currentHitBoxState = HitBoxState.Active;
|
||||
_hitBox.DoesDealDamage = true;
|
||||
}
|
||||
|
||||
if (Time.time >= activeEndTime + _startTime && _currentHitBoxState == HitBoxState.Active)
|
||||
{
|
||||
_currentHitBoxState = HitBoxState.Recovery;
|
||||
_hitBox.DoesDealDamage = false;
|
||||
}
|
||||
|
||||
if (Time.time >= stateInfo.length + _startTime)
|
||||
{
|
||||
// we done!
|
||||
var player = animator.GetComponent<Player>();
|
||||
player.StopAttack();
|
||||
}
|
||||
}
|
||||
|
||||
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
|
||||
// public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
|
||||
// OnStateMove is called right after Animator.OnAnimatorMove()
|
||||
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
//{
|
||||
// // Implement code that processes and affects root motion
|
||||
//}
|
||||
|
||||
// OnStateIK is called right after Animator.OnAnimatorIK()
|
||||
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
|
||||
//{
|
||||
// // Implement code that sets up animation IK (inverse kinematics)
|
||||
//}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce794600e85f9c24c9bec9e94963c9da
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,33 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class EnemyLogic : InputBase
|
||||
{
|
||||
private Transform _transform;
|
||||
private float _currentDirection;
|
||||
|
||||
public Transform target;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var horizontalDistance = target.position.x - _transform.position.x;
|
||||
if (Mathf.Abs(horizontalDistance) > 1.8f)
|
||||
{
|
||||
_currentDirection = horizontalDistance / Mathf.Abs(horizontalDistance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentDirection = -horizontalDistance / Mathf.Abs(horizontalDistance);
|
||||
AttackCallback(AttackState.Heavy);
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetMovementDirection()
|
||||
{
|
||||
return _currentDirection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84c0ec2cd4edfde4aa36e2b68bac7b96
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(Image))]
|
||||
public class HealthBar : MonoBehaviour
|
||||
{
|
||||
private Image _healthBar;
|
||||
private Material _material;
|
||||
|
||||
public Player target;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_healthBar = GetComponent<Image>();
|
||||
_material = Instantiate(_healthBar.material);
|
||||
_healthBar.material = _material;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_material.SetFloat("_Value", target.HealthPercent());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e16e7cca04243f74f92959565ea79ddc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class HitBox : MonoBehaviour
|
||||
{
|
||||
public bool DoesDealDamage;
|
||||
public float DamageAmount;
|
||||
public float hitStunDuration;
|
||||
public Action<float, float> TakeDamage;
|
||||
|
||||
private BoxCollider2D _collider;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_collider = GetComponent<BoxCollider2D>();
|
||||
}
|
||||
|
||||
private void OnTriggerStay2D(Collider2D other)
|
||||
{
|
||||
var otherHitBox = other.GetComponent<HitBox>();
|
||||
if (otherHitBox != null && DoesDealDamage)
|
||||
{
|
||||
// do damage.
|
||||
Physics2D.IgnoreCollision(_collider, other);
|
||||
otherHitBox.TakeDamage(DamageAmount, hitStunDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
Physics2D.IgnoreCollision(_collider, other, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20f3662ca1aaf554598d2538cb18c0e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public abstract class InputBase : MonoBehaviour
|
||||
{
|
||||
public Action<AttackState> AttackCallback;
|
||||
public abstract float GetMovementDirection();
|
||||
}
|
||||
|
||||
public class Input : InputBase
|
||||
{
|
||||
// Will want to create an "Input Actions" asset, this should allow the user to set their bindings in a menu and I'll be able to load that during the game.
|
||||
// The above is super important for this prototype, but could be used for all other prototypes.
|
||||
public InputActionAsset controlScheme;
|
||||
|
||||
private InputActionMap _inputActionMap;
|
||||
private InputAction _movementAction;
|
||||
//private InputAction _jumpAction;
|
||||
//private InputAction _crouchAction;
|
||||
private InputAction _lightPunch;
|
||||
private InputAction _mediumPunch;
|
||||
private InputAction _heavyPunch;
|
||||
|
||||
// This looks dumb, but I think will make the AI setup better.
|
||||
public override float GetMovementDirection()
|
||||
{
|
||||
return _movementAction.ReadValue<float>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_inputActionMap = controlScheme.FindActionMap("P1");
|
||||
|
||||
_movementAction = _inputActionMap["MovementAction"];
|
||||
_movementAction.Enable();
|
||||
|
||||
_lightPunch = _inputActionMap["LightPunch"];
|
||||
_lightPunch.Enable();
|
||||
_lightPunch.performed += LightPunchPerformed;
|
||||
|
||||
_mediumPunch = _inputActionMap["MediumPunch"];
|
||||
_mediumPunch.Enable();
|
||||
_mediumPunch.performed += MediumPunchPerformed;
|
||||
|
||||
_heavyPunch = _inputActionMap["HeavyPunch"];
|
||||
_heavyPunch.Enable();
|
||||
_heavyPunch.performed += HeavyPunchPerformed;
|
||||
}
|
||||
|
||||
// TODO: probably need to handle input better.
|
||||
// In terms of performance this is good, 0 latency from when the program gets the input and the animation starting.
|
||||
// Obviously this will change as I think buffered input is good, but it's nice to know that I'm starting at 0ms.
|
||||
// Also as combos and buffered input become a thing I'll want to handle this differently, maybe a flag/bitmask?
|
||||
private void HeavyPunchPerformed(InputAction.CallbackContext obj)
|
||||
{
|
||||
AttackCallback(AttackState.Heavy);
|
||||
}
|
||||
|
||||
private void MediumPunchPerformed(InputAction.CallbackContext obj)
|
||||
{
|
||||
AttackCallback(AttackState.Medium);
|
||||
}
|
||||
|
||||
private void LightPunchPerformed(InputAction.CallbackContext obj)
|
||||
{
|
||||
AttackCallback(AttackState.Light);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0524c116968b874985f7154bca3e653
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,148 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public enum AttackState : byte
|
||||
{
|
||||
Idle = 0,
|
||||
Light,
|
||||
Medium,
|
||||
Heavy
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(Rigidbody2D), typeof(Animator), typeof(InputBase))]
|
||||
public class Player : MonoBehaviour
|
||||
{
|
||||
// Will want to create an "Input Actions" asset, this should allow the user to set their bindings in a menu and I'll be able to load that during the game.
|
||||
// The above is super important for this prototype, but could be used for all other prototypes.
|
||||
public float speed;
|
||||
public float jumpForce;
|
||||
public HitBox bodyHitBox;
|
||||
public HitBox lightHitBox;
|
||||
public HitBox mediumHitBox;
|
||||
public HitBox heavyHitBox;
|
||||
public float maxHealth;
|
||||
|
||||
private Rigidbody2D _rigidbody;
|
||||
private bool _isGrounded;
|
||||
private Vector2 _verticalVelocity;
|
||||
private Animator _animator;
|
||||
private AttackState _currentAttack;
|
||||
private int _currentAnimationStateIndex;
|
||||
private InputBase _input;
|
||||
private float _health;
|
||||
private float _hitStunStartTime;
|
||||
private float _hitStunDuration;
|
||||
private bool _isHitStun;
|
||||
|
||||
private const string CurrentAttackText = "CurrentAttack";
|
||||
private bool IsAttacking => _currentAttack != AttackState.Idle;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_health = maxHealth;
|
||||
_rigidbody = GetComponent<Rigidbody2D>();
|
||||
_animator = GetComponent<Animator>();
|
||||
_input = GetComponent<InputBase>();
|
||||
_input.AttackCallback = ChangeAttackingState;
|
||||
|
||||
_rigidbody.gravityScale = 0;
|
||||
|
||||
if (_animator.parameterCount < 1)
|
||||
{
|
||||
Debug.LogAssertion("Animator needs to have one property named \"CurrentState\"");
|
||||
}
|
||||
|
||||
foreach (var parameter in _animator.parameters)
|
||||
{
|
||||
if (parameter.name == CurrentAttackText)
|
||||
{
|
||||
_currentAnimationStateIndex = parameter.nameHash;
|
||||
}
|
||||
}
|
||||
|
||||
bodyHitBox.DamageAmount = 0;
|
||||
bodyHitBox.DoesDealDamage = false;
|
||||
|
||||
bodyHitBox.TakeDamage += DealDamage;
|
||||
lightHitBox.TakeDamage += DealDamage;
|
||||
mediumHitBox.TakeDamage += DealDamage;
|
||||
heavyHitBox.TakeDamage += DealDamage;
|
||||
}
|
||||
|
||||
private void DealDamage(float damage, float hitStunDuration)
|
||||
{
|
||||
_health -= damage;
|
||||
_hitStunDuration = hitStunDuration;
|
||||
_hitStunStartTime = Time.time;
|
||||
_isHitStun = true;
|
||||
_animator.SetBool("IsHitStun", _isHitStun);
|
||||
if (_health <= 0)
|
||||
{
|
||||
Debug.Log("You are dead!");
|
||||
}
|
||||
}
|
||||
|
||||
public float HealthPercent() => _health / maxHealth;
|
||||
|
||||
public void StopAttack()
|
||||
{
|
||||
_currentAttack = AttackState.Idle;
|
||||
}
|
||||
|
||||
private void ChangeAttackingState(AttackState state)
|
||||
{
|
||||
_currentAttack = state;
|
||||
}
|
||||
|
||||
private void JumpPerform(InputAction.CallbackContext callbackContext)
|
||||
{
|
||||
if (_isGrounded)
|
||||
{
|
||||
_verticalVelocity.y = jumpForce;
|
||||
_verticalVelocity.x = _input.GetMovementDirection() * speed;
|
||||
_isGrounded = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_isGrounded)
|
||||
{
|
||||
if (Time.time >= _hitStunStartTime + _hitStunDuration)
|
||||
{
|
||||
_isHitStun = false;
|
||||
_animator.SetBool("IsHitStun", _isHitStun);
|
||||
}
|
||||
|
||||
if (!IsAttacking && !_isHitStun)
|
||||
{
|
||||
var dir = _input.GetMovementDirection();
|
||||
var pos = _rigidbody.position;
|
||||
pos.x += dir * speed * Time.deltaTime;
|
||||
_rigidbody.position = pos;
|
||||
}
|
||||
|
||||
_animator.SetInteger(_currentAnimationStateIndex, (int)_currentAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
var pos = _rigidbody.position;
|
||||
pos += _verticalVelocity * Time.deltaTime;
|
||||
_rigidbody.position = pos;
|
||||
_verticalVelocity += Physics2D.gravity * Time.deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollisionEnter2D(Collision2D other)
|
||||
{
|
||||
foreach (var contact in other.contacts)
|
||||
{
|
||||
// Depending how crouch is implemented this might break or be fickle.
|
||||
if (contact.point.y < _rigidbody.transform.position.y)
|
||||
{
|
||||
_isGrounded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a1ef10236b718a4b851d400ea760226
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user