Entity.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Entity : MonoBehaviour
{
#region Components
public Animator anim { get; private set; }
public Rigidbody2D rb { get; private set; }
public SpriteRenderer sr { get; private set; }
public CharacterStats stats { get; private set; }
public CapsuleCollider2D cd { get; private set; }
#endregion
[Header("Knockback info")]
[SerializeField] protected Vector2 knockbackPower;
[SerializeField] protected float knockbackDuration;
protected bool isKnocked;
[Header("Collsion info")]
public Transform attackCheck;
public float attackCheckRadius;
[SerializeField] protected Transform groundCheck;
[SerializeField] protected float groundCheckDistance;
[SerializeField] protected Transform wallCheck;
[SerializeField] protected float wallCheckDistance;
[SerializeField] protected LayerMask whatIsGround;
public int knockbackDir { get; private set; }
public int facingDir { get; private set; } = 1;
protected bool facingRight = true;
public System.Action onFlipped;
protected virtual void Awake()
{
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
}
protected virtual void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();
anim = GetComponentInChildren<Animator>();
rb = GetComponent<Rigidbody2D>();
stats = GetComponentInChildren<CharacterStats>();
cd = GetComponent<CapsuleCollider2D>();
}
protected virtual void Update()
{
}
public virtual void SlowEntityBy(float _slowPercentage, float _SlowDuration)
{
}
protected virtual void ReturnDefaultSpeed()
{
anim.speed = 1;
}
public virtual void DamageImpact()
{
//fx.StartCoroutine("FlashFX");
StartCoroutine("HitKnockback");
//Debug.Log(gameObject.name + "was damaged");
}
public virtual void SetupKnockbackDir(Transform _damageDirection)
{
if (_damageDirection.position.x > transform.position.x)
knockbackDir = -1;
else if(_damageDirection.position.x < transform.position.x)
knockbackDir = 1;
}
public void SetupKnockbackPower(Vector2 _knockbackpower) => knockbackPower = _knockbackpower;
protected virtual IEnumerator HitKnockback()
{
isKnocked = true;
// 强制停止所有可能存在的惯性
//rb.velocity = Vector2.zero;
rb.velocity = new Vector2(knockbackPower.x * -facingDir, knockbackPower.y);
yield return new WaitForSeconds(knockbackDuration);
// 强制停止所有可能存在的惯性
//rb.velocity = Vector2.zero;
isKnocked = false;
SetupZeroKnockbackPower();
}
protected virtual void SetupZeroKnockbackPower()
{
}
#region Velocity //设置速度
public void SetZeroVelocity()
{
if (isKnocked)
{
return;
}
rb.velocity = new Vector2(0, 0);
}
public void SetVelocity(float _xVelocity, float _yVelocity)
{
if (isKnocked)
{
return;
}
rb.velocity = new Vector2(_xVelocity, _yVelocity);
FlipControler(_xVelocity);
}
#endregion
#region Collision // 碰撞检测
public virtual bool IsGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down , groundCheckDistance, whatIsGround);
public virtual bool IsWallDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsGround);
protected virtual void OnDrawGizmos()
{
// 地面检测可视化
Gizmos.color = Color.green;
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
// 墙壁检测可视化
//Gizmos.color = Color.red;
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance, wallCheck.position.y));
Gizmos.DrawWireSphere(attackCheck.position, attackCheckRadius);
}
#endregion
#region Flip //翻动
public virtual void Flip()
{
facingDir = facingDir * -1;
facingRight = !facingRight;
//Debug.Log(facingDir);
transform.Rotate(0, 180, 0);
if (onFlipped != null)
{
onFlipped();
}
}
public virtual void FlipControler(float _x)
{
if (_x > 0 && !facingRight)
{
Flip();
}
else if (_x < 0 && facingRight)
{
Flip();
}
}
#endregion
public virtual void Die()
{ }
}
EntityFX.cs
using Cinemachine;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using Random = UnityEngine.Random;
public class EntityFX : MonoBehaviour
{
protected Player player;
protected SpriteRenderer sr;
[Header("Pop Up Text")]
[SerializeField] private GameObject popUpTextPrefab;
[Header("Flash FX")]
[SerializeField] private float flashDuration;
[SerializeField] private Material hitMat;
private Material originalMat;
[Header("Ailemnt colors")]
[SerializeField] private Color[] chillColor;
[SerializeField] private Color[] igniteColor;
[SerializeField] private Color[] shockColor;
[Header("Ailment particles")]
[SerializeField] private ParticleSystem igniteFx;
[SerializeField] private ParticleSystem chillFx;
[SerializeField] private ParticleSystem shockFx;
[Header("Hit FX")]
[SerializeField] private GameObject hitFx;
[SerializeField] private GameObject criticalHitFx;
protected virtual void Start()
{
sr = GetComponentInChildren<SpriteRenderer>();
//sr = GetComponent<SpriteRenderer>();
player = PlayerManager.instance.player;
originalMat = sr.material;
}
public void CreatePopUpText(string _text)
{
float randomX = Random.Range(-1, 1);
float randomY = Random.Range(3, 5);
Vector3 positionOffset = new Vector3(randomX, randomY, 0);
GameObject newText = Instantiate(popUpTextPrefab,transform.position + positionOffset, Quaternion.identity);
newText.GetComponent<TextMeshPro>().text = _text;
}
public void MakeTransprent(bool _transprent)
{
if (_transprent)
{
sr.color = Color.clear;
}
else
{
sr.color = Color.white;
}
}
private IEnumerator FlashFX()
{
sr.material = hitMat;
Color currentColor = sr.color;
sr.color = Color.white;
yield return new WaitForSeconds(flashDuration);
sr.color = currentColor;
sr.material = originalMat;
//Invoke
}
private void RedColorBlink()
{
if (sr.color != Color.white)
{
sr.color = Color.white;
}
else
{
sr.color = Color.red;
}
}
private void CancelColorChange()
{
CancelInvoke();
sr.color = Color.white;
igniteFx.Stop();
chillFx.Stop();
shockFx.Stop();
}
public void IgniteFxFor(float _seconds)
{
if (igniteFx == null)
return;
else
igniteFx.Play();
InvokeRepeating("IgniteColorFx", 0, .3f);
Invoke("CancelColorChange", _seconds);
}
public void ShockFxFor(float _seconds)
{
if (shockFx == null)
return;
else
shockFx.Play();
InvokeRepeating("ShockColorFx", 0, .3f);
Invoke("CancelColorChange", _seconds);
}
public void ChillFxFor(float _seconds)
{
if (chillFx == null)
return;
else
chillFx.Play();
InvokeRepeating("ChillColorFx", 0, .3f);
Invoke("CancelColorChange", _seconds);
}
private void IgniteColorFx()
{
if (sr.color != igniteColor[0])
sr.color = igniteColor[0];
else
sr.color = igniteColor[1];
}
private void ShockColorFx()
{
if (sr.color != shockColor[0])
sr.color = shockColor[0];
else
sr.color = shockColor[1];
}
private void ChillColorFx()
{
if (sr.color != chillColor[0])
sr.color = chillColor[0];
else
sr.color = chillColor[1];
}
public void CreateHitFx(Transform _target,bool _critical)
{
if (hitFx == null|| (_critical && criticalHitFx == null)) return;
float zRotation = Random.Range(-90, 90);
float xPostiotion = Random.Range(-.5f, .5f);
float yPostiotion = Random.Range(-.5f, .5f);
Vector3 hitFxRotation = new Vector3(0,0,zRotation);
GameObject hitPrefab = hitFx;
if (_critical)
{
hitPrefab = criticalHitFx;
float yRotation = 0;
zRotation = Random.Range(-45,45);
if (GetComponent<Entity>().facingDir == -1)
yRotation = 180;
hitFxRotation = new Vector3(0, yRotation, zRotation);
}
GameObject newHitFx = Instantiate(hitPrefab,_target.position + new Vector3(xPostiotion, yPostiotion), Quaternion.identity,_target);
//newHitFx.transform.position = new Vector2(xPostiotion,yPostiotion);
newHitFx.transform.Rotate(hitFxRotation);
Destroy(newHitFx, .5f);
}
}
Player.cs
using System.Collections;
using UnityEngine;
public class Player : Entity
{
[Header("Attack detail")]
public Vector2[] attackMovement;
public float counterAttackDuration = .2f;
public bool isBusy { get; private set; }
[Header("Move info")]
public float moveSpeed = 12f;
public float jumpForce;
public float swordReturnImpact;
private float defaultMoveSpeed;
private float defaultJumpForce;
[Header("Dash info")]
//[SerializeField] private float dashCoolDown;
//private float dashUsageTimer;
public float dashSpeed;
public float dashDuration;
private float defaultDashSpeed;
public float dashDir { get; private set; }
public static Player instance;
public GameObject sword { get; private set; }//声明sword
public SkillManager skill { get; private set; }
#region States
public PlayerStateMachine stateMachine { get; private set; }
public PlayerIdleState idleState { get; private set; }
public PlayerMoveState moveState { get; private set; }
public PlayerJumpState jumpState { get; private set; }
public PlayerAirState airState { get; private set; }
public PlayerWallSlideState wallSlide { get; private set; }
public PlayerWallJumpState wallJump { get; private set; }
public PlayerDashState dashState { get; private set; }
public PlayerPrimaryAttackState primaryAttack { get; private set; }
public PlayerCounterAttackState counterAttack { get; private set; }
public PlayerAimSwordState aimSword { get; private set; }
public PlayerCatchSwordState catchSword { get; private set; }
public PlayerBlackholeState blackhole { get; private set; }
public PlayerDeadState deadState { get; private set; }
public PlayerFX fx { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
stateMachine = new PlayerStateMachine();
idleState = new PlayerIdleState(this, stateMachine, "Idle");// 建立自己的状态值
moveState = new PlayerMoveState(this, stateMachine, "Move");// 建立自己的状态值
jumpState = new PlayerJumpState(this, stateMachine, "Jump");
airState = new PlayerAirState(this, stateMachine, "Jump");
dashState = new PlayerDashState(this, stateMachine, "Dash");
wallSlide = new PlayerWallSlideState(this, stateMachine, "WallSlide");
wallJump = new PlayerWallJumpState(this, stateMachine, "Jump");
primaryAttack = new PlayerPrimaryAttackState(this, stateMachine, "Attack");
counterAttack = new PlayerCounterAttackState(this, stateMachine, "CounterAttack");
aimSword = new PlayerAimSwordState(this, stateMachine, "AimSword");
catchSword = new PlayerCatchSwordState(this, stateMachine, "CatchSword");
blackhole = new PlayerBlackholeState(this, stateMachine, "Jump");
deadState = new PlayerDeadState(this, stateMachine, "Die");
if (instance != null) // 250608新增
Destroy(instance);
else
instance = this;
}
protected override void Start()
{
base.Start();
fx = GetComponent<PlayerFX>();
skill = SkillManager.instance;
stateMachine.Initialize(idleState);// 对自己的状态值进行初始化处理
defaultMoveSpeed = moveSpeed;
defaultJumpForce = jumpForce;
defaultDashSpeed = dashSpeed;
}
protected override void Update()
{
if(Time.timeScale == 0)
return;
base.Update();
stateMachine.currentState.Update();// 通过currentState(PlayerStateMachine(进入PlayerState))实现对自己的状态更新,两次进入状态,嵌套进入
CheckForDashInput();
if (Input.GetKeyDown(KeyCode.F) && skill.crystal.crystalUnlocked)
{
skill.crystal.CanUseSkill();
}
if (Input.GetKeyDown(KeyCode.Alpha1))
{
Debug.Log("use flask");
Inventory.instance.UseFlask();
}
}
public override void SlowEntityBy(float _slowPercentage, float _slowDuration)
{
moveSpeed = moveSpeed * (1 - _slowPercentage);
jumpForce = jumpForce * (1 - _slowPercentage);
dashSpeed = dashSpeed * (1 - _slowPercentage);
anim.speed = anim.speed * (1 - _slowPercentage);
Invoke("ReturnDefaultSpeed", _slowDuration);
}
protected override void ReturnDefaultSpeed()
{
base.ReturnDefaultSpeed();
moveSpeed = defaultMoveSpeed;
jumpForce = defaultJumpForce;
dashSpeed = defaultDashSpeed;
}
public void AssignNewSword(GameObject _newSword)
{
sword = _newSword;
}
public void CatchTheSword()
{
stateMachine.ChangeState(catchSword);
Destroy(sword);
sword = null;
}
public IEnumerator BusyFor(float _seconds)
{
isBusy = true;
yield return new WaitForSeconds(_seconds);
isBusy = false;
}
public void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();
private void CheckForDashInput()
{
if (IsWallDetected())
{
return;
}
if (skill.dash.dashUnlocked == false)
{
return;
}
//dashUsageTimer -= Time.deltaTime;
if (Input.GetKeyDown(KeyCode.LeftShift) && SkillManager.instance.dash.CanUseSkill())
{
//dashUsageTimer = dashCoolDown;
dashDir = Input.GetAxisRaw("Horizontal");
if (dashDir == 0)
dashDir = facingDir;
stateMachine.ChangeState(dashState);
}
}
public override void Die()
{
base.Die();
stateMachine.ChangeState(deadState);
}
protected override void SetupZeroKnockbackPower()
{
knockbackPower = new Vector2(0,0);
}
}
PlayerFX.cs
using Cinemachine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerFX : EntityFX
{
[Header("Screen shake FX")]
private CinemachineImpulseSource screenShake;
[SerializeField] private float shakeMultiplier;
public Vector3 shakeSwordImpact;
public Vector3 shakeHighDamage;
[Header("After image fx")]
[SerializeField] private GameObject afterImagePrefab;
[SerializeField] private float colorLooseRate;
[SerializeField] private float afterImageCooldown;
private float afterImageCooldownTimer;
[Space]
[SerializeField] private ParticleSystem dustFx;
protected override void Start()
{
base.Start();
screenShake = GetComponent<CinemachineImpulseSource>();
}
private void Update()
{
afterImageCooldownTimer -= Time.deltaTime;
}
public void CreateAfterImage()
{
if (afterImageCooldownTimer < 0)
{
afterImageCooldownTimer = afterImageCooldown;
GameObject newAfterImage = Instantiate(afterImagePrefab, transform.position, transform.rotation);
newAfterImage.GetComponent<AfterImageFX>().SetUpAfterImage(colorLooseRate, sr.sprite);
}
}
public void ScreenShake(Vector3 _shakePower)
{
screenShake.m_DefaultVelocity = new Vector3(_shakePower.x * player.facingDir, _shakePower.y) * shakeMultiplier;
screenShake.GenerateImpulse();
}
public void PlayDustFX()
{
if (dustFx != null)
dustFx.Play();
}
}