欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > RPG UNITY实战

RPG UNITY实战

2025/4/6 12:29:39 来源:https://blog.csdn.net/m0_72964546/article/details/139692653  浏览:    关键词:RPG UNITY实战

1.移动

//移动,分别是x轴和y轴的速度
rb.velocity = new Vector2 (horizontal * moveSpeed, rb.velocity.y);

2.私有且可以在检查器浏览和修改此值

    //私有且可以在检查器浏览和修改此值[SerializeField] private float moveSpeed;[SerializeField] private float jumpForce;

3.切分sprite图

3.1.当sprite和中心点不匹配

  • 创建空子物体,此子物体加上Sprite Renderer组件把Sprite放在子物体上,移动子物体来使sprite和中心点对齐; 

4.如何允许对象只在地面上才可以跳跃

1.找到地面和对象的距离:对象往下划线,如果超越地面就可得到距离,这个距离值设为公开;

  • //不需要启动游戏,直接就会划线
    [SerializeField] private float GroundCheckDistance;//不需要启动游戏,直接就会划线//检测距离地面的数值private void OnDrawGizmos(){Gizmos.DrawLine(transform.position, new Vector2(transform.position.x, transform.position.y - GroundCheckDistance));}

2. 使用射线检测是否在地面上

    //检测是否在地面[SerializeField] private LayerMask groundLayer;[SerializeField] private bool isGround;private void GroundCollisionCheck(){isGround = Physics2D.Raycast(transform.position, Vector2.down, groundCheckDistance, groundLayer);}

5.角色粘墙问题

  • 给墙体创建物理材质,摩擦力设为0即可解决

6.冲刺:点击左右键+冲刺键,在一定时间内速度提升

  • 在冲刺时间内播放冲刺动画且速度提升;
  • 设置一个冲刺时间初始值,按冲刺键获取这个值,Update函数用获得值-=Time.deltaTime

7.非玩家角色检测是否在地面边缘

  • 创建一个Transform组件的对象作为游戏物体的子对象,调整Transform位置在游戏物体脚下
    [SerializeField] protected Transform groundCheck;protected virtual void OnDrawGizmos(){Gizmos.DrawLine(groundCheck.position, new Vector2(groundCheck.position.x, groundCheck.position.y - groundDistance));}

7.P40:可以是攻击时完全不移动; 

8.调色板

8.1.创建调色板并设置不同的图层

给Ground层添加Tilemap和Composition碰撞器,tilemap设置复用选项

背景层图层设置-1

8.2.调色板选项和使用

8.3.摄像机

9.做背景:选择图片2张及以上拷贝3份,依次排列,一个快一个慢 ,超出图片尺寸距离,移动图片位置

计算图片超出距离需要改

public class ParallaxBackGround : MonoBehaviour
{private GameObject cam;[SerializeField] private float parallaxEffect;private float xPosition; private float len;void Start(){cam = GameObject.Find("Main Camera");xPosition = transform.position.x;len =GetComponent<SpriteRenderer>().bounds.size.x;//获取图片大小}// Update is called once per framevoid Update(){float distanceToMove = cam.transform.position.x * parallaxEffect;float distanceMove = cam.transform.position.x * (1 - parallaxEffect);//原本坐标加上摄像机坐标*视差transform.position = new Vector3(xPosition + distanceToMove, transform.position.y);//动画不缺失if (distanceMove > xPosition + len)xPosition += len;else if (distanceMove < xPosition - len)xPosition -= len;}
}

10.攻击检测

在基类写一个收到伤害函数

    public void Damage(){Debug.Log(gameObject.name + " was damage ");}

基类需要一个Transform对象和检测半径

    public Transform _attackCheck;public float _attackCheckRadius;protected virtual void OnDrawGizmos(){Gizmos.DrawWireSphere(_attackCheck.position, _attackCheckRadius);}

在攻击动画添加事件

OverlapCircleAll检测圆内的所有碰撞体

    void AttackTrigger(){Collider2D[] collider2Ds = Physics2D.OverlapCircleAll(_player._attackCheck.position, _player._attackCheckRadius);foreach(var hit in  collider2Ds){if(hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}

11.不同图层是否做碰撞检测

12.受击闪光

创建一个材质,设置为白色并且shader为GUI

开始使用默认材质,flashTime时间后使用时候添加的材质

    private SpriteRenderer sr;[Header("Flash FX")]private Material originalMaterial;[SerializeField] private Material hitMaterial;[SerializeField] private float flashTime;private void Start(){sr = GetComponent<SpriteRenderer>();originalMaterial = sr.material;}private IEnumerator  FlashFX(){sr.material = hitMaterial;yield return  new WaitForSeconds(flashTime);sr.material = originalMaterial;}

13.受击后退

在攻击动画中添加事件

添加一个子对象,使用此对象的位置和Gizmos.DrawWireSphere来画圆,调整圆大小

如何检测

14.受击反击 

1.敌人创建一个被反击击晕状态

public class Skeleton_Stunned : EnemyState
{Skeleton_Enemy _enemy;public Skeleton_Stunned(Skeleton_Enemy enemy, EnemyStateMachine stateMachine, string stateName) : base(enemy, stateMachine, stateName){_enemy = enemy;}public override void Enter(){base.Enter();stateTime =1;//击晕时间_rb.velocity = new Vector2(_enemy.StunnedDir.x * -_enemy.facingDir, _enemy.StunnedDir.y);//击退_enemy._entityFX.InvokeRepeating("RedColorBlink", 0, 0.2f);//闪光}public override void Exit(){base.Exit();_enemy._entityFX.Invoke("CancelRedBlink",0);//取消闪光}public override void Update(){base.Update();if(stateTime < 0)_enemyStateMachine.ChangeState(_enemy.idleState);}   
}

格挡反击逻辑:玩家就如格挡动画,格挡成功进入反击动画,敌人进入击晕动画

15.冲刺留下影像攻击

1.在冲刺技能enter()创建影像

    public override void Enter(){base.Enter();SkillManager._instance._clone.CreateClone(_player.transform);//clone}

2.设置Animotor,动画内有AttackTrigger事件,检测攻击范围敌人,并使敌人受击;

3.创建预设体,设置预设体寻找最近的敌人面向它,使用技能管理器决定能否攻击,随机是用3种攻击的一种;Update使物体的颜色逐渐变淡,为零删除对象

public class Clone_Skill : Skill
{[SerializeField] private GameObject _clonePrefab;//可用预设体[SerializeField] private bool _canAttack;public void CreateClone(Transform cloneTrans){GameObject newClone = Instantiate(_clonePrefab);newClone.GetComponent<Clone_Skill_Control>().SetupClone(cloneTrans,_canAttack);}
}
public class Clone_Skill_Control : MonoBehaviour
{private SpriteRenderer sr;private Animator _anim;[Header("Collider info")][SerializeField] private Transform _attackCheck;[SerializeField] private float _attackCheckRadius;[SerializeField] private float _cloneDuration;private float _cloneTime;[SerializeField] private float _cloneLoosingSpeed;private void Awake(){sr = GetComponent<SpriteRenderer>();_anim = GetComponent<Animator>();}//颜色逐渐变淡void Update(){_cloneTime -= Time.deltaTime;if (_cloneTime < 0){sr.color = new Color(1, 1, 1, sr.color.a - (Time.deltaTime * _cloneLoosingSpeed));}}public void SetupClone(Transform newTransform, bool canAttack){_cloneTime = _cloneDuration;transform.position = newTransform.position;//选择最近敌人,并且面向他float minDistance = float.MaxValue;Collider2D minDIsCollider = null;Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 25);foreach (var collider in colliders){if(collider.GetComponent<Enemy>() != null){float current = Vector2.Distance(transform.position, collider.transform.position);if (current < minDistance){minDistance = current;minDIsCollider = collider;}}}if(minDIsCollider != null) {if (minDIsCollider.transform.position.x < transform.position.x)transform.Rotate(0, 180, 0);}//有技能管理器决定能否攻击if(canAttack)_anim.SetInteger("AttackCounter", Random.Range(1, 3));}private void AnimationTriggers(){}private void AttackTrigger(){Collider2D[] collider2Ds = Physics2D.OverlapCircleAll(_attackCheck.position, _attackCheckRadius);foreach (var hit in collider2Ds){if (hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}
}

16.投掷技能

16.1.创建一个剑并投掷

1.Animator设置:按鼠标右键(Aim参数 == true)进入瞄准动画,松开(Aim参数 == false)进入投掷动画;

2.逻辑

public class PlayerAimSwordState : PlayerState
{public PlayerAimSwordState(Player player, PlayerStateMachine playerStateMachine, string animName) : base(player, playerStateMachine, animName){}//事件创建剑public override void Enter(){base.Enter();}public override void Exit(){base.Exit();}public override void Update(){base.Update();if(Input.GetKeyUp(KeyCode.Mouse1)) _playerStateMachine.ChangeState(_player._idleState);}
}

事件

    private void ThrowSword(){SkillManager._instance._sword.CreateSword();}

使用预设体创建剑物体并对剑物体设置

    [SerializeField] private GameObject _swordPrefab;[SerializeField] private float _graivty;[SerializeField] private Vector2 _launchForce;public void CreateSword(){GameObject newSword = Instantiate(_swordPrefab, _player.transform.position, transform.rotation);Sword_Skill_Control swordControl = newSword.GetComponent<Sword_Skill_Control>();swordControl.SetupSword(_graivty, _launchForce);}

下面代码挂载的剑预设体上 

    public void SetupSword(float graivty, Vector2 launch){_rb.gravityScale = graivty;_rb.velocity = launch;}

16.2.瞄准点设置

1.进入瞄准激活点,退出瞄准非激活点

public class PlayerAimSwordState : PlayerState
{public PlayerAimSwordState(Player player, PlayerStateMachine playerStateMachine, string animName) : base(player, playerStateMachine, animName){}//事件创建剑public override void Enter(){base.Enter();SkillManager._instance._sword.DotsActive(true);}public override void Exit(){base.Exit();SkillManager._instance._sword.DotsActive(false);}
}

2.start中使用DotsActive创建一批点,Update使用DotsPosition()来计算不同的位置;

  • AimDirection:计算和player的位置;Camera.main.ScreenToWorldPoint()函数计算世界位置:超出游戏画面也能计算位置,在3D可以计算Z位置;
  • DotsPosition:斜抛公式计算点具体位置;
  • DotsActive创建一批点;
public class Sword_Skill : Skill
{[SerializeField] private GameObject _swordPrefab;[SerializeField] private float _graivty;[SerializeField] private Vector2 _launchForce;private Vector2 _finalDirection;[Header("Dot Info")][SerializeField] private GameObject _dotPrefab;[SerializeField] private int _numberDots;[SerializeField] private float _spaceBetweenDots;[SerializeField] private Transform _parentTransform;private GameObject[] _dots;protected override void Start(){base.Start();GenerateDots();}protected override void Update(){if (Input.GetKey(KeyCode.Mouse1))_finalDirection = new Vector2(AimDirection().normalized.x * _launchForce.x, AimDirection().normalized.y * _launchForce.y);if(Input.GetKey(KeyCode.Mouse1)){for (int i = 0; i < _dots.Length; i++){_dots[i].transform.position = (Vector2)_player.transform.position+ DotsPosition(i * _spaceBetweenDots);}}}public Vector2 AimDirection(){Vector2 playerPosition =_player.transform.position;Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);Vector2 direction = mousePosition - playerPosition;return direction;}public void DotsActive(bool isActive){for (int i = 0; i < _dots.Length; i++){_dots[i].SetActive(isActive);}}public void GenerateDots(){_dots = new GameObject[_numberDots];for (int i = 0; i < _numberDots; i++){_dots[i] = Instantiate(_dotPrefab, _player.transform.position, Quaternion.identity, _parentTransform);_dots[i].SetActive(false);}}//斜抛公式  Physics2D.gravity为[0,-9.8];public Vector2 DotsPosition(float time){Vector2 position = new Vector2(AimDirection().normalized.x * _launchForce.x, AimDirection().normalized.y * _launchForce.y) * time + 0.5f * (Physics2D.gravity * _graivty) * time * time;return position;}
}

17.黑洞技能

17.1.创建黑洞,添加热键,使用热键添加敌人transform

1.创建一个黑洞,创建一个圆形,将碰撞器设置为是触发,使用Vector2.Lerp前面快速增长后面慢速增长

    [SerializeField] private float _maxSize;[SerializeField] private float _growSpeed;[SerializeField] private bool _isGrow;[SerializeField] private List<Transform> _targets;// Update is called once per framevoid Update(){if(_isGrow){//取插值,例【0,0】,【5,5】,0.5f,返回【2.5,2.5】//随着时间范围越来越小,增长的也越来越小transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(_maxSize,_maxSize),_growSpeed *Time.deltaTime);}}

2.创建一个UI预设体方便使用

3.随着黑洞不断扩张,当碰到敌人时,从热键List内获取一个唯一的热键,传递给热键UI预设体脚本,将文本变成对应热键,按下对应热键将敌人的位置添加进List备用;

    public void SetupHotKey(KeyCode myKeyCode, Transform enemy, BlackHole_Skill_Controller blackHoleScript){_sr = GetComponent<SpriteRenderer>();//获得唯一的热键,并且文本也设置为此热键_myText = GetComponentInChildren<TextMeshProUGUI>();_myKeyCode = myKeyCode;_myText.text = _myKeyCode.ToString();_enemy = enemy;_blackHoleScript = blackHoleScript;}// Update is called once per framevoid Update(){if(Input.GetKeyUp(_myKeyCode)) {_blackHoleScript.AddEnemy(_enemy);_sr.color = Color.clear;_myText.color =Color.clear;}}
private void OnTriggerEnter2D(Collider2D collision){if(collision.GetComponent<Enemy>() != null){collision.GetComponent<Enemy>().FreezeTimeTrue();CreateHotKey(collision);}}private void CreateHotKey(Collider2D collision){if (_keyCodeList.Count == 0){Debug.Log("Not enough keys in hot key list");return;}//实例化热键GameObject newHotKey = Instantiate(_hotKeyPre, collision.transform.position + new Vector3(0, 2), Quaternion.identity);KeyCode chosenKeyCode = _keyCodeList[Random.Range(1, _keyCodeList.Count)];_keyCodeList.Remove(chosenKeyCode);BlackHole_HotKey_Controller newHotkeyScript = newHotKey.GetComponentInChildren<BlackHole_HotKey_Controller>();newHotkeyScript.SetupHotKey(chosenKeyCode, collision.transform, this);}public void AddEnemy(Transform enemyTransform){_targets.Add(enemyTransform);}

17.2.按下R键在敌人任一一侧创建克隆攻击,攻击完毕应销毁黑洞

    void Update(){if (Input.GetKeyDown(KeyCode.R) && _amountOfAttack > 0){DestroyHotkey();_cloneAttackReleased = true;}_attackTime -= Time.deltaTime;if (_attackTime < 0 && _cloneAttackReleased){_attackTime = _attackCoolDown;int random = Random.Range(0, _targets.Count);//随机放置在对象的两侧float xOffset;if (Random.Range(1, 100) > 50)xOffset = 2;elsexOffset = -2;//调用克隆攻击SkillManager._instance._clone.CreateClone(_targets[random], new Vector3(xOffset,0));_amountOfAttack--;if (_amountOfAttack <= 0){_cloneAttackReleased = false;}}if (_isShrink){transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(-1, -1), _shrinkSpeed * Time.deltaTime);if (transform.localScale.x < 0)Destroy(gameObject);}}private void DestroyHotkey(){if (_createHotKeyList.Count == 0)return;else{for(int i = 0; i < _createHotKeyList.Count; i++){Destroy(_createHotKeyList[i]);}}}

17.3.

17.3.1创建玩家黑洞状态,进入状态飞到最高点时透明,结束变为默认;黑洞释放完毕应将玩家状态设置为Air状态正常掉落

    public override void Enter(){base.Enter();_stateTime = _flyTime;_defaultGravity = _player._rb.gravityScale;_player._rb.gravityScale = 0;_skillUsed =false;}public override void Exit(){base.Exit();_player._rb.gravityScale = _defaultGravity;_player.Transparent(false);}public override void Update(){base.Update();_stateTime -= Time.deltaTime;if (_stateTime > 0){_player.SetRigidbobyVolecity(0, _flySpeed);}else{_player.SetRigidbobyVolecity(0, -0.1f);if(!_skillUsed) {//飞到最高点变透明_player.Transparent(true);SkillManager._instance._blackHole.CanUseSkill();_skillUsed = true;}//黑洞攻击结束if (SkillManager._instance._blackHole._blackHoleScript.CanChangeAir())_playerStateMachine.ChangeState(_player._airState);}}
}

17.3.2.黑洞技能脚本设置黑洞脚本的值

    [SerializeField] private float _maxSize;[SerializeField] private float _growSpeed;[SerializeField] private float _shrinkSpeed;[SerializeField] private float _attackCoolDown = 0.3f;[SerializeField] private int _amountOfAttack = 4;[SerializeField] private float _blackHoleDuration;[SerializeField] private GameObject _blackHole;public BlackHole_Skill_Controller _blackHoleScript { get; private set; }public override bool CanUseSkill(){return base.CanUseSkill();}protected override void UseSkill(){base.UseSkill();GameObject blackHole = Instantiate(_blackHole, _player.transform.position, Quaternion.identity);_blackHoleScript = blackHole.GetComponent<BlackHole_Skill_Controller>();_blackHoleScript.SetupBlackHole(_maxSize, true, false,_growSpeed, _shrinkSpeed, _attackCoolDown, _amountOfAttack, _blackHoleDuration);}
}

17.3.3.设置黑洞持续时间,时间耗尽如果有对象可攻击则进入攻击,没有则结束黑洞;黑洞结束则可以切换玩家至air状态

  void Update(){CLoneAttackLogic();}private void CLoneAttackLogic(){if (Input.GetKeyDown(KeyCode.R) && _amountOfAttack > 0){DestroyHotkey();_cloneAttackReleased = true;}_attackTime -= Time.deltaTime;_blackHoleDuration -= Time.deltaTime;//黑洞持续时间耗尽,有可攻击对象攻击,没有退出if (_blackHoleDuration < 0){_blackHoleDuration = Mathf.Infinity;DestroyHotkey();if (_targets.Count > 0){_cloneAttackReleased = true;ReleaseAttack();}elseFinishBlackHoleAbility();}ReleaseAttack();}private void ReleaseAttack(){if (_attackTime < 0 && _cloneAttackReleased ){if (_targets.Count > 0 && _amountOfAttack > 0){_attackTime = _attackCoolDown;int random = Random.Range(0, _targets.Count);//随机放置在对象的两侧float xOffset;if (Random.Range(1, 100) > 50)xOffset = 2;elsexOffset = -2;//调用克隆攻击SkillManager._instance._clone.CreateClone(_targets[random], new Vector3(xOffset, 0));_amountOfAttack--;}//攻击次数耗尽或者没有可攻击目标FinishBlackHoleAbility();}}public bool CanChangeAir(){return _canChangeAirState;}private void FinishBlackHoleAbility(){if (_targets.Count == 0 || _amountOfAttack == 0){_cloneAttackReleased = false;_isShrink = true;//从黑洞状态切换到空中状态_canChangeAirState = true;}}

18.水晶技能

18.1.返回记录点

  • 创建一个水晶的旋转和销毁动画
  • 逻辑:如果没有水晶,在当前位置创建一个水晶,如果有则销毁水晶,交换位置后水晶销毁;耗尽水晶时间也没有使用,销毁水晶;
  • 时间消耗殆尽或在已有水晶的情况下再次释放,切换到爆炸动画,此动画有爆炸伤害和销毁事件
  • 设置移动模式,不在可以传送,向最近敌人移动,移动到一定距离发生爆炸
    void Start(){}void Update(){_cristalDuration -= Time.deltaTime;if(_canMove && _closestEnemy){transform.position = Vector2.MoveTowards(transform.position, _closestEnemy.position, _moveSpeed * Time.deltaTime);//如果距离小于1则爆炸if (Vector2.Distance(transform.position, _closestEnemy.position) < 1){_canMove = false;FinishCristal();}}if (_cristalDuration < 0){FinishCristal();}if(_canGrow)transform.localScale = Vector2.Lerp(transform.localScale, new Vector2(_maxSize, _maxSize), _growSpeed * Time.deltaTime);}public void FinishCristal(){if (_canExplode){_anim.SetBool("Explode", true);_canGrow = true;}else{SelfDestroy();}}private void AnimtionExplodeFinish(){Collider2D[] collider2Ds = Physics2D.OverlapCircleAll(transform.position, _ccd.radius);foreach (var hit in collider2Ds){if (hit.GetComponent<Enemy>() != null)hit.GetComponent<Enemy>().Damage();}}public void SetupCristal(float cristalDuration, bool canExpolode, bool canMove, float moveSpeed, Transform newTransform){_cristalDuration = cristalDuration;_canExplode = canExpolode;_canMove = canMove;_moveSpeed = moveSpeed; _closestEnemy = newTransform;}private void SelfDestroy(){Destroy(gameObject);}
}
    protected override void UseSkill(){base.UseSkill();if (_currentCristal == null){_currentCristal = Instantiate(_cristalPrefab, _player.transform.position, Quaternion.identity);Cristal_Skill_Controller cristalScript = _currentCristal.GetComponent<Cristal_Skill_Controller>();cristalScript.SetupCristal(_cristalDuration, _canExplode, _canMove, _moveSpeed, FindClosestEnemy(cristalScript.transform));}else//已有水晶,交换位置{if (_canMove)return;Vector2 playerPosition = _player.transform.position;_player.transform.position = _currentCristal.transform.position;_currentCristal.transform.position = playerPosition;_currentCristal.GetComponent<Cristal_Skill_Controller>()?.FinishCristal();}}
}

18.2.检测最近敌人,写在技能基类,派生类继承

    protected Transform FindClosestEnemy(Transform chooseTransform){//选择最近敌人,并且面向他float minDistance = float.MaxValue;Collider2D minDIsCollider = null;Collider2D[] colliders = Physics2D.OverlapCircleAll(chooseTransform.position, 25);foreach (var collider in colliders){if(collider.GetComponent<Enemy>() != null){float current = Vector2.Distance(chooseTransform.position, collider.transform.position);if (current < minDistance){minDistance = current;minDIsCollider = collider;}}}return minDIsCollider.transform;}

18.3.水晶攻击模式,有x使用次数,耗尽进入冷却补充,在一定时间内如果没有使用完也会进入冷却补充

    [Header("Multi Stack Cristal")][SerializeField] private bool _canMultiStack;[SerializeField] private int _amountOfCrystal;[SerializeField] private float _refillCoolDown;[SerializeField] private float _useCoolDownWindow;[SerializeField] private List<GameObject> _cryst    protected override void UseSkill(){base.UseSkill();if (CanUsemultiStack())return;else_coolDown = 0;}private bool CanUsemultiStack(){if (_canMultiStack){//使用第一个后,在一定时间内如果没有耗尽crystal,将自动填装if (_crystalList.Count > 0){if (_crystalList.Count == _amountOfCrystal)Invoke("ResetAbility", _useCoolDownWindow);_coolDown = 0;//使用将冷却设为0,因为填装将冷却设置了GameObject tailValue = _crystalList[_crystalList.Count - 1];GameObject newGameobeject = Instantiate(tailValue, _player.transform.position, Quaternion.identity);_crystalList.Remove(tailValue);newGameobeject.GetComponent<Crystal_Skill_Controller>().SetupCristal(_crystalDuration, _canExplode, _canMove, _moveSpeed, FindClosestEnemy(newGameobeject.transform));//次数耗尽,填充次数并进入冷却if (_crystalList.Count == 0){_coolDown = _refillCoolDown;RefillCrystal();}}return true;}elsereturn false;}private void RefillCrystal(){int amount = _amountOfCrystal - _crystalList.Count;for (int i = 0; i < amount; i++){_crystalList.Add(_crystalPrefab);}}private void ResetAbility(){if (_coolDownTime > 0)return;_coolDownTime = _refillCoolDown;RefillCrystal();}alList = new List<GameObject>();

19.克隆攻击产生克隆攻击(概率)

  • 在有敌人目标是克隆攻击,再判断是否会再次产生克隆攻击;
    private void AttackTrigger(){Collider2D[] collider2Ds = Physics2D.OverlapCircleAll(_attackCheck.position, _attackCheckRadius);foreach (var hit in collider2Ds){if (hit.GetComponent<Enemy>() != null){hit.GetComponent<Enemy>().Damage();//克隆攻击概率产生克隆攻击if(_canDuplicateClone && _canCreateDuplicate){_canCreateDuplicate = false;if(Random.Range(1, 100) <= _DuplicateChance){SkillManager._instance._clone.CreateClone(hit.transform, new Vector3(1f * _facingDirection, 0, 0));}}}}}

20.角色统计

20.1.每个角色和玩家都需要角色统计来计算数值

添加上角色统计组件,在entity获取组件,子类自动继承

public class CharacterStats : MonoBehaviour
{public int _damage;public int _maxHealth;private int _health;//初始血量private void Start(){_health = _maxHealth;}//造成伤害public void Damage(int damage){_health -= damage;}
}

20.2.玩家和敌人各自重写角色统计函数

  • DoDamage:造成伤害目标位敌人;
  • takeDamage: 承受伤害目标为自己
public class PlayerStats : CharacterStats
{private Player _player;//造成伤害目标位敌人public override void DoDamage(CharacterStats target){base.DoDamage(target);}//承受伤害目标为自己public override void TakeDamage(int damage){base.TakeDamage(damage);_player.DamageEffect();}protected override void Die(){base.Die();}protected override void Start(){base.Start();_player = GetComponent<Player>();}
}
public class EnemyStats : CharacterStats
{private Skeleton_Enemy _enemy;public override void DoDamage(CharacterStats target){base.DoDamage(target);}public override void TakeDamage(int damage){base.TakeDamage(damage);}protected override void Die(){base.Die();}protected override void Start(){base.Start();_enemy.DamageEffect();}
}

20.3.添加主要数值,伤害计算闪避和护甲值

    [Header("Major Stats")]public Stat _strength;//力量:1点增加伤害1点和制造能力1%public Stat _agility;//敏捷:1点增加闪避和制造速度1%public Stat _intelligence;//智力:1点增加魔法伤害1点和法抗1%public Stat _vitality;//活力:1点增加生命值3点[Header("Defensive Stat")]public Stat _maxHealth;//最高血量public Stat _armor;//护甲public Stat _evasion;//闪避public Stat _damage;[SerializeField] protected int _currentHealth;//造成伤害,参数是收到伤害的对象public virtual void DoDamage(CharacterStats target){//计算闪避值,并判断是否闪避成功if (TargetCanAvoidAttack(target))return;//计算总伤害,减去护甲值int totalDamage = CheckTargetArmor(target);target.TakeDamage(totalDamage);}private int CheckTargetArmor(CharacterStats target){int totalDamage = _damage.GetBaseValue() + _strength.GetBaseValue();totalDamage -= target._armor.GetBaseValue();//totalDamage = Mathf.Clamp(totalDamage, 0, int.MaxValue);//value在两者之间返回value,小于最小值返回最小值,大于最大返回最大return totalDamage;}private bool TargetCanAvoidAttack(CharacterStats target){int totalEvasion = target._evasion.GetBaseValue() + target._agility.GetBaseValue();if (Random.Range(1, 100) <= totalEvasion){Debug.Log("ATTACK EVASION");return true;}return false;}

20.4.暴击倍率和暴击概率

  • 暴击倍率 = 初始暴击倍率 + 1点力量加1点暴击倍率
  • 暴击概率 = 初始暴击概率 + 1点敏捷加移动暴击概率
    private bool CanCrit(){int critChance = _critChance.GetBaseValue() + _agility.GetBaseValue();if(Random.Range(1, 100) <= critChance)return true;   return false;}private int CheckTargetArmor(CharacterStats target){int totalDamage = _damage.GetBaseValue() + _strength.GetBaseValue();totalDamage -= target._armor.GetBaseValue();//totalDamage = Mathf.Clamp(totalDamage, 0, int.MaxValue);//value在两者之间返回value,小于最小值返回最小值,大于最大返回最大return totalDamage;}

20.5.计算魔法伤害

  • 魔法伤害 = (1点智力加1点魔法伤害 + 火魔法伤害 + 冰魔法伤害 + 光魔法伤害 )- (魔法抗性 + 1点智力加3点魔法抗性);
    private void ApplyAilments(bool isIgnite, bool isChilled, bool isShocked){if (_isIgnite || _isChilled || _isShocked)return;_isIgnite = isIgnite;_isChilled = isChilled; _isShocked = isShocked;}private int CalculateCritDamage(int totalDamage){float totalCritPower = (_critPower.GetBaseValue() + _strength.GetBaseValue()) * 0.01f;float critDamage = totalDamage * totalCritPower;return Mathf.RoundToInt(critDamage);}

20.6.根据最高的属性魔法给目标添加上状态

    protected virtual void SelectElement( CharacterStats target, int fireDamage, int iceDamage, int lightningDamage){//3种元素伤害都不超过0,则不造成特殊效果if (Mathf.Max(fireDamage, iceDamage, lightningDamage) <= 0)return;//选择最大元素,决定受到的特殊效果bool canApplyIgnited = fireDamage > iceDamage && fireDamage > lightningDamage;bool canApplyChilled = iceDamage > fireDamage && iceDamage > lightningDamage;bool canApplyShocked = lightningDamage > fireDamage && lightningDamage > iceDamage;//如果最大元素伤害相同,随机一个while (!canApplyIgnited && !canApplyChilled && !canApplyShocked){if (fireDamage > 0 && Random.value < 0.33f){canApplyIgnited = true;break;}if (iceDamage > 0 && Random.value < 0.5f){canApplyChilled = true;break;}if (lightningDamage > 0 && Random.value < 1f){canApplyChilled = true;break;}}//设置火焰伤害target.SetupIgniteDamage(Mathf.RoundToInt(fireDamage * 0.2f));target.ApplyAilments(canApplyIgnited, canApplyChilled, canApplyShocked);}private void ApplyAilments(bool isIgnite, bool isChilled, bool isShocked){if (_isIgnite || _isChilled || _isShocked)return;if (isIgnite){_isIgnite = isIgnite;_igniteTimer = 2;//Debug.Log("_isIgnite");}if (isChilled){_isChilled = isChilled;_chilledTimer = 2;//Debug.Log("_isChilled");}if (isShocked){_isShocked = isShocked;_shockTimer = 2;//Debug.Log("_isShocked");}}

20.6.1.根据不同的状态,添加不同的特殊效果;

    [Header("Magic Stat")]public Stat _fireDamage;public Stat _iceDamage;public Stat _lightningDamage;public bool _isIgnite;//点燃,持续造成火焰伤害(自己)public bool _isChilled;//冰冻,减速 + 穿甲 //处于冰冻状态减少20%的护甲(目标)public bool _isShocked;//眩晕,- 命中率  //处于震撼状态减少命中率,及增加目标的闪避值(自己)private float _igniteTimer;//燃烧状态的时间private float _chilledTimer;private float _shockTimer;private float _igniteDamageCoolDown = 0.3f;//燃烧伤害的冷却private float _igniteDamageTimer;//燃烧伤害的更新private int _igniteDamage;protected virtual void Update(){_igniteTimer -= Time.deltaTime;_chilledTimer -= Time.deltaTime;_shockTimer -= Time.deltaTime;_igniteDamageTimer -= Time.deltaTime;//燃烧间隔if (_igniteTimer < 0)_isIgnite = false;if(_chilledTimer < 0)_isChilled = false;if(_shockTimer < 0)_isShocked = false;if(_isIgnite == true && _igniteDamageTimer < 0){_currentHealth -= _igniteDamage;_igniteDamageTimer = _igniteDamageCoolDown;Debug.Log("Ignite Damage" + _igniteDamage);}}

20.7.不同状态不同的FX视觉效果

  • 采用两种颜色切换来做效果

    [Header("Ailment Color")]public Color[] _ignitedColor;public Color[] _chilledColor;public Color[] _shockColor;private void CancelColorChange(){CancelInvoke();sr.color = Color.white;}public void IgniteFxFor(float s){InvokeRepeating("IgniteColorFX", 0, 0.3f);Invoke("CancelColorChange", s);}public void ChilledFXFor(float s){InvokeRepeating("ChilledColorFX", 0, 0.3f);Invoke("CancelColorChange", s);}public void ShockFXFor(float s){InvokeRepeating("ShockColorFX", 0, 0.3f);Invoke("CancelColorChange", s);}private void IgniteColorFX(){if(sr.color != _ignitedColor[0])sr.color= _ignitedColor[0];elsesr.color = _ignitedColor[1];}private void ChilledColorFX(){if (sr.color != _chilledColor[0])sr.color = _chilledColor[0];elsesr.color = _chilledColor[1];}private void ShockColorFX(){if (sr.color != _shockColor[0])sr.color = _shockColor[0];elsesr.color = _shockColor[1];

20.8.减速效果

  • 记录速度默认值
  • 减少动画速度和移动速度等,%的速度
    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);}public override void ReturnDefaultSpeed(){base.ReturnDefaultSpeed();_moveSpeed = _moveDefaultSpeed;_jumpForce = _jumpDefaultForce;_dashSpeed = _dashDefaultSpeed;}

20.9.雷击

  • 如果已处于震撼状态,再被施加震撼状态,将会在此敌人对最近敌人(没有则此敌人)发射雷击;
public class ThunderStrike_Contorller : MonoBehaviour
{private CharacterStats _targetStats;private float _speed;private int _damage;private bool _triggered;private Animator _anim;void Start(){_anim = GetComponentInChildren<Animator>();}public void SetupThunderStrike(CharacterStats targetStats, int damage, float speed){_targetStats = targetStats;_damage = damage;_speed = speed;}void Update(){if (_targetStats == null)return;//已触发雷击效果,不在触发if (_triggered)return;//向目标靠近transform.position = Vector2.MoveTowards(transform.position, _targetStats.transform.position, _speed * Time.deltaTime);transform.right = transform.position - _targetStats.transform.position;//雷电和目标距离小于0.1,切换攻击动画if(Vector2.Distance(transform.position, _targetStats.transform.position) < 0.1f){//打击动画变大_anim.transform.localRotation = Quaternion.identity;_anim.transform.localPosition = new Vector3(0, 0.5f);//与父物体的相对位置,使雷击和地面有一定距离transform.localRotation = Quaternion.identity;transform.localScale = new Vector3(3, 3);//造成伤害和切换动画Invoke("DamageAndSelfDestroy", 0.2f);//让动画先播一会,效果更好_anim.SetBool("Hit", true);_triggered = true;}}private void DamageAndSelfDestroy(){_targetStats.ApplyShock(true);_targetStats.TakeDamage(1);Destroy(gameObject, 0.4f);}
}
        if (isShocked && canApplyShock){if (_isShocked == false)//没有震撼状态{ApplyShock(isShocked);}else//已有震撼状态{//玩家不受此效果if (GetComponent<Player>() != null)return;HitNearesTagetWithShockStrike();}}private void HitNearesTagetWithShockStrike(){float minDistance = float.MaxValue;Transform closestEnemy = null;Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, 25);foreach (var collider in colliders){//不选择自己if (collider.GetComponent<Enemy>() != null && Vector2.Distance(collider.transform.position, transform.position) > 0.1f){float current = Vector2.Distance(transform.position, collider.transform.position);if (current < minDistance){minDistance = current;closestEnemy = collider.transform;}}}if (closestEnemy == null)closestEnemy = transform;GameObject newThunderstrike = Instantiate(_thunderStrikePrefab, transform.position, Quaternion.identity);ThunderStrike_Contorller newScript = newThunderstrike.GetComponent<ThunderStrike_Contorller>();newScript.SetupThunderStrike(closestEnemy.GetComponent<CharacterStats>(), _shockDamage, _ThunderStrickSpeed);}

21.装备背包栏

21.1.ScriptableObject类

  • 继承ScriptableObject类,类内写物品信息
public class ItemData : ScriptableObject //继承ScriptableObject类
{//物品信息public string _itemName;public Sprite _Icon;}
  • 创建资产菜单,才可以创建物品 
[CreateAssetMenu(fileName = "New Item Menu", menuName = "Item/Date")]

 

  • 如何使用使用物品 
public class ItemObject : MonoBehaviour
{[SerializeField] private ItemData _itemData;//获取物品数据private SpriteRenderer _sr;void Start(){_sr = GetComponent<SpriteRenderer>();_sr.sprite = _itemData._icon;}private void OnTriggerEnter2D(Collider2D collision){if (collision.GetComponent<Player>() != null){Debug.Log("Pick up item" + _itemData._itemName);Destroy(gameObject);}}
}

21.2.对物品添加和对物品的包装

  • 对物品的包装,添加堆叠数量 
[Serializable]
public class InventoryItem
{private ItemData _itemData;public int _stackSize;//构造函数,对ItemData的包装public InventoryItem(ItemData itemData){_itemData = itemData;_stackSize = 1;}public void AddStack() => _stackSize++;public void RemoveStack() => _stackSize--;
}
  • 添加物品添加进链表,字典树查找物品的堆叠数量; 
public class Inventory : MonoBehaviour
{public static Inventory _Instance;public List<InventoryItem> _inventoryItems;public Dictionary<ItemData, InventoryItem> _inventoryDictionary;private void Awake(){if (_Instance == null)_Instance = this;elseDestroy(this);}private void Start(){_inventoryItems = new List<InventoryItem>();_inventoryDictionary = new Dictionary<ItemData, InventoryItem>();}public void AddItem(ItemData item){if (_inventoryDictionary.TryGetValue(item, out InventoryItem value)){value.AddStack();}else//还没有此物品,添加新物品{InventoryItem newItem = new InventoryItem(item);_inventoryItems.Add(newItem);_inventoryDictionary[item] = newItem;}}public void RemoveItem(ItemData item){if (_inventoryDictionary.TryGetValue(item, out InventoryItem value)){if (value._stackSize <= 1){_inventoryItems.Remove(value);_inventoryDictionary.Remove(item);}elsevalue.RemoveStack();}}
}

21.3.物品栏的制作

  •  先创建画布和图像和文本,设置为屏幕大小缩放和分辨率为1920和1080;

  • 每当拾取和丢弃物品时,更新物品栏;
public class UI_ItemStack : MonoBehaviour
{[SerializeField] private Image _image;[SerializeField] private TextMeshProUGUI _itemText;public InventoryItem _item;public void UpdateSlot(InventoryItem itemData){GetComponent<Image>().color = Color.white;//有物品不在透明_item = itemData;if (_item != null){if (_item._stackSize > 1){_image.sprite = _item._itemData._icon;_itemText.text = _item._stackSize.ToString();}else{_image.sprite = _item._itemData._icon;_itemText.text = "";}}}
}

20.4.将物品分为材料和装备,使用enum分别,武器(继承物品类)有分为武器、铠甲、护身符、携带物,也使用enum分别;

//物品类
public enum Itemtype
{Material,Equipment
}[CreateAssetMenu(fileName = "New Item Menu", menuName = "Data/Item")]
public class ItemData : ScriptableObject //继承ScriptableObject类
{//物品信息public string _itemName;public Sprite _icon;public Itemtype _itemtype;
}//装备类继承物品类
public enum Equipment
{Weapon, //武器Armor,  //盔甲Amulet, //护身符Flask   //携带物
}[CreateAssetMenu(fileName = "New Item Menu", menuName = "Data/Equipment")]
public class ItemDataEquipment : ItemData
{public Equipment _quipmentType;
}

添加或删除物品后更新物品栏 

    //更新物品栏private void UpdateSlotUI(){for(int i = 0; i < _inventory.Count; i++){_inventoryItemSlots[i].UpdateSlot(_inventory[i]);}for(int i = 0; i < _stash.Count; i++){_stashItemSlots[i].UpdateSlot(_stash[i]);}}//已有此物品添加数量,没有添加物品public void AddItem(ItemData item){if (item._itemtype == Itemtype.Material)AddToInventory(item);else if (item._itemtype == Itemtype.Equipment)AddToStash(item);UpdateSlotUI();}//添加装备至装备栏private void AddToStash(ItemData item){if (_stashDictionary.TryGetValue(item, out InventoryItem stashValue)){stashValue.AddStack();}else{InventoryItem newItem = new InventoryItem(item);_stash.Add(newItem);_stashDictionary[item] = newItem;}}//添加材料至材料栏private void AddToInventory(ItemData item){if (_inventoryDictionary.TryGetValue(item, out InventoryItem value)){value.AddStack();}else//还没有此物品,添加新物品{InventoryItem newItem = new InventoryItem(item);_inventory.Add(newItem);_inventoryDictionary[item] = newItem;}}

20.5.装卸装备

1.继承IPointerDownHandler接口类,重写OnPointerDown类,点击装备栏就会调用此函数

public class UI_ItemStack : MonoBehaviour , IPointerDownHandler
{[SerializeField] private Image _image;[SerializeField] private TextMeshProUGUI _itemText;public InventoryItem _item;//鼠标点击的物体,触发此事件public void OnPointerDown(PointerEventData eventData){if (_item._itemData._itemtype == Itemtype.Equipment){Inventory._Instance.EquipItem(_item._itemData);}}
}

2.装备装备,每种装备类型只能装备一种,有旧装备放回装备储存区,然后装备新装备 

    //装备装备,在装备栏显示//没有装备此类型,直接装备;已装备此类型,去除当前装备再装备新装备public void EquipItem(ItemData itemData){//转化为ItemDataEquipment,获取装备类型来达到比较的目的ItemDataEquipment newEquipment = itemData as ItemDataEquipment;ItemDataEquipment OldEquipment = null;//获取键值对,来比较装备类型foreach (KeyValuePair<ItemDataEquipment, InventoryItem> item in _equipmentDictionary){if (newEquipment._equipmentType == item.Key._equipmentType)OldEquipment = item.Key;}//卸去旧装备,放回装备储藏区if (OldEquipment != null){ItemToRemove(OldEquipment);AddItem(OldEquipment);}//装备新装备InventoryItem newInventory = new InventoryItem(newEquipment);_equipment.Add(newInventory);_equipmentDictionary[newEquipment] = newInventory;RemoveItem(newEquipment);//装备储存栏去除物品}

3.重写此函数,点击卸去装备移除属性

public class UI_EquipmentSlot : UI_ItemStack
{public EquipmentType _equipmentType;//装备类型//点击装备栏物品卸下物品,减去装备属性public override void OnPointerDown(PointerEventData eventData){if (_image == null)return; ItemDataEquipment current = _item._itemData as ItemDataEquipment;Inventory._Instance.Unequipment(current);Inventory._Instance.AddItem(current);CleanUpSlot();}
}

20.6.获得装备属性和敌人随难度提升属性

1.装备类添加属性,每当调用装备函数后就修改值;

[CreateAssetMenu(fileName = "New Item Menu", menuName = "Data/Equipment")]
public class ItemDataEquipment : ItemData
{public EquipmentType _equipmentType;//装备的属性,用于修改玩家属性[Header("Major Stats")]public int _strength;//力量:1点增加伤害1点和暴击倍率1%public int _agility;//敏捷:1点增加闪避和暴击概率1%public int _intelligence;//智力:1点增加魔法伤害1点和法抗3点public int _vitality;//活力:1点增加生命值5点[Header("Offensive Stats")]public int _damage;//基础伤害public int _critPower;//暴击倍率public int _critChance;//暴击概率[Header("Defensive Stats")]public int _maxHealth;//最高血量public int _armor;//护甲public int _evasion;//闪避public int _magicResistance;//魔抗[Header("Magic Stats")]public int _fireDamage;public int _iceDamage;public int _lightningDamage;//装备装备public void AddModifliers(){//获取玩家Player player = PlayerManager._instance._player;//加上属性player._stats._strength.AddModifiers(_strength);player._stats._agility.AddModifiers(_agility);player._stats._intelligence.AddModifiers(_intelligence);player._stats._vitality.AddModifiers(_vitality);player._stats._damage.AddModifiers(_damage);player._stats._critChance.AddModifiers(_critChance);player._stats._critPower.AddModifiers(_critPower);player._stats._maxHealth.AddModifiers(_maxHealth);player._stats._armor.AddModifiers(_armor);player._stats._evasion.AddModifiers(_evasion);player._stats._magicResistance.AddModifiers(_magicResistance);player._stats._fireDamage.AddModifiers(_fireDamage);player._stats._iceDamage.AddModifiers(_iceDamage);player._stats._lightningDamage.AddModifiers(_lightningDamage);}public void RemoveModifliers(){//获取玩家Player player = PlayerManager._instance._player;//减上属性player._stats._strength.RemoveModifiers(_strength);player._stats._agility.RemoveModifiers(_agility);player._stats._intelligence.RemoveModifiers(_intelligence);player._stats._vitality.RemoveModifiers(_vitality);player._stats._damage.RemoveModifiers(_damage);player._stats._critChance.RemoveModifiers(_critChance);player._stats._critPower.RemoveModifiers(_critPower);player._stats._maxHealth.RemoveModifiers(_maxHealth);player._stats._armor.RemoveModifiers(_armor);player._stats._evasion.RemoveModifiers(_evasion);player._stats._magicResistance.RemoveModifiers(_magicResistance);player._stats._fireDamage.RemoveModifiers(_fireDamage);player._stats._iceDamage.RemoveModifiers(_iceDamage);player._stats._lightningDamage.RemoveModifiers(_lightningDamage);}
}

2.敌人随着难度提升属性

    [Header("Level Details")]public int _level = 1;//难度等级[Range(0f, 1.0f)]public float _percentageModifiers = .4f ;//每提升一级难度属性提升此%protected override void Start(){ApplyLevelModifiers();base.Start();_enemy = GetComponent<Skeleton_Enemy>();}private void ApplyLevelModifiers(){Modify(_strength);Modify(_agility);Modify(_intelligence);Modify(_vitality);Modify(_damage);Modify(_critPower);Modify(_critChance);Modify(_maxHealth);Modify(_armor);Modify(_evasion);Modify(_magicResistance);Modify(_fireDamage);Modify(_iceDamage);Modify(_lightningDamage);}public virtual void Modify(Stat stat){for(int i = 0; i < _level; i++){float modifier = stat.GetFinishValue() * _percentageModifiers;stat.AddModifiers(Mathf.RoundToInt(modifier));}}

20.7.制作物品

1.武器数据类添加一个制作要求列表

public class ItemDataEquipment : ItemData
{[Header("Craft Requirements")]public List<InventoryItem> _craftMaterials;//所需材料表
}

制造UI添加此武器数据,点击制作UIslot,将会制作

public class UI_CraftSlot : UI_ItemStack
{private void OnEnable(){UpdateSlot(_item);}public override void OnPointerDown(PointerEventData eventData){ItemDataEquipment itemToCraft = _item._itemData as ItemDataEquipment;//点击则会制造物品if(Inventory._Instance.CanCraft(itemToCraft, itemToCraft._craftMaterials)){//制作成功可以播放成功欢快的音乐;}}}

2.比较仓库材料和制作所需材料是否足够,满足至制作

    //制造装备public bool CanCraft(ItemDataEquipment itemOfCraft, List<InventoryItem> craftRequirements){List<InventoryItem> materialToRemove = new List<InventoryItem>();//记录制作所需的材料//比较是否有足够的材料for(int i = 0; i < craftRequirements.Count; i++){if (_inventoryDictionary.TryGetValue(craftRequirements[i]._itemData, out InventoryItem inventoryValue)){if (craftRequirements[i]._stackSize <= inventoryValue._stackSize){materialToRemove.Add(craftRequirements[i]);}else{Debug.Log("not enough material");return false;}}else{Debug.Log("not enough material");return false;}}//移除制作所需材料for(int i = 0; i < materialToRemove.Count; i++){for(int j = 0; j < materialToRemove[i]._stackSize; j++){RemoveItem(materialToRemove[i]._itemData);}}

20.8.敌人死亡掉落物品

1.因为爆出物品,物品会向左右射出需要使用碰撞器和刚体,所以让子类来做物品拾捡检测

public class ItemTrigger : MonoBehaviour
{private ItemObject _itemObject => GetComponentInParent<ItemObject>();private void OnTriggerEnter2D(Collider2D collision){if (collision.GetComponent<Player>() != null){_itemObject.PickUpItem();}}
}
public class ItemObject : MonoBehaviour
{private ItemData _itemData;//获取物品数据private Rigidbody2D _rb => GetComponent<Rigidbody2D>();//爆出物品设置物品种类和弹射速度public void SetupItem(ItemData itemData, Vector2 volecity){_itemData = itemData;_rb.velocity = volecity;OnValidate();}    //当被加载和面板被修改将会调用private void OnValidate(){if (_itemData == null)return;GetComponent<SpriteRenderer>().sprite = _itemData._icon;gameObject.name = "item object - " + _itemData.name.ToString();}//因为爆出物品,物品会向左右射出需要使用碰撞器和刚体,所以让子类来做物品拾捡检测public void PickUpItem(){Debug.Log("Pick up item" + _itemData._itemName);Inventory._Instance.AddItem(_itemData);Destroy(gameObject);}
}

物品掉落类挂载在具体的敌人上, 此脚本有一个掉落物品列表,当敌人死亡调用物品掉落函数;

  • 掉落物品:物品有掉落率,算出是否掉落后;再判断掉落数量:材料类可以掉落多个,装备一次只能掉落一个;
[Serializable]
struct DropChance
{public DropChance(int dropChance, ItemData itemData){_dropChance = dropChance;_itemData = itemData;}[Range(0, 100)]public int _dropChance;public ItemData _itemData;
}public class ItemDrop : MonoBehaviour
{[SerializeField] private GameObject _itemObjectPrefab;//物品对象//可能掉落物品列表,为固长[SerializeField] private DropChance[] _possibleIDroptem;//敌人死亡后改掉落的物品private List<ItemData> _dropList = new List<ItemData>();public void GeneralDrop(){for (int i = 0; i < _possibleIDroptem.Length; i++){//将物品加入即将掉落列表if (UnityEngine.Random.Range(1, 100) <= _possibleIDroptem[i]._dropChance)_dropList.Add(_possibleIDroptem[i]._itemData);}for (int i = 0; i < _dropList.Count; i++){//材料可能掉落多个if (_dropList[i]._itemtype == Itemtype.Material){int j = UnityEngine.Random.Range(1, 3);while (j > 0){DropItem(_dropList[i]);j--;}}elseDropItem(_dropList[i]);}_dropList.Clear();//最后清理}public void DropItem(ItemData itemData){//此物体挂载在敌人上ItemObject newItemObject = Instantiate(_itemObjectPrefab, transform.position, Quaternion.identity).GetComponent<ItemObject>();//随机速度Vector2 randomVolecity = new Vector2(UnityEngine.Random.Range(-5, 5), UnityEngine.Random.Range(12, 18));newItemObject.SetupItem(itemData, randomVolecity);}
}

20.9.玩家死亡掉落物品

1.玩家死亡后调用此函数;

public class PlayerItemDrop : ItemDrop
{[Header("Player's Drop ")]public float _chanceToLoseItems;public float _chanceToLoseMaterial;public override void GeneralDrop(){//记录需要删除的元素,不能边遍历边删除List<InventoryItem> itemToUnequip = new List<InventoryItem>();List<InventoryItem> MaterialToLose = new List<InventoryItem>();foreach(InventoryItem item in Inventory._Instance.GetEquipmentList()) {//死亡判断是否掉落装备if(Random.Range(1, 100) <= _chanceToLoseItems){Debug.Log("Item drop" + item._itemData._itemName);DropItem(item._itemData);itemToUnequip.Add(item);}}//移除装备for(int i = 0; i < itemToUnequip.Count; i++)Inventory._Instance.Unequipment(itemToUnequip[i]._itemData as ItemDataEquipment);foreach(InventoryItem material in Inventory._Instance.GetMaterialList()){if(Random.Range(1, 100) <= _chanceToLoseMaterial){Debug.Log("Item drop" + material._itemData._itemName);DropItem(material._itemData);MaterialToLose.Add(material);}}for (int i = 0; i < MaterialToLose.Count; i++)Inventory._Instance.RemoveItem(MaterialToLose[i]._itemData);}
}

2.Ctrl+鼠标左键删除物品,UI_ItemStack类

    //鼠标点击的物体,触发此事件public virtual void OnPointerDown(PointerEventData eventData){//Ctrl+鼠标左键删除物品if(Input.GetKey(KeyCode.LeftControl)) {Inventory._Instance.RemoveItem(_item._itemData);return;}if (_item._itemData._itemtype == Itemtype.Equipment){Inventory._Instance.EquipItem(_item._itemData);}}

21.1.装备特效制作

装备特效类

[CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Item effect")]
public class ItemEffect : ScriptableObject
{public virtual void ExecuteEffect(){Debug.Log("Execute Effect");}
}

装备类添加装备特效列表

public class ItemDataEquipment : ItemData
{//装备特效public ItemEffect[] _itemEffects;//使用此武器的特效public void ItemEffect(){foreach(var effect in _itemEffects){effect.ExecuteEffect();}}
}

传入装备类型获得当前装备 

    //获取武器特效public ItemDataEquipment GetEquipmentEffect(EquipmentType key){ItemDataEquipment equipment = null;foreach(var item in _equipmentDictionary){if(item.Key._equipmentType == key){equipment = item.Key;}}return equipment;}

普通攻击位置调用装备的特效函数 

//使用武器的攻击特效
Inventory._Instance.GetEquipmentEffect(EquipmentType.Weapon)?.ItemEffect();

21.1.雷击特效 

 继承装备特效,添加预设体,;当被攻击时实例雷击预设体

[CreateAssetMenu(fileName = "Thunder Strike Data", menuName = "Data/Item effect/Thunder strike")]
public class ThunderStrickEffect : ItemEffect
{[SerializeField] private GameObject _thunderStrikePrefab;public override void ExecuteEffect(Transform enemyTransform){GameObject thunderStrike = Instantiate(_thunderStrikePrefab, enemyTransform.position, Quaternion.identity);Destroy(thunderStrike, 0.5f);}
}

  被雷击预设体碰撞器碰撞,造成伤害

public class ThunderStrikeController : MonoBehaviour
{//计算伤害private void OnTriggerEnter2D(Collider2D collision){//获取统计组件,用于计算PlayerStats _playerStat = PlayerManager._instance._player.GetComponent<PlayerStats>();if (collision.GetComponent<Enemy>() != null){EnemyStats enemyStats = collision.GetComponent<EnemyStats>();_playerStat.DoDamage(enemyStats);}}
}

21.2.冰火特效 

第三次攻击触发        

[CreateAssetMenu(fileName = "IceAndFire Data", menuName = "Data/Item effect/Ice And Fire")]
public class IceAndFireEffect : ItemEffect
{public GameObject _iceAndFirePrefab;public float _xVelocity;public override void ExecuteEffect(Transform enemyTransform){Player player = PlayerManager._instance._player;if(player._attackState.comboCounter == 2 ){GameObject iceAndFire = Instantiate(_iceAndFirePrefab, player.transform.position, player.transform.rotation);//设置速度iceAndFire.GetComponent<Rigidbody2D>().velocity = new Vector2(_xVelocity * player.facingDir, 0);}}
}

21.3护身符特效:技能将造成装备特效

在造成伤害位置,检测是否护身符和此护身符是否有装备特效 

    private void SwordSkillDamage(Enemy enemy){_player._stats.DoDamage(enemy.GetComponent<CharacterStats>());//造成装备特性ItemDataEquipment amulet = Inventory._Instance.GetEquipment(EquipmentType.Amulet);if (amulet != null){amulet.ItemEffect(enemy.transform);}}

21.4.回血特效

[CreateAssetMenu(fileName = "Health Effect Data", menuName = "Data/Item effect/Health Effect")]
public class HealthEffect : ItemEffect
{[Range(0f, 1f)][SerializeField] private float _healthPercent;public override void ExecuteEffect(Transform enemyTransform){//获取玩家统计PlayerStats playerStats = PlayerManager._instance._player.GetComponent<PlayerStats>();//每次增加一定比例的最大血量int health = Mathf.RoundToInt(playerStats.GetMaxHealth() * _healthPercent);playerStats.IncreaseHealthBy(health);}
}

 制作血瓶

    public void UseFlask(){ItemDataEquipment currentFlask = GetEquipment(EquipmentType.Flask);//已装备瓶子if (currentFlask != null){//不在冷却if ( Time.deltaTime > _flaskLaskCoolDown + currentFlask._coolDown){foreach (var effect in currentFlask._itemEffects)effect.ExecuteEffect(null);_flaskLaskCoolDown = Time.deltaTime;}elseDebug.Log("Flask On CoolDown");}elseDebug.Log("Not equip Flask");}

21.5.BUFF制作


public enum StatType
{Strength,Agility,Intelegence,Vitality,Damage,CritChance,CritPower,Health,Armor,Evasion,MagicRes,FireDamage,IceDamage,LightingDamage
}[CreateAssetMenu(fileName = "BUFF Effect", menuName = "Data/Item effect/BUFF Effect")]
public class BUFF_Effect : ItemEffect
{private PlayerStats _playerStat;[SerializeField] private int _modifier;//数值[SerializeField] private float _rotetion;[SerializeField] private StatType _buffType;public override void ExecuteEffect(Transform enemyTransform){_playerStat = PlayerManager._instance._player.GetComponent<PlayerStats>();//调用BUFF函数增加属性_playerStat.IncreaseStatBy(_modifier, _rotetion, StatToModify());}private Stat StatToModify(){if (_buffType == StatType.Strength) return _playerStat._strength;else if (_buffType == StatType.Agility) return _playerStat._agility;else if (_buffType == StatType.Intelegence) return _playerStat._intelligence;else if (_buffType == StatType.Vitality) return _playerStat._vitality;else if (_buffType == StatType.Damage) return _playerStat._damage;else if (_buffType == StatType.CritChance) return _playerStat._critChance;else if (_buffType == StatType.CritPower) return _playerStat._critPower;else if (_buffType == StatType.Health) return _playerStat._maxHealth;else if (_buffType == StatType.Armor) return _playerStat._armor;else if (_buffType == StatType.Evasion) return _playerStat._evasion;else if (_buffType == StatType.MagicRes) return _playerStat._magicResistance;else if (_buffType == StatType.FireDamage) return _playerStat._fireDamage;else if (_buffType == StatType.IceDamage) return _playerStat._iceDamage;else if (_buffType == StatType.LightingDamage) return _playerStat._lightningDamage;return null;}
}

使用协程,对目标在一定的时间内增加数值 

    //buff效果, 数值、持续时间、目标public virtual void IncreaseStatBy(int modifier, float rotetion, Stat statToModify ){StartCoroutine(StatModifyCoroutinr(modifier, rotetion, statToModify));  }protected IEnumerator StatModifyCoroutinr(int modifier, float rotetion, Stat statToModify){statToModify.AddModifiers(modifier);yield return new WaitForSeconds(rotetion);statToModify.RemoveModifiers(modifier);}

21.6.护甲装备时间停止

当满足不在冷却和血量低于10%时,才可触发; 

[CreateAssetMenu(fileName = "FreezeEnemies Effect", menuName = "Data/Item effect/FreezeEnemies Effect")]
public class FreezeEnemiesEffect : ItemEffect
{[SerializeField] private float _rotetion;//冻结的持续时间public override void ExecuteEffect(Transform playerTransform){//血量低于10%才会触发PlayerStats playerStats = playerTransform.GetComponent<PlayerStats>();if (playerStats._currentHealth > playerStats._maxHealth.GetFinishValue() * 0.1f)return;//在冷却中if (!Inventory._Instance.CanUseArmor())return;Collider2D[] collider2Ds = Physics2D.OverlapCircleAll(playerTransform.position, 2);foreach (var hit in collider2Ds){//在角色统计的伤害计算里面,判断是否装备了此特效的物品hit.GetComponent<Enemy>()?.FreezeTimeFor(_rotetion);}}
}

22.制作UI菜单 

2

1.在UI画布,添加图像,将源图像设置为目标图像;

2.在页眉使用对应的源图像,并添加Grid Layout Group组件

3.创建按钮并使用好图片和字体

22.1.切换菜单

按钮的点击事件,点击关闭当前游戏对象,激活传入的游戏对象 

    public void SwitchTo(GameObject menu){Debug.Log(menu.name);//关闭当前菜单for(int i = 0; i < transform.childCount; i++){transform.GetChild(i).gameObject.SetActive(false);}if(menu != null){//激活点击的菜单menu.SetActive(true);}}

对所有按键添加单击事件,此事件在Canvas的UI脚本的SwitchTo类 

对同一按钮添加同一个菜单游戏对象 

22.2.

更新属性 

using UnityEngine;
using TMPro;public class UI_StatSlot : MonoBehaviour
{[SerializeField] private string _statName;//名字[SerializeField] private StatType _statType;//属性类型[SerializeField] private TextMeshProUGUI _statValueText;[SerializeField] private TextMeshProUGUI _statNameText;private void OnValidate(){//对象名gameObject.name = "Stat - " + _statName;if(_statNameText != null)_statNameText.text = _statName;}private void Start(){UpdateStatValueUI();}//更新属性栏信息public void UpdateStatValueUI(){PlayerStats playerStats = PlayerManager._instance._player.GetComponent<PlayerStats>();if(_statValueText != null){_statValueText.text = playerStats.GetStat(_statType).GetFinishValue().ToString();}}
}

无法点击取消此项 :光线投射目标

 

22.3.物品信息显示栏

 显示就是激活次UI对象,隐藏反之

public class UI_ToolTip : MonoBehaviour
{[SerializeField] private TextMeshProUGUI _itemNameText;[SerializeField] private TextMeshProUGUI _itemTypeText;[SerializeField] private TextMeshProUGUI _descriptionText;[SerializeField] private int _defaultFontSize = 32;public void ShowToolTip(ItemDataEquipment item){//获取物品信息_itemNameText.text = item._itemName;_itemTypeText.text = item._itemtype.ToString();_descriptionText.text = item.GetDescription();//防止字符串过长换行if (_itemNameText.text.Length > 14)_itemNameText.fontSize = _itemNameText.fontSize * 0.7f;else_itemNameText.fontSize = _defaultFontSize;gameObject.SetActive(true); //显示信息栏}public void HideToolTip() {_itemNameText.fontSize = _defaultFontSize;gameObject.SetActive(false);}
}

 接口类: IPointerEnterHandler鼠标放置在物体上 IPointerEnterHandler鼠标从物体上移除

//接口类:IPointerDownHandler IPointerEnterHandler鼠标放置在物体上 IPointerEnterHandler鼠标从物体上移除
public class UI_ItemStack : MonoBehaviour , IPointerDownHandler, IPointerEnterHandler, IPointerExitHandler
{[SerializeField] protected Image _image;[SerializeField] protected TextMeshProUGUI _itemText;//物品数量public InventoryItem _item;private UI _ui;//为了使用物品信息栏void Start(){_ui = GetComponentInParent<UI>();}public void OnPointerEnter(PointerEventData eventData){//为空不调用if (_item == null)return;_ui._toolTip.ShowToolTip(_item._itemData);}public void OnPointerExit(PointerEventData eventData){//为空不调用if (_item == null)return;_ui._toolTip.HideToolTip();}
}

添加这两组件,管理显示栏 

如果装备此属性不为零,这添加进StringBuilder中;stringBuilder中的内容最终会添加进 UI创建的描述文本(text)

    //物品描述protected StringBuilder _sb = new StringBuilder();//输出描述public override string GetDescription(){//清空_sb.Clear();//不为0的属性添加if (_strength != 0) AddItemDescription(_strength, "Strength");if (_agility != 0) AddItemDescription(_agility, "Agility");if (_intelligence != 0) AddItemDescription(_intelligence, "Intelligence");if (_vitality != 0) AddItemDescription(_vitality, "Vitality");if (_damage != 0) AddItemDescription(_damage, "Damage");if (_critPower != 0) AddItemDescription(_critPower, "CritPower");if (_critChance != 0) AddItemDescription(_critChance, "CritChance");if (_fireDamage != 0) AddItemDescription(_fireDamage, "FireDamage");if (_iceDamage != 0) AddItemDescription(_iceDamage, "IceDamage");if (_lightningDamage != 0) AddItemDescription(_lightningDamage, "LightningDamage");if (_maxHealth != 0) AddItemDescription(_maxHealth, "MaxHealth");if (_armor != 0) AddItemDescription(_armor, "Armor");if (_evasion != 0) AddItemDescription(_evasion, "Evasion");if (_magicResistance != 0) AddItemDescription(_magicResistance, "MagicResistance");return _sb.ToString();}protected void AddItemDescription(int value, string name){//说明有此属性if(value != 0){//为了第一行不跳行if (_sb.Length > 0)_sb.AppendLine();//添加属性数值_sb.Append(name + ": " + value);}}

 String和stringBuilder区别

String 类:

  • 不可变性:字符串是不可变的,每次对字符串进行修改(如拼接、替换等)时,都会创建一个新的字符串实例。
  • 内存分配:由于不可变性,每次字符串操作都需要在内存堆中为新字符串分配空间,这会导致频繁的内存分配和垃圾回收。
  • 性能:对于单个或少量的字符串操作,性能影响可能不大。但在大量或频繁的字符串连接操作(尤其是循环中),会产生大量的中间字符串,严重影响性能。

StringBuilder 类:

  • 可变性:StringBuilder 是可变的,可以在原对象上直接修改内容,不会生成新的对象。
  • 内存效率:它预先分配了一定大小的缓冲区,并且可以根据需要动态扩展容量,减少了内存分配次数,从而提高了内存使用效率。
  • 性能:适用于处理多个字符串拼接的情况,尤其是在循环或其他需要多次修改字符串的场景下,其性能远优于 String 类。

                        
原文链接:https://blog.csdn.net/qqrrjj2011/article/details/135370510

22.4.角色属性栏

 //像Strength等属性对其他属性有加成,需要计算

    //更新属性栏信息public void UpdateStatValueUI(){PlayerStats playerStats = PlayerManager._instance._player.GetComponent<PlayerStats>();if(_statValueText != null){_statValueText.text = playerStats.GetStat(_statType).GetFinishValue().ToString();//像Strength等属性对其他属性有加成,需要计算if(_statType == StatType.Health){_statValueText.text = playerStats.GetMaxHealth().ToString();}//力量if (_statType == StatType.Damage)_statValueText.text = (playerStats._damage.GetFinishValue() + playerStats._strength.GetFinishValue()).ToString();if(_statType == StatType.CritPower)_statValueText.text = (playerStats._critPower.GetFinishValue() + playerStats._strength.GetFinishValue()).ToString();//敏捷if(_statType == StatType.Evasion)_statValueText.text = (playerStats._evasion.GetFinishValue() + playerStats._agility.GetFinishValue()).ToString();if (_statType == StatType.CritChance)_statValueText.text = (playerStats._critChance.GetFinishValue() + playerStats._agility.GetFinishValue()).ToString();//智力if (_statType == StatType.MagicRes)_statValueText.text = (playerStats._magicResistance.GetFinishValue() + playerStats._intelligence.GetFinishValue() * 3).ToString();}}

public class UI_StatsToolTip : MonoBehaviour
{[SerializeField] private TextMeshProUGUI _description;public void ShowStatToolTip(string text){_description.text = text;gameObject.SetActive(true);}public void HideStatToolTip(){_description.text = "";gameObject.SetActive(false);}
}
public class UI_StatSlot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{//属性描述内容[TextArea][SerializeField] private string _statText;public void OnPointerEnter(PointerEventData eventData){_ui._statsToolTip.ShowStatToolTip(_statText);}public void OnPointerExit(PointerEventData eventData){_ui._statsToolTip.HideStatToolTip();}
}

22.5.制作信息栏

_craftEquipment将被设置,它里面有所需材料,创建工艺品栏,使用equipment初始化 

public class UI_CraftList : MonoBehaviour, IPointerDownHandler
{//为了找所有的工艺品配方[SerializeField] private Transform _craftOfParent;[SerializeField] private GameObject _craftListPrefab;//所有工艺品栏[SerializeField] private List<UI_CraftSlot> _craftList;//可以制作的工艺品装备已添加[SerializeField] private List<ItemDataEquipment> _craftEquipment;public void OnPointerDown(PointerEventData eventData){SetupCraftList();}private void AssignCraftList(){for(int i = 0; i < _craftOfParent.childCount; i++){//添加所有工艺品栏_craftList.Add(_craftOfParent.GetChild(i).GetComponent<UI_CraftSlot>());}}//private void SetupCraftList(){//删除旧的工艺品栏for(int i = 0; i < _craftList.Count; i++){Destroy(_craftList[i].gameObject);}//新空间_craftList = new List<UI_CraftSlot>();//使用已添加的装备成品,生成新工艺品栏for(int i = 0; i < _craftEquipment.Count; i++){Debug.Log(i);Debug.Log(_craftEquipment[i]._itemName);GameObject newSlot = Instantiate(_craftListPrefab, _craftOfParent);newSlot.GetComponent<UI_CraftSlot>().SetupCraftSlot(_craftEquipment[i]);}AssignCraftList();}void Start(){AssignCraftList();}
}

 工艺品栏

public class UI_CraftSlot : UI_ItemStack
{public void SetupCraftSlot(ItemDataEquipment equipment){if (equipment == null)return;_item._itemData = equipment;//设置图片和名字_itemIcon.sprite = equipment._icon;_itemText.text = equipment._itemName;}public override void OnPointerDown(PointerEventData eventData){ItemDataEquipment itemToCraft = _item._itemData as ItemDataEquipment;//点击则会制造物品if (Inventory._Instance.CanCraft(itemToCraft, itemToCraft._craftMaterials)){//制作成功可以播放成功欢快的音乐;}}
}

public class UI_CraftList : MonoBehaviour, IPointerDownHandler
{//为了找所有的工艺品配方[SerializeField] private Transform _craftOfParent;[SerializeField] private GameObject _craftListPrefab;//所有工艺品栏[SerializeField] private List<UI_CraftSlot> _craftList;//可以制作的工艺品装备已添加[SerializeField] private List<ItemDataEquipment> _craftEquipment;public void OnPointerDown(PointerEventData eventData){SetupCraftList();}private void AssignCraftList(){for(int i = 0; i < _craftOfParent.childCount; i++){//添加所有工艺品栏_craftList.Add(_craftOfParent.GetChild(i).GetComponent<UI_CraftSlot>());}}//private void SetupCraftList(){//删除旧的工艺品栏for(int i = 0; i < _craftList.Count; i++){Destroy(_craftList[i].gameObject);}//新空间_craftList = new List<UI_CraftSlot>();//使用已添加的装备成品,生成新工艺品栏for(int i = 0; i < _craftEquipment.Count; i++){GameObject newSlot = Instantiate(_craftListPrefab, _craftOfParent);newSlot.GetComponent<UI_CraftSlot>().SetupCraftSlot(_craftEquipment[i]);}AssignCraftList();}void Start(){AssignCraftList();}
}

 

public class UI_CraftWindow : MonoBehaviour
{[SerializeField] private Image _image;//武器图标[SerializeField] private TextMeshProUGUI _nameText;//名字[SerializeField] private TextMeshProUGUI _statText;//属性描述[SerializeField] private Image[] _MaterialImages;//材料图片[SerializeField] private Button _craftButton;//按钮public void SetupCraftWindow(ItemDataEquipment equipment){_craftButton.onClick.RemoveAllListeners();//材料图片和名字先透明for(int i = 0; i < _MaterialImages.Length; i++){_MaterialImages[i].color = Color.clear;_MaterialImages[i].GetComponentInChildren<TextMeshProUGUI>().color = Color.clear;}//材料数量超过所需材料栏的数量,那么是不合法的if (equipment._craftMaterials.Count > _MaterialImages.Length){Debug.LogWarning("You have materials amount than you have material slots in craft window");return;}//设置图标、名字、属性描述_image.sprite = equipment._itemIcon;_nameText.text = equipment._itemName;_statText.text = equipment.GetDescription();//将所需材料添加进所需材料栏for (int i = 0; i < equipment._craftMaterials.Count; i++) {_MaterialImages[i].sprite = equipment._craftMaterials[i]._itemData._itemIcon;_MaterialImages[i].GetComponentInChildren<TextMeshProUGUI>().text = equipment._craftMaterials[i]._stackSize.ToString();_MaterialImages[i].color = Color.white;_MaterialImages[i].GetComponentInChildren<TextMeshProUGUI>().color = Color.white;}_craftButton.onClick.AddListener(() => Inventory._Instance.CanCraft(equipment, equipment._craftMaterials));}
}

22.6.制作技能树

只能树的分支有两种:

  1. 等级提升(一条直线)
  2. 技能分支(多选一)

    //技能是否解锁[SerializeField] private bool _unlocked;//所需要的解锁技能[SerializeField] private UI_SkillTreeSlot[] _shouldBeUnlocked;//所需要的非解锁技能[SerializeField] private UI_SkillTreeSlot[] _shouldBeLocked;private Image _skillImage;private void OnValidate(){gameObject.name = "SkillTreeSlot - " +  _skillName;}void Start(){_skillImage = GetComponent<Image>();GetComponent<Button>().onClick.AddListener(UnlockSkillSlot);}public void UnlockSkillSlot(){//等级解锁(2级需要1级才能解锁)for (int i = 0; i < _shouldBeUnlocked.Length; i++){if (_shouldBeUnlocked[i]._unlocked == false){Debug.Log("Can't unlocked skill");return;}}//分支解锁,多选一(选择一个解锁方向)for (int i = 0; i < _shouldBeLocked.Length; i++){if (_shouldBeLocked[i]._unlocked == true){Debug.Log("Can't unlocked skill");return;}}//满足条件解锁技能_unlocked = true;_skillImage.color = Color.red;}

 技能描述面板

public class UI_SkillToolTip : MonoBehaviour
{//名字和描述[SerializeField] private TextMeshProUGUI _skillName;[SerializeField] private TextMeshProUGUI _skillText;public void ShowToolTip(string skillName, string skillText){//写入名字和描述_skillName.text = skillName;_skillText.text = skillText;gameObject.SetActive(true);//激活提示}public void HideToolTip() => gameObject.SetActive(false);}

22.7.技能树和技能关联

 当激活按钮时;因为下面的start添加了事件

    [Header("Dash")]public bool _dashUnlock;[SerializeField] private UI_SkillTreeSlot _dashUnlockButton;protected override void Start(){base.Start();//添加按钮事件_dashUnlockButton.GetComponent<Button>().onClick.AddListener(UnlockDash);}//解锁技能public void UnlockDash(){if(_dashUnlockButton._unlocked)_dashUnlock = true;}

22.7.技能冷却效果制作

 

public class UI_InGame : MonoBehaviour
{[Header("Skill Cooldown")]//冲刺技能冷却[SerializeField] private Image _dashImage;[SerializeField] private float _dashCooldown;void Start(){//初始化技能冷却_dashCooldown = SkillManager._instance._dash.GetCooldown();}private void Update(){if (Input.GetKeyDown(KeyCode.LeftShift))CheckCooldownOf(_dashImage);SetCooldown(_dashImage);}//冷却是否完毕private void CheckCooldownOf(Image image){if (image && image.fillAmount <= 0)image.fillAmount = 1;}//设置冷却private void SetCooldown(Image image){if(image && image.fillAmount > 0)image.fillAmount -=  1 / _dashCooldown * Time.deltaTime; }
}

23.保存系统

23.1.游戏数据基础的保存和加载

游戏开始是创建一个新的游戏数据类来保存数据,开始读取文件内的数据,退出时调用保存函数,保存游戏数据

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;public class SaveManager : MonoBehaviour
{public static SaveManager _instance;//单例public GameData _gameData;//游戏数据public List<ISaveManagr> _saveManagerList;//获取所有使用包含此接口的类、private FileDataHandler _fileDataHandler;//序列化和读写文件[SerializeField] string _fileName;//保存文件名private void Awake(){if( _instance == null )_instance = this;elseDestroy( _instance.gameObject );}private void Start(){_saveManagerList = FindAllSaveManager();//获取所有使用包含此接口的类_fileDataHandler = new FileDataHandler(Application.persistentDataPath, _fileName);NewGameData();LoadGameData();}private void NewGameData(){_gameData = new GameData();}//加载游戏数据private void LoadGameData(){_gameData = _fileDataHandler.Load();if(_gameData == null){NewGameData();Debug.Log("No save data found");}//加载所有类包含此接口的游戏数据foreach(ISaveManagr saveManagr in _saveManagerList){saveManagr.LoadGameData(_gameData);}Debug.Log("Load currency" + _gameData._curency);}//保存游戏数据private void SaveGameData(){_fileDataHandler.Save(_gameData);//保存所有包含此接口的类的游戏数据foreach(ISaveManagr saveManagr in _saveManagerList ){saveManagr.SaveGameData(ref _gameData);}Debug.Log("Game data was saved");Debug.Log("Save currency" + _gameData._curency);}//程序退出保存数据private void OnApplicationQuit(){SaveGameData();}//获取所有包含此接口的类private List<ISaveManagr> FindAllSaveManager(){IEnumerable<ISaveManagr> saveManagrs = FindObjectsOfType<MonoBehaviour>().OfType<ISaveManagr>();return new List<ISaveManagr>(saveManagrs);}
}

 游戏数据类

[System.Serializable]
public class GameData 
{public int _curency;public GameData(){_curency = 0;}
}

23.1.2.批量调用 使用同一个接口实现的函数 

    public List<ISaveManagr> _saveManagerList;//获取所有使用包含此接口的类、private void Start(){_saveManagerList = FindAllSaveManager();//获取所有使用包含此接口的类}//获取所有包含此接口的类private List<ISaveManagr> FindAllSaveManager(){IEnumerable<ISaveManagr> saveManagrs = FindObjectsOfType<MonoBehaviour>().OfType<ISaveManagr>();return new List<ISaveManagr>(saveManagrs);}

23.2.文件的序列化和IO

public class FileDataHandler
{//文件保存目录路径private string _dataDirPath;//文件保存名字private string _dataFileName;public FileDataHandler(string dataDirPath, string dataFileName){_dataDirPath = dataDirPath;_dataFileName = dataFileName;}public void Save(GameData gameData ){//组合成一个路径:目录 + 文件名string fullPath = Path.Combine( _dataDirPath, _dataFileName );try{//创建目录文件Directory.CreateDirectory( Path.GetDirectoryName(fullPath) );//序列化数据string dataToStore = JsonUtility.ToJson( gameData , true);//创建文本文件using(FileStream fs = new FileStream(fullPath, FileMode.Create)){//以写入的方式打开using(StreamWriter sw = new StreamWriter(fs)){//写入数据sw.Write(dataToStore);}}}catch(Exception e){Debug.LogError("Error trying to save data to feil: " + fullPath + '\n' +  e);}}public GameData Load(){string fullPath = Path.Combine(_dataDirPath, _dataFileName);GameData loadData = null;//有保存的文件if(File.Exists(fullPath)){try{string dataToStore = "";//打开文件using(FileStream fs = new FileStream(fullPath, FileMode.Open)){//以读取文件的方式打开using(StreamReader sr = new StreamReader(fs)){//读取存储的数据dataToStore = sr.ReadToEnd();}}loadData = JsonUtility.FromJson<GameData>(dataToStore);}catch (Exception e){Debug.LogError("Error trying to load data to feil: " + fullPath + '\n' + e);}}return loadData;}
}

23.2.1.字典如何序列化

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//使字典能序列化
[System.Serializable]
public class SerializableDictionary<TKey, TValue> :Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{[SerializeField]private List<TKey> _keys = new List<TKey>();//保存key值[SerializeField] private List<TValue> _values = new List<TValue>();//保存value值//序列化之前public void OnAfterDeserialize(){_keys.Clear();_values.Clear();foreach(KeyValuePair<TKey, TValue> kv in this){_keys.Add(kv.Key);_values.Add(kv.Value);}}//反序列化之后public void OnBeforeSerialize(){this.Clear();//kv的数量不对等if(_keys.Count != _values.Count){Debug.Log("key count is not equal to value count");}else{//反序列化的数据添加进字典for(int i =  0; i < _keys.Count; i++){this.Add(_keys[i], _values[i]);}}}
}

23.2.2.如何获取所有资源

    [Header("Data Base")]private List<ItemData> _itemDataBase;public List<InventoryItem> _loadDataBase;//设置数据库(所有资源),并获取它private List<ItemData> GetItemDataBase(){_itemDataBase = new List<ItemData>();string[] _assetName = AssetDatabase.FindAssets( "", new[] {"Assets/ItemData/Equipment"} );//返回GUIDforeach(var SOName in _assetName){var SOPath = AssetDatabase.GUIDToAssetPath( SOName );//获取路径var itemData = AssetDatabase.LoadAssetAtPath<ItemData>(SOPath);//加载资源_itemDataBase.Add(itemData);//添加进数据库}return _itemDataBase;}

加载保存的资源,比对本地资源库,获取对应资源 

    public void LoadGameData(GameData gameData){//需要加载的资源foreach(KeyValuePair<String, int> pair in gameData._inventory){//数据库的数据foreach(var item in _itemDataBase){//不是文件夹if(item != null && item._itemID == pair.Key){InventoryItem itemToLoad = new InventoryItem(item);itemToLoad._stackSize = pair.Value;_loadDataBase.Add(itemToLoad);//添加进加载数据库}}}}

22.3.3. 保存加载物品和已装备的装备

加载物品和已装备的装备

    public List<InventoryItem> _loadDataBase;//物品public List<ItemDataEquipment> _loadEquipments;//已装备的装备private void AddStartingItem(){//加载已装备物品foreach(ItemDataEquipment equipment in _loadEquipments){EquipItem(equipment);}if(_loadDataBase.Count > 0){foreach(var item in _loadDataBase){for(int i = 0; i < item._stackSize; i++){AddItem(item._itemData);}}return;//第一次会添加初始物品,后续则不需要}//添加初始物品for (int i = 0; i < _startingItem.Count; i++){if (_startingItem[i] != null )AddItem(_startingItem[i]);}}public void LoadGameData(GameData gameData){//需要加载的物品foreach(KeyValuePair<String, int> pair in gameData._inventory){//数据库的数据foreach(var item in GetItemDataBase()){//不是文件夹if(item != null && item._itemID == pair.Key){InventoryItem itemToLoad = new InventoryItem(item);itemToLoad._stackSize = pair.Value;_loadDataBase.Add(itemToLoad);//添加进加载数据库}}}//已装备的装备foreach (string equiment in gameData._equipmentID){foreach(var item in GetItemDataBase()){if(item != null && equiment == item._itemID ){//添加进加载链表_loadEquipments.Add(item as ItemDataEquipment);}}}}//设置数据库(所有资源),并获取它private List<ItemData> GetItemDataBase(){List<ItemData> _itemDataBase = new List<ItemData>();string[] _assetName = AssetDatabase.FindAssets( "", new[] {"Assets/ItemData/Items"} );//返回GUIDforeach(var SOName in _assetName){var SOPath = AssetDatabase.GUIDToAssetPath( SOName );//获取路径var itemData = AssetDatabase.LoadAssetAtPath<ItemData>(SOPath);//加载资源_itemDataBase.Add(itemData);//添加进数据库}return _itemDataBase;}

22.3.4.保存加载技能树

界面技能栏解锁 加载保存技能

    public void LoadGameData(GameData gameData){if (gameData._skillTree.TryGetValue(_skillName, out bool value))_unlocked = value;}public void SaveGameData(ref GameData gameData){//已经存在,删除在插入if (gameData._skillTree.TryGetValue(_skillName, out bool value)){gameData._skillTree.Remove(_skillName);gameData._skillTree.Add(_skillName, _unlocked);}elsegameData._skillTree.Add(_skillName, _unlocked);}

技能根据界面技能树的解锁与否,解锁技能(在基类的start函数调用)

    //加载技能树时,测试技能是否解锁protected override void CheckUnlock(){UnlockMirage();UnlockAggresive();UnlockDuplicate();UnlockCrystalInstead();}

22.3.5.加密数据

    //加密密钥private bool _encryptData = false;private string _codeWord = "mingtianhuigenghao";    private string EncryptDecrypt(string data){string modifiedData = "";//异或相同值两次会变回原来的值for (int i = 0; i < data.Length; i++)modifiedData += (char) data[i] ^ _codeWord[i % _codeWord.Length];return modifiedData;}

24.主菜单

对场景生成设置

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;public class UI_MainMenu : MonoBehaviour
{private string _mainScene = "MainScene";//主场景[SerializeField] private GameObject _continueButton;//继续游戏按钮private void Start(){if (SaveManager._instance.HasGameData())_continueButton.SetActive(true);}public void ContinueGame(){SceneManager.LoadScene(_mainScene);}public void NewGame()//新游戏,即删除保存文件{SaveManager._instance.DeleteSaveData();SceneManager.LoadScene(_mainScene);}public void ExitGame(){Debug.Log("Exit game");//Application.Quit();}}

24.1.黑屏浅入浅出效果

public class UI_DarkScreenFade : MonoBehaviour
{private Animator _anim;private void Start(){_anim = GetComponent<Animator>();}public void FadeOut() => _anim.SetBool("FadeOut", true);public void FadeIn() => _anim.SetBool("FadeIn", true);
}

制作黑屏效果 

24.2.死亡重新游戏

using UnityEngine.SceneManagement;public class GameManager : MonoBehaviour
{public static GameManager _instance;private void Awake(){if (_instance == null)_instance = this;elseDestroy(gameObject);}//重新开始public void RestartScene(){Scene scene = SceneManager.GetActiveScene();//当前场景SceneManager.LoadScene(scene.name);}
}

24.3.货币快速增长减少效果

    //当前货币值[Header("Currency Info")][SerializeField] private TextMeshProUGUI _currentCurrency;[SerializeField] private float _amountOfCurrency;[SerializeField] private float _increaseRate;private void UpdateCurrencyUI(){//和货币值比较if (_amountOfCurrency < PlayerManager._instance.GetCurrentCurency())_amountOfCurrency += _increaseRate * Time.deltaTime;else_amountOfCurrency = PlayerManager._instance.GetCurrentCurency();//货币快速增长效果_currentCurrency.text = ((int)_amountOfCurrency).ToString("#,#");}

25.记录点


public class CheckPoint : MonoBehaviour
{private Animator _anim;public string _checkPointId;//检测点idpublic bool _active;//是否开启检测点private void Start(){_anim = GetComponent<Animator>();}[ContextMenu("Generate Checkpoint id")]private void GenerateId(){_checkPointId = System.Guid.NewGuid().ToString();   }//碰撞解锁,只有碰撞检测没有碰撞效果,只是OnCollisionEnter的一部分效果private void OnTriggerEnter2D(Collider2D collision){if (collision.GetComponent<Player>() != null)ActiveCheckPointer();}public void ActiveCheckPointer(){_active = true;_anim.SetBool("Active", true);  }
}

本地保存记录点 

public class GameManager : MonoBehaviour, ISaveManagr
{public static GameManager _instance;[SerializeField] private CheckPoint[] _checkPoints;//监测点集合private void Awake(){if (_instance == null)_instance = this;elseDestroy(gameObject);}//获取所有的检测点private void Start(){_checkPoints = FindObjectsOfType<CheckPoint>();}//重新开始public void RestartScene(){Scene scene = SceneManager.GetActiveScene();//当前场景SceneManager.LoadScene(scene.name);}//保存检测点public void SaveGameData(ref GameData gameData){//先清空数据gameData._checkpoints.Clear();foreach(CheckPoint checkPoint in _checkPoints){gameData._checkpoints.Add(checkPoint._checkPointId, checkPoint._active);}}public void LoadGameData(GameData gameData){//读取本地数据foreach (var checkPoint in gameData._checkpoints){//比对Guidfor (int i = 0; i < _checkPoints.Length; i++){if (checkPoint.Key == _checkPoints[i]._checkPointId && checkPoint.Value == true)_checkPoints[i].ActiveCheckPointer();}}}
}

找到最近记录点

    //保存检测点public void SaveGameData(ref GameData gameData){gameData._closestCheckPoint = ClosestCheckPoint()?._checkPointId;}public void LoadGameData(GameData gameData){//角色移动最近记录点foreach(CheckPoint checkPoint in _checkPoints){//通过Guid找到对应位置if(gameData._closestCheckPoint == checkPoint._checkPointId)PlayerManager._instance._player.transform.position = checkPoint.transform.position;}}//找到最近记录点private CheckPoint ClosestCheckPoint(){CheckPoint ClosestCheckPoint = null;float closestDistance = Mathf.Infinity;foreach(CheckPoint checkPoint in _checkPoints){float currentClosest = Vector2.Distance(checkPoint.transform.position, PlayerManager._instance._player.transform.position);//距离更近且处于激活,更新值if(currentClosest < closestDistance && checkPoint._active){ClosestCheckPoint = checkPoint;closestDistance = currentClosest;}return ClosestCheckPoint;}

26.音频管理

26.1.基本逻辑

为所有音效和BGM创建一个父物体,并关闭唤醒时播放

一直同一个音效使人感到枯燥,可以是试着降低或者升高音高

基本逻辑

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class AudioManager : MonoBehaviour
{public static AudioManager _instance;[SerializeField] private AudioSource[] _sfx;//音效集合[SerializeField] private AudioSource[] _bgm;//背景音乐集合private bool _playBGM;//是否需要播放private int _bgmIndex;//当前正在播放private void Awake(){if (_instance == null)_instance = this;elseDestroy(this.gameObject);}private void Update(){if(_playBGM == false)StopAllBGM();else{//需要播放的BGM正在播放吗if (!_bgm[_bgmIndex].isPlaying)PlayBGM(_bgmIndex);}}//打开某个音效public void PlaySFX(int index){if( index < _sfx.Length){_sfx[index].pitch = Random.Range(0.85f, 1.2f);//升高或者降低音高,形成差异化_sfx[index].Play();}}//关闭某个音效public void StopSFX(int index){if( index < _sfx.Length){_sfx[index].Stop();}}//开启某个BGM(一次只能有一个BGM)public void PlayBGM(int index){_bgmIndex = index;//关闭所有StopAllBGM();_bgm[_bgmIndex].Play();}//随机播放BGMpublic void PlayRandomBGM(){int index = Random.Range(0, _bgm.Length);PlayBGM(index);}//关闭所有BGMpublic void StopAllBGM(){for(int i = 0; i < _bgm.Length; i++){_bgm[i].Stop();}}
}

26.2.控制播放距离

    [SerializeField] private float _sfxMinimumdistance;//音效最远播放距离//打开某个音效public void PlaySFX(int index, Transform targetTransform){//超出范围,已经在播放的不用再播放if (index > _sfx.Length || _sfx[index].isPlaying)return;//比较最远播放距离if (targetTransform != null && _sfxMinimumdistance < (Vector2.Distance(PlayerManager._instance._player.transform.position, targetTransform.position)))return;_sfx[index].pitch = Random.Range(0.85f, 1.2f);//升高或者降低音高,形成差异化_sfx[index].Play();}

26.3.音量控制

1.创建一个音频混合器

2.将所有的背景音乐和音效添加进对应的音频组

3.音频控制的原理:将滑块和音频组关联

4.代码逻辑

使用对数使范围在【0,-3】

public class UI_VolumnSlider : MonoBehaviour
{public Slider _slider;//音量滑块public string _parameter;//参数名字[SerializeField] private AudioMixer _mixer;//[SerializeField] private float _multiplier;//乘数public void SliderValue(float value){_mixer.SetFloat(_parameter, Mathf.Log10(value) * _multiplier);//设置音量}
}

26.4.音量值保存

G阿么Data类 

    //音量值public SerializableDictionary<string, float> _volumeSetting;public GameData(){_volumeSetting = new SerializableDictionary<string, float>();}

UI类 

    //音量值集合[SerializeField] private UI_VolumnSlider[] _volumnSlider;//保存音量值public void SaveGameData(ref GameData gameData){gameData._volumeSetting.Clear();foreach(UI_VolumnSlider volumeValue in _volumnSlider){gameData._volumeSetting.Add(volumeValue._parameter, volumeValue._slider.value);}}//加载音量值public void LoadGameData(GameData gameData){foreach(KeyValuePair<string, float> kv in gameData._volumeSetting){foreach(UI_VolumnSlider volume in _volumnSlider){if (kv.Key == volume._parameter)volume.LoadVolume(kv.Value);}}}

26.5.区域音乐

1.创建空对象

 2.触发逻辑

public class AreaSound : MonoBehaviour
{[SerializeField] private int _areaSoundIndex;//进入某一个区域播放区域音乐private void OnTriggerEnter2D(Collider2D collision){if(collision.GetComponent<Player>() != null)AudioManager._instance.PlaySFX(_areaSoundIndex, null);}private void OnTriggerExit2D(Collider2D collision){if(collision.GetComponent<Player>() != null)AudioManager._instance.StopSFXWithTime(_areaSoundIndex);}
}

3.随时间减少音量,小于0.1停止音乐,恢复默认值

    //随时间音乐逐渐停止,区域音效使用public void StopSFXWithTime(int index) => StartCoroutine(DecreaseVolume(index));//随时间减小音量private IEnumerator DecreaseVolume(int index){float defualtVolume  = _sfx[index].volume;Debug.Log(defualtVolume);while (_sfx[index].volume > 0.1f){_sfx[index].volume -= 0.2f;//减小音量yield return new WaitForSeconds(0.5f);if (_sfx[index].volume < 0.1f)//快没有声音了,停止{_sfx[index].volume = defualtVolume;StopSFX(index);break;}}}

26.6.如何找到一些音效资Unity Store, itch.io;

27.抛光

27.1.正确对目标击退

1.敌人的击退方向

    private int _knockbackDir;//击退方向public void SetKnockbackDirection(Transform target){if (transform.position.x < target.position.x)_knockbackDir = -1;else_knockbackDir = 1;}private IEnumerator HitKnockback(){isKnock = true;_rb.velocity = new Vector2(_knockbackDirection.x * _knockbackDir, _knockbackDirection.y);yield return new WaitForSeconds(_knockbackDuring);isKnock = false;}

2.玩家的击退效果:只有单次伤害超过0.3,才会被击退

    public void SetupKnockbackPower(Vector2 power) => _knockbackPower = power;//玩家击退力设置为0protected virtual void SetupZeroKnockbackPower(){}

重写方法

    //击退力设置为0protected override void SetupZeroKnockbackPower(){_knockbackPower = new Vector2(0, 0);}

伤害超过百分之30才会击退

    protected override void DecareaseHealthBy(int damage){base.DecareaseHealthBy(damage);//攻击超过最大生命的百分之30,将被击退if(damage > Mathf.RoundToInt(_maxHealth.GetFinishValue()* 0.3f ) ){int randomSound = Random.Range(33, 34);AudioManager._instance.PlaySFX(randomSound, null);_player.SetupKnockbackPower(new Vector2(8, 4));}//装备特效ItemDataEquipment armor = Inventory._instance.GetEquipment(EquipmentType.Armor);if (armor != null)armor.ItemEffect(PlayerManager._instance._player.transform);}

27.2.在打开菜单是暂停游戏

timeScale时间刻度,为1和实时流逝速度相同,0则基本上为暂停状态,0.5为实时流逝速度的0.5倍

GameManager类 

    //timeScale时间刻度,为1和实时流逝速度相同,0则基本上为暂停状态,0.5为实时流逝速度的0.5倍public void PauseGame(bool pause){if(pause == true)Time.timeScale = 0;elseTime.timeScale = 1;}

 UI类

        //使用timeScale暂停游戏if(GameManager._instance != null){if (menu == _inGame_UI)GameManager._instance.PauseGame(false);elseGameManager._instance.PauseGame(true);}

Player类 

    protected override void Update(){//打开菜单暂停游戏中if(Time.timeScale == 0)return;}

27.3.坠入虚空死亡 

在会掉落的位置,放置碰撞器

public class DeadZone : MonoBehaviour
{private void OnTriggerEnter2D(Collider2D collision){if (collision.GetComponent<CharacterStats>() != null )collision.GetComponent<CharacterStats>().KillEntity();elseDestroy(collision.gameObject);}
}

27.粒子效果使用

27.1.在角色上使用粒子效果

27.2.下雪效果 

形状为一个巨大的box,在里面生成大量的粒子效果,缩小粒子且给他向下的速度;和玩家、敌人、地面有碰撞效果

27.3.尘埃效果

  • 将持续时间设为0.1f,发射粒子数量设为500,将只发射一次大量粒子 ;
  • 将起始生命周期设为更短(1.5f),模拟速度设为2会以更快速度移动;
  • 模拟空间设为世界,碰撞设为everything

28.特效

28.打击特效

普通攻击和暴击的特效不同,使用不同的动画,对旋转和朝向进行一定的调整

    //打击星型特效public void CreateHitFX(Transform target, bool critical){//增加一点随机性float zRotate = Random.Range(-90, 90);//是星型特效旋转一点角度float xPosition = Random.Range(-0.2f, 0.5f);//位置float yPosition = Random.Range(-0.5f, 0.5f);Vector3 hitFXRotate = new Vector3(0, 0, zRotate);GameObject hitFx = _StarHitFX;//如果暴击,那么设置为暴击特效,修改选择if (critical){hitFx = _criticalHitFX;zRotate = Random.Range(-45, 45);float yRotate = 0;if(GetComponentInParent<Entity>()._facingDir == -1)//特效朝向yRotate = 180;hitFXRotate = new Vector3(0, yRotate, zRotate);}GameObject newHitFX = Instantiate(hitFx, target.position + new Vector3(xPosition, yPosition), Quaternion.identity);newHitFX.transform.Rotate(hitFXRotate);//旋转一定角度Destroy(newHitFX, 0.5f);}
}

29.冲刺残影效果,如果放置在PlayerState上那么就类似造梦西游无双效果

残影效果设置

public class AfterImage_FX : MonoBehaviour
{private SpriteRenderer _sr;private float _colorLooseRate;public void SetupAfterImage(Sprite sprite, float colorLooseRate){_sr = GetComponent<SpriteRenderer>();_sr.sprite = sprite;_colorLooseRate = colorLooseRate;}private void Update(){//减少alpha值float alpha = _sr.color.a - _colorLooseRate * Time.deltaTime;_sr.color = new Color(_sr.color.r, _sr.color.g, _sr.color.b, alpha);if(_sr.color.a <= 0 )//alpha值小于0,销毁对象Destroy(gameObject);}
}

创建残影效果并对他进行调整 

    //冲刺残影效果public void AfterImageFX(){if(_afterImageCoolDownTimer <= 0){_afterImageCoolDownTimer = _afterImageCoolDown;GameObject newGameObject = Instantiate(_afterIamgePrefab, transform.position, Quaternion.identity);if(PlayerManager._instance._player._facingDir == -1)newGameObject.transform.Rotate(new Vector3(0, 180, 0));newGameObject.GetComponent<AfterImage_FX>().SetupAfterImage(_sr.sprite, _colorLooseRate);//使用_sr会获取当前玩家的图片}}

30.窗口抖动 

1.添加监听 

using Cinemachine;//窗口抖动效果private CinemachineImpulseSource _screenShack;[Header("Screen Shack FX")][SerializeField] private float _shackMultiplier;public Vector3 _sowrdShackPower;//抓住剑 使屏幕抖动的力public Vector3 _highHitShackPower;//受到高伤害 使屏幕抖动的力//屏幕抖动效果public void ScreenShack(Vector3 shackPower){_screenShack.m_DefaultVelocity = new Vector3(shackPower.x, shackPower.y) * _shackMultiplier;_screenShack.GenerateImpulse();}

2.添加脚本 

31.弹出式窗口

1.使用3D对象的文本,非UI里面的

2.弹出文本逻辑:生命周期内,缓慢移动,生命周期结束减少Alpha值且快速移动,Alpha为0删除文本

using TMPro;
using UnityEngine;public class PopUpText_FX : MonoBehaviour
{private TextMeshPro _myText;[SerializeField] private float _speed;//文本移动速度[SerializeField] private float _desappearanceSpeed;//生命周期结束后的速度[SerializeField] private float _colorDesappearanceSpeed;//颜色失去速度[SerializeField] private float _lifeTime;//生命周期private float _textTimer;private void Start(){_myText = GetComponent<TextMeshPro>();_textTimer = _lifeTime;}private void Update(){transform.position = Vector2.MoveTowards(transform.position, new Vector2(transform.position.x, transform.position.y + 1), _speed * Time.deltaTime);_textTimer -= Time.deltaTime;if(_textTimer < 0){float alpha = _myText.color.a - _colorDesappearanceSpeed * Time.deltaTime;_myText.color = new Color(_myText.color.r, _myText.color.g, _myText.color.b,alpha);//写入alpha值if (alpha < 50) //透明度到一定程度,移动_speed = _desappearanceSpeed;if(alpha < 0)Destroy(gameObject);}}
}

3 .调用创建文本函数,传入需要使用的字符串即可

    //弹出文本效果public void CreatePPopUpText(string text){float randomX = Random.Range(-1, 1);float randomy = Random.Range(2, 4);Vector3 positionOffset = new Vector3(randomX, randomy, 0);//实例预制体GameObject newText = Instantiate(_popUpTextprefab, transform.position + positionOffset, Quaternion.identity);newText.GetComponent<TextMeshPro>().text = text;//填写字符串}

29.生成和构建

去除多余的头文件

遇到的问题如下:

    public List<ItemData> _itemDataBase;//因AssetDatabase类在运行时不可使用,所以在unity编辑器中提前调用,拿到所有物品;#if UNITY_EDITOR//预编译指令:仅在unity编辑器阶段使用//因AssetDatabase类在运行时不可使用,所以在unity编辑器中提前调用,拿到所有物品;[ContextMenu("Fill up item data base")]public void FillUpItemDataBase() => _itemDataBase = new List<ItemData>(GetItemDataBase());//设置数据库(所有资源),并获取它private List<ItemData> GetItemDataBase(){List<ItemData> _itemDataBase = new List<ItemData>();string[] _assetName = AssetDatabase.FindAssets( "", new[] {"Assets/ItemData/Items"} );//返回GUIDforeach(var SOName in _assetName){var SOPath = AssetDatabase.GUIDToAssetPath( SOName );//获取路径var itemData = AssetDatabase.LoadAssetAtPath<ItemData>(SOPath);//加载资源_itemDataBase.Add(itemData);//添加进数据库}return _itemDataBase;}
#endif

 29.1.图标和光标

文件-?生成设置

生成对应的图标可执行文件 :生成下的CleanBulid

Alt+回车键:切换全屏

30.网络发行

1.生成和构造中:需要安装此模块

2.在unityhu中安装此模块

31.制作新的敌人

在entity脚本添加上RequireComponent:意思为需要的组件,会自动添加组件

using UnityEngine;[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent (typeof(CapsuleCollider2D))]
[RequireComponent(typeof(EnemyStats))]
[RequireComponent(typeof(EntityFX))]
[RequireComponent((typeof(ItemDrop)))]public class Enemy : Entity
{}

31.1.slime(史莱姆)

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词