Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考
此代码仅为较上一P有所改变的代码
【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
UI_itemSlot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;
public class UI_itemSlot : MonoBehaviour ,IPointerDownHandler, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] protected Image itemImage;
[SerializeField] protected TextMeshProUGUI itemText;
protected UI ui;
public InventoryItem item;
protected virtual void Start()
{
ui = GetComponentInParent<UI>();
}
public void UpdateSlots(InventoryItem _newItem)
{
item = _newItem;
itemImage.color = Color.white;
if (item != null)
{
itemImage.sprite = item.data.icon;
if (item.stackSize > 1)
{
itemText.text = item.stackSize.ToString();
}
else
{
itemText.text = "";
}
}
}
public void CleanUpSlot()//解决出现UI没有跟着Inventory变化的bug
{
item = null;
itemImage.sprite = null;
itemImage.color = Color.clear;
itemText.text = "";
}
public virtual void OnPointerDown(PointerEventData eventData)
{
if(item == null)//修复点击空白处会报错的bug
{
return;
}
if(Input.GetKey(KeyCode.LeftControl))
{
Inventory.instance.RemoveItem(item.data);
ui.itemToolTip.HideToolTip();
return;
}
if (item.data.itemType == ItemType.Equipment)
Inventory.instance.EquipItem(item.data);
ui.itemToolTip.HideToolTip();//修复已经装备了装备还会出现详细信息栏的bug
}
public void OnPointerEnter(PointerEventData eventData)
{
if (item == null || item.data == null)
return;
//给装备描述窗口以跟着鼠标移动的功能
Vector2 mousePosition = Input.mousePosition;
float xOffset = 0;
float yOffset = 0;
if (mousePosition.x > 600)
{
xOffset = -75;
}
else
{
xOffset = 75;
}
if (mousePosition.y > 320)
{
yOffset = -75;
}
else
{
yOffset = 75;
}
ui.itemToolTip.ShowToolTip(item.data as ItemData_Equipment);
ui.itemToolTip.transform.position = new Vector2(mousePosition.x + xOffset, mousePosition.y + yOffset);
}
public void OnPointerExit(PointerEventData eventData)
{
if (item == null || item.data == null)
return;
ui.itemToolTip.HideToolTip();
}
}
ItemData_Equipment.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum EquipmentType
{
Weapon,
Armor,
Amulet,
Flask
}
[CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Equipment")]
public class ItemData_Equipment : ItemData
{
public EquipmentType equipmentType;
[Header("Unique effect")]
public float itemCooldown;
public ItemEffect[] itemEffects;
[TextArea]
public string itemEffectDescription;//给装备加上独特的描述
[Header("Major stats")]
public int strength; // 力量 增伤1点 爆伤增加 1% 物抗
public int agility;// 敏捷 闪避 1% 闪避几率增加 1%
public int intelligence;// 1 点 魔法伤害 1点魔抗
public int vitality;//加血的
[Header("Offensive stats")]
public int damage;
public int critChance; // 暴击率
public int critPower; //150% 爆伤
[Header("Defensive stats")]
public int health;
public int armor;
public int evasion;//闪避值
public int magicResistance;
[Header("Magic stats")]
public int fireDamage;
public int iceDamage;
public int lightingDamage;
[Header("Craft requirements")]
public List<InventoryItem> craftingMaterials;
public int descriptionLength;
public void AddModifiers()
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
playerStats.strength.AddModifier(strength);
playerStats.agility.AddModifier(agility);
playerStats.intelligence.AddModifier(intelligence);
playerStats.vitality.AddModifier(vitality);
playerStats.damage.AddModifier(damage);
playerStats.critChance.AddModifier(critChance);
playerStats.critPower.AddModifier(critPower);
playerStats.Health.AddModifier(health);
playerStats.armor.AddModifier(armor);
playerStats.evasion.AddModifier(evasion);
playerStats.magicResistance.AddModifier(magicResistance);
playerStats.fireDamage.AddModifier(fireDamage);
playerStats.iceDamage.AddModifier(iceDamage);
playerStats.lightingDamage.AddModifier(lightingDamage);
}
public void RemoveModifiers()
{
PlayerStats playerStats = PlayerManager.instance.player.GetComponent<PlayerStats>();
playerStats.strength.RemoveModifier(strength);
playerStats.agility.RemoveModifier(agility);
playerStats.intelligence.RemoveModifier(intelligence);
playerStats.vitality.RemoveModifier(vitality);
playerStats.damage.RemoveModifier(damage);
playerStats.critChance.RemoveModifier(critChance);
playerStats.critPower.RemoveModifier(critPower);
playerStats.Health.RemoveModifier(health);
playerStats.armor.RemoveModifier(armor);
playerStats.evasion.RemoveModifier(evasion);
playerStats.magicResistance.RemoveModifier(magicResistance);
playerStats.fireDamage.RemoveModifier(fireDamage);
playerStats.iceDamage.RemoveModifier(iceDamage);
playerStats.lightingDamage.RemoveModifier(lightingDamage);
}
public void Effect(Transform _enemyPosition)//循环调用Effect类里的Effect的函数
{
foreach(var item in itemEffects)
{
item.ExecuteEffect(_enemyPosition);
}
}
public override string GetDescription()//让外界能够拿到拼出字符串的函数
{
sb.Length = 0;
descriptionLength = 0;
AddItemDescription(strength, "strength");
AddItemDescription(agility, "agility");
AddItemDescription(intelligence, "intelligence");
AddItemDescription(vitality, "vitality");
AddItemDescription(damage, "damage");
AddItemDescription(critChance, "critChance");
AddItemDescription(critPower, "critPower");
AddItemDescription(health, "health");
AddItemDescription(evasion, "evasion");
AddItemDescription(armor, "armor");
AddItemDescription(magicResistance, "magicResistance");
AddItemDescription(fireDamage, "fireDamage");
AddItemDescription(iceDamage, "iceDamage");
AddItemDescription(lightingDamage, "lightingDamage");
if(descriptionLength < 5)//使窗口拥有最小尺寸
{
for(int i = 0;i < 5- descriptionLength;i++)
{
sb.AppendLine();
sb.Append("");
}
}
if(itemEffectDescription.Length > 0)//给装备加上独特的描述
{
sb.AppendLine();
sb.Append(itemEffectDescription);
}
return sb.ToString();
}
private void AddItemDescription(int _value,string _name)//通过判断拼出想要的字符串的函数
{
if(_value != 0)
{
if(sb.Length >= 0)
sb.AppendLine();//这是控制行数的
if(_value > 0)
sb.Append(" + "+_value + " " + _name);//这是实打实添加字符串的
descriptionLength++;
}
}
}
UI_SkillTreeSlot.cs
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class UI_SkillTreeSlot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private int skillPrice;
[SerializeField] private string skillName;
[TextArea]
[SerializeField] private string skillDescription;
[SerializeField] private Color lockedSkillColor;
private UI ui;
public bool unlocked;//一个表示是否解锁的bool值
private Image skillImage;
[SerializeField] private UI_SkillTreeSlot[] shouldBeUnlocked;
[SerializeField] private UI_SkillTreeSlot[] shouldBeLocked;//应该解锁和不该解锁的Slot组和Image
private void OnValidate()
{
gameObject.name = "SkillTreeSlot_UI - " + skillName;
}
private void Awake()
{
GetComponent<Button>().onClick.AddListener(() => UnlockSkillSlot());
}
private void Start()
{
skillImage = GetComponent<Image>();
skillImage.color = lockedSkillColor;
ui = GetComponentInParent<UI>();
}
public void UnlockSkillSlot()//一个判断此Skill是否可以解锁的函数
{
if (PlayerManager.instance.HaveEnoughMoney(skillPrice) == false)
return;
Debug.Log("Slot unlocked");
for (int i = 0; i < shouldBeUnlocked.Length; i++)
{
if (shouldBeUnlocked[i].unlocked == false)
{
Debug.Log("Cannot unlock skill");
return;
}
}
for (int i = 0; i < shouldBeLocked.Length; i++)
{
if (shouldBeLocked[i].unlocked == true)
{
Debug.Log("Cannot unlock skill");
return;
}
}
unlocked = true;
skillImage.color = Color.white;
}
public void OnPointerEnter(PointerEventData eventData)
{
ui.skillToolTip.ShowToolTip(skillDescription, skillName);
Vector2 mousePosition = Input.mousePosition;
float xOffset = 0;
float yOffset = 0;
if (mousePosition.x > 600)
{
xOffset = -150;
}
else
{
xOffset = 150;
}
if (mousePosition.y > 200)
{
yOffset = -100;
}
else
{
yOffset = 100;
}
ui.skillToolTip.transform.position = new Vector2(mousePosition.x + xOffset, mousePosition.y + yOffset);
}
public void OnPointerExit(PointerEventData eventData)
{
ui.skillToolTip.HideToolTip();
}
}
PlayerManager.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class PlayerManager : MonoBehaviour
{
public static PlayerManager instance;
public Player player;//这是通过在外部设置了一个组件,让这个组件能够直接把Player找到,从而减少FInd的方式所带来的高负载
public int currency;
private void Awake()
{
if(instance != null)
{
Destroy(instance.gameObject);
}
else
instance = this;
}
public bool HaveEnoughMoney(int _price)
{
if(_price > currency)
{
Debug.Log("Not enough money");
return false;
}
currency -= _price;
return true;
}
}
Player.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Player : Entity
{
[Header("Attack Details")]
public Vector2[] attackMovement;//每个攻击时获得的速度组
public float counterAttackDuration = .2f;
public bool isBusy{ get; private set; }//防止在攻击间隔中进入move
//
[Header("Move Info")]
public float moveSpeed;//定义速度,与xInput相乘控制速度的大小
public float jumpForce;
public float swordReturnImpact;//在player里设置swordReturnImpact作为击退的参数
private float defaultMoveSpeed;
private float defaultJumpForce;
[Header("Dash Info")]
[SerializeField] private float dashCooldown;
private float dashUsageTimer;//为dash设置冷却时间,在一定时间内不能连续使用
public float dashSpeed;//冲刺速度
public float dashDuration;//持续时间
private float defaultDashSpeed;
public float dashDir { 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 PlayerDashState dashState { get; private set; }
public PlayerWallSlideState wallSlide { get; private set; }
public PlayerWallJumpState wallJump { get; private set; }
public PlayerDeadState deadState { 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 SkillManager skill { get; private set; }
public GameObject sword{ get; private set; }//声明sword
#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");//wallJump也是Jump动画
deadState = new PlayerDeadState(this, stateMachine, "Die");
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");
//this 就是 Player这个类本身
}//Awake初始化所以State,为所有State传入各自独有的参数,及animBool,以判断是否调用此动画(与animatoin配合完成)
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
skill = SkillManager.instance;
defaultMoveSpeed = moveSpeed;
defaultJumpForce = jumpForce;
defaultDashSpeed = dashSpeed;
}
protected override void Update()//在mano中update会自动刷新但其他没有mano的不会故,需要在这个updata中调用其他脚本中的函数stateMachine.currentState.update以实现 //stateMachine中的update
{
base.Update();
stateMachine.currentState.Update();//反复调用CurrentState的Update函数
CheckForDashInput();
if(Input.GetKeyDown(KeyCode.F))
{
skill.crystal.CanUseSkill();
}
if(Input.GetKeyDown(KeyCode.Alpha1))//血瓶回血
{
Inventory.instance.UseFlask();
}
}
public override void SlowEntityBy(float _slowPercentage, float flowDuration)//减缓一切速度函数
{
base.SlowEntityBy(_slowPercentage, flowDuration);
moveSpeed = moveSpeed * (1 - _slowPercentage);
jumpForce = jumpForce * (1 - _slowPercentage);
dashSpeed = dashSpeed * (1 - _slowPercentage);
anim.speed = anim.speed * (1 - _slowPercentage);
Invoke("ReturnDefaultSpeed", flowDuration);
}
protected override void ReturnDefaultSpeed()//全部速度恢复正常函数
{
base.ReturnDefaultSpeed();
moveSpeed = defaultMoveSpeed;
jumpForce = defaultJumpForce;
dashSpeed = defaultDashSpeed;
}
public void AssignNewSword(GameObject _newSword)//保持创造的sword实例的函数
{
sword = _newSword;
}
public void CatchTheSword()//通过player的CatchTheSword进入,及当剑消失的瞬间进入
{
stateMachine.ChangeState(catchSword);
Destroy(sword);
}
public IEnumerator BusyFor(float _seconds)//https://www.zhihu.com/tardis/bd/art/504607545?source_id=1001
{
isBusy = true;
yield return new WaitForSeconds(_seconds);
isBusy = false;
}//p39 4.防止在攻击间隔中进入move,通过设置busy值,在使用某些状态时,使其为busy为true,抑制其进入其他state
//IEnumertor本质就是将一个函数分块执行,只有满足某些条件才能执行下一段代码,此函数有StartCoroutine调用
public void AnimationTrigger() => stateMachine.currentState.AnimationFinishTrigger();
//从当前状态拿到AnimationTrigger进行调用的函数
public void CheckForDashInput()
{
if (IsWallDetected())
{
return;
}//修复在wallslide可以dash的BUG
if (skill.dash.dashUnlocked == false)
return;
if (Input.GetKeyDown(KeyCode.LeftShift) && skill.dash.CanUseSkill())//将DashTimer<0 的判断 改成DashSkill里的判断
{
dashDir = Input.GetAxisRaw("Horizontal");//设置一个值,可以将dash的方向改为你想要的方向而不是你的朝向
if (dashDir == 0)
{
dashDir = facingDir;//只有当玩家没有控制方向时才使用默认朝向
}
stateMachine.ChangeState(dashState);
}
}//将Dash切换设置成一个函数,使其在所以情况下都能使用
public override void Die()
{
base.Die();
stateMachine.ChangeState(deadState);
}
}
PlayerDashState.cs
public class PlayerDashState : PlayerState
{ //由Ground进入
public PlayerDashState(Player _player, PlayerStateMachine _stateMachine, string _animBoolName) : base(_player, _stateMachine, _animBoolName)
{
}
public override void Enter()
{
base.Enter();
player.skill.dash.CloneOnDash();//调用克隆函数,并传入当前位置已调试clone的位置
stateTimer = player.dashDuration;设置Dash可以保持的值
}
public override void Exit()
{
base.Exit();
player.skill.dash.CloneOnArrival();
player.SetVelocity(0, rb.velocity.y);//当退出时使速度为0防止动作结束后速度不变导致的持续移动
}
public override void Update()
{
base.Update();
if (!player.IsGroundDetected() && player.IsWallDetected())//修复无法在空中dash直接进入wallSlide,修复在wallslide可以dash
//因为一旦在wallSlide里dash,就会直接进wallSlide,当然还有更简单的方法
{
stateMachine.ChangeState(player.wallSlide);
}
player.SetVelocity(player.dashSpeed * player.dashDir, 0);//这个写在Update里,防止在x轴方向减速了,同时Y轴写成0,以防止dash还没有完成就从空中掉下去了
if (stateTimer < 0)//当timer小于0,切换
{
stateMachine.ChangeState(player.idleState);
}
}
}
Dash_Skill.cs
using UnityEngine;
using UnityEngine.UI;
public class Dash_Skill : Skill
{
[Header("Dash")]
public bool dashUnlocked;
[SerializeField] private UI_SkillTreeSlot dashUnlockButton;
[Header("Dash")]
public bool cloneOnDashUnlocked;
[SerializeField] private UI_SkillTreeSlot cloneOnDashUnlockedButton;
[Header("Dash")]
public bool cloneOnArrivalUnlocked;
[SerializeField] private UI_SkillTreeSlot cloneOnArrivalUnlockedButton;
public override void UseSkill()
{
base.UseSkill();
}
protected override void Start()
{
dashUnlockButton.GetComponent<Button>().onClick.AddListener(UnlockDash);
cloneOnDashUnlockedButton.GetComponent<Button>().onClick.AddListener(UnlockCloneOnDash);
cloneOnArrivalUnlockedButton.GetComponent<Button>().onClick.AddListener(UnlockCloneOnArrival);
}
private void UnlockDash()
{
if (dashUnlockButton.unlocked)
dashUnlocked = true;
}
private void UnlockCloneOnDash()
{
if (cloneOnDashUnlockedButton.unlocked)
cloneOnDashUnlocked = true;
}
private void UnlockCloneOnArrival()
{
if(cloneOnArrivalUnlockedButton.unlocked)
cloneOnArrivalUnlocked = true;
}
//让冲刺留下来的克隆在开始和结束各有一个
public void CloneOnDash()
{
if (cloneOnDashUnlocked)
{
SkillManager.instance.clone.CreateClone(PlayerManager.instance.player.transform, Vector3.zero);
Debug.Log("Create Clone");
}
}
public void CloneOnArrival()
{
if (cloneOnArrivalUnlocked)
SkillManager.instance.clone.CreateClone(PlayerManager.instance.player.transform, Vector3.zero);
}
}
Clone_Skill.cs
using System.Collections;
using UnityEngine;
public class Clone_Skill : Skill
{
[Header("Clone Info")]
[SerializeField] private GameObject clonePrefab;//克隆原型
[SerializeField] private float cloneDuration;//克隆持续时间
[SerializeField] private bool canAttack;// 判断是否可以攻击
[SerializeField] private bool canCreateCloneOnCounterAttack;
[Header("Clone can duplicate")]
[SerializeField] private bool canDuplicateClone;
[SerializeField] private float chanceToDuplicate;
[Header("Crystal instead of clone")]
public bool crystalInsteadOfClone;
public void CreateClone(Transform _clonePosition, Vector3 _offset)//传入克隆位置
{
if (crystalInsteadOfClone)
{
SkillManager.instance.crystal.CreateCrystal();
return;
}//让所有的生成克隆的技能都变成生成水晶
GameObject newClone = Instantiate(clonePrefab);//创建新的克隆//克隆 original 对象并返回克隆对象。
//https://docs.unity3d.com/cn/current/ScriptReference/Object.Instantiate.html
newClone.GetComponent<Clone_Skill_Controller>().SetupClone(_clonePosition, cloneDuration, canAttack, _offset, FindClosestEnemy(newClone.transform), canDuplicateClone, chanceToDuplicate, player);//调试clone的位置,同时调试克隆持续时间 //Controller绑在克隆原型上的,所以用GetComponent
}
//反击后产生一个克隆被刺敌人
public void CanCreateCloneOnCounterAttack(Transform _enemyTransform)
{
if (canCreateCloneOnCounterAttack)
StartCoroutine(CreateCloneWithDelay(_enemyTransform, new Vector3(1 * player.facingDir, 0, 0)));
}
//整个延迟生成
private IEnumerator CreateCloneWithDelay(Transform _enemyTransform, Vector3 _offset)
{
yield return new WaitForSeconds(.4f);
CreateClone(_enemyTransform, _offset);
}
}