【用unity实现100个游戏之15】开发一个类保卫萝卜的Unity2D塔防游戏3(附项目源码)

文章目录

  • 先看本次实现的最终效果
  • 前言
  • 绘制炮塔UI
  • 炮塔转向敌人
  • 生成炮弹
  • 旋转我们的子弹
  • 对敌人造成伤害,回收子弹
  • 自动发射子弹
  • 添加攻击间隔
  • 显示伤害字体
  • 设计通用泛型单例
  • 创建更多炮塔
  • 升级增加伤害
  • 升级缩短攻击间隔
  • 添加货币
  • 杀死敌人获取金币
  • 源码
  • 完结

先看本次实现的最终效果

在这里插入图片描述

前言

本期紧接着上一篇,主要内容主要是实现不同炮塔、攻击行为逻辑和伤害飘字效果。

绘制炮塔UI

在这里插入图片描述

新增炮塔脚本,配置炮塔攻击范围

[RequireComponent(typeof(CircleCollider2D))]
public class Turret : MonoBehaviour
{[SerializeField] private float attackRange = 3f; // 视为攻击范围private bool _gameStarted; // 游戏是否已经开始private void Start(){_gameStarted = true; // 设置游戏为已开始状态}private void OnDrawGizmos(){if (!_gameStarted){// 在Scene视图中绘制攻击范围的可视化表示GetComponent<CircleCollider2D>().radius = attackRange; // 设置CircleCollider2D的半径为攻击范围Gizmos.DrawWireSphere(transform.position, attackRange); // 在指定位置绘制表示攻击范围的线框球体}}
}

在这里插入图片描述

炮塔转向敌人

修改Turret

public class Turret : MonoBehaviour
{//...public Enemy CurrentEnemyTarget { get; set; }private List<Enemy> _enemies;private void Start(){_gameStarted = true; // 设置游戏为已开始状态_enemies = new List<Enemy>();}private void Update(){GetCurrentEnemyTarget();RotateTowardsTarget();}//获取当前敌人目标private void GetCurrentEnemyTarget(){if (_enemies.Count <= 0){CurrentEnemyTarget = null;return;}CurrentEnemyTarget = _enemies[0];}//面向敌人private void RotateTowardsTarget(){if (CurrentEnemyTarget == null) return;Vector3 targetPos = CurrentEnemyTarget.transform.position - transform.position;float angle = Vector3.SignedAngle(transform.up, targetPos, transform.forward);transform.Rotate(0f, 0f, angle);}private void OnTriggerEnter2D(Collider2D other){if (other.CompareTag("Enemy")){Enemy newEnemy = other.GetComponent<Enemy>();_enemies.Add(newEnemy);}}private void OnTriggerExit2D(Collider2D other){if (other.CompareTag("Enemy")){Enemy enemy = other.GetComponent<Enemy>();if (_enemies.Contains(enemy)){_enemies.Remove(enemy);}};}//...
}

记得修改敌人标签,添加刚体和碰撞体
在这里插入图片描述

效果,进入范围获取了目标,敌人超出范围,不再跟随
在这里插入图片描述

生成炮弹

新增Projectile,子弹控制脚本

public class Projectile : MonoBehaviour
{[SerializeField, Header("子弹移动速度")] private float moveSpeed = 10f;private Enemy _enemyTarget; // 目标敌人private void Update(){if (_enemyTarget != null){MoveProjectile();}}private void MoveProjectile(){// 将子弹向目标位置移动transform.position = Vector2.MoveTowards(transform.position,_enemyTarget.transform.position,moveSpeed * Time.deltaTime);}public void SetEnemy(Enemy enemy){// 设置目标敌人_enemyTarget = enemy;}
}

制作子弹预制体,并挂载Projectile 脚本
在这里插入图片描述
新增TurretProjectile,控制子弹生成脚本

public class TurretProjectile : MonoBehaviour
{[SerializeField] private Transform projectileSpawnPosition; // 发射子弹的位置private ObjectPooler pooler; // 对象池管理器private Turret _turret;// 炮塔组件private Projectile _currentProjectileLoaded;// 当前装载的子弹private void Start(){pooler = GetComponent<ObjectPooler>(); // 获取对象池管理器组件_turret = GetComponent<Turret>();}private void Update(){if (Input.GetKeyDown(KeyCode.G)){LoadProjectile(); // 测试按下G键时加载子弹}// 如果有敌人目标、已加载子弹且敌人目标的健康值大于0if (_turret.CurrentEnemyTarget != null && _currentProjectileLoaded != null && _turret.CurrentEnemyTarget._enemyHealth.CurrentHealth > 0f){// 解除子弹的父节点关系_currentProjectileLoaded.transform.parent = null;// 设置子弹的目标敌人_currentProjectileLoaded.SetEnemy(_turret.CurrentEnemyTarget);}}private void LoadProjectile(){GameObject newInstance = pooler.GetInstanceFromPool(); // 从对象池获取一个新的子弹实例newInstance.transform.localPosition = projectileSpawnPosition.position; // 设置子弹的位置为发射位置newInstance.transform.SetParent(projectileSpawnPosition); // 设置子弹的父节点为发射位置_currentProjectileLoaded = newInstance.GetComponent<Projectile>();// 获取子弹组件newInstance.SetActive(true); // 激活子弹}
}

挂载脚本,并配置参数
在这里插入图片描述
效果
在这里插入图片描述

旋转我们的子弹

修改Projectile

private void Update()
{if (_enemyTarget != null){MoveProjectile();RotateProjectile();}
}//控制子弹自动旋转
private void RotateProjectile()
{Vector3 enemyPos = _enemyTarget.transform.position - transform.position; // 计算目标敌人相对于子弹的位置向量float angle = Vector3.SignedAngle(transform.up, enemyPos, transform.forward); // 计算旋转角度transform.Rotate(0f, 0f, angle); // 绕Z轴旋转子弹
}

效果
在这里插入图片描述

对敌人造成伤害,回收子弹

修改Projectile

[SerializeField, Header("子弹造成的伤害值")] 
private float damage = 2f;
[SerializeField, Header("造成伤害的距离")] 
private float minDistanceToDealDamage = 0.1f;private void MoveProjectile()
{// 将子弹向目标位置移动//...// 计算子弹与目标的距离float distanceToTarget = (_enemyTarget.transform.position - transform.position).magnitude;// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹if (distanceToTarget < minDistanceToDealDamage){_enemyTarget._enemyHealth.DealDamage(damage);ObjectPooler.ReturnToPool(gameObject);}
}

效果
在这里插入图片描述

自动发射子弹

修改Projectile

public TurretProjectile Turretowner { get; set; }private void MoveProjectile()
{// ...// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹if (distanceToTarget < minDistanceToDealDamage){Turretowner.ResetTurretProjectile(); //重置装载的子弹//...}
}

修改TurretProjectile

private void Update()
{if(IsTurretEmpty() && _turret.CurrentEnemyTarget != null){LoadProjectile();}//...
}
//加载子弹
private void LoadProjectile()
{//..._currentProjectileLoaded.Turretowner = this;
}//判断是否没有装载子弹
private bool IsTurretEmpty()
{return _currentProjectileLoaded == null;
}//重置装载的子弹
public void ResetTurretProjectile()
{_currentProjectileLoaded = null;
}

在这里插入图片描述

添加攻击间隔

修改TurretProjectile

[SerializeField, Header("攻击间隔")] 
private float delayBtwAttacks = 2f;//攻击间隔private float nextAttackTime;//下一次攻击时间private void Update()
{if (Time.time > nextAttackTime){// 如果有敌人目标、已加载子弹且敌人目标的健康值大于0if (_turret.CurrentEnemyTarget != null && _turret.CurrentEnemyTarget._enemyHealth.CurrentHealth > 0f){LoadProjectile();// 解除子弹的父节点关系_currentProjectileLoaded.transform.parent = null;// 设置子弹的目标敌人_currentProjectileLoaded.SetEnemy(_turret.CurrentEnemyTarget);}nextAttackTime = Time.time + delayBtwAttacks;}//加载子弹private void LoadProjectile(){//。。。_currentProjectileLoaded.ResetProjectile();//重置炮弹}
}

修改Projectile

//重置炮弹
public void ResetProjectile()
{_enemyTarget = null;transform.localRotation = Quaternion.identity;
}

效果,每两秒发动一次攻击
在这里插入图片描述

显示伤害字体

绘制字体UI
在这里插入图片描述

配置动画,这个动画就大家自由发挥吧,怎么放大缩小或者上飘
在这里插入图片描述

新增脚本DamageText

public class DamageText : MonoBehaviour
{public TextMeshPro DmgText => GetComponent<TextMeshPro>();// 回收飘字public void ReturnTextToPool(){transform.SetParent(null);ObjectPooler.ReturnToPool(gameObject);}
}

挂载脚本到字体,动画完成时添加事件
在这里插入图片描述

新增DamageTextManager

public class DamageTextManager : MonoBehaviour
{public ObjectPooler Pooler { get; set; }public DamageText damageText;//飘字特效预制体public static DamageTextManager Instance;void Awake(){Instance = this;}private void Start(){Pooler = GetComponent<ObjectPooler>();}
}

挂载脚本,配置参数
在这里插入图片描述

修改Projectile,添加敌人受伤委托

public static Action<Enemy, float> OnEnemyHit;//敌人受伤委托private void MoveProjectile()
{// 。。。// 如果子弹与目标的距离小于最小距离,造成伤害并回收子弹if (distanceToTarget < minDistanceToDealDamage){OnEnemyHit?.Invoke(_enemyTarget, damage);//。。。}
}

新增EnemyFX

public class EnemyFX : MonoBehaviour
{[SerializeField] private Transform textDamageSpawnPosition; // 伤害数字显示位置private Enemy _enemy; // 敌人对象private void Awake(){_enemy = GetComponent<Enemy>(); // 获取敌人组件}// 敌人受到攻击时的处理方法private void EnemyHit(Enemy enemy, float damage){if (_enemy == enemy) // 判断是否为当前敌人{GameObject newInstance = DamageTextManager.Instance.Pooler.GetInstanceFromPool(); // 从对象池中获取伤害数字的实例TextMeshPro damageText = newInstance.GetComponent<DamageText>().DmgText; // 获取伤害数字的Text组件damageText.text = damage.ToString(); // 设置显示的伤害数值newInstance.transform.SetParent(textDamageSpawnPosition); // 设置父物体为伤害数字显示位置newInstance.transform.position = textDamageSpawnPosition.position; // 设置显示位置newInstance.SetActive(true); // 激活显示}}private void OnEnable(){Projectile.OnEnemyHit += EnemyHit; // 注册敌人被攻击事件的监听}private void OnDisable(){Projectile.OnEnemyHit -= EnemyHit; // 取消注册敌人被攻击事件的监听}
}

挂载脚本
在这里插入图片描述
效果
在这里插入图片描述

设计通用泛型单例

新增泛型单例

using UnityEngine;//简单的泛型单例
public class Singleton<T> : MonoBehaviour where T : Component
{private static T instance;public static T Instance{get{if (instance == null){instance = FindObjectOfType<T>();if (instance == null){GameObject newInstance = new GameObject();instance = newInstance.AddComponent<T>();}}return instance;}}public void Awake(){instance = this as T;}
}

修改DamageTextManager ,简化代码

public class DamageTextManager : Singleton<DamageTextManager>
{public ObjectPooler Pooler { get; set; }public DamageText damageText;//飘字特效预制体private void Start(){Pooler = GetComponent<ObjectPooler>();}
}

创建更多炮塔

在这里插入图片描述
效果
在这里插入图片描述

升级增加伤害

修改TurretProjectile

[SerializeField, Header("造成的伤害值")]
protected float damage = 2f;
public float Damge { get; set; }private void Start()
{//。。。Damge = damage;
}//加载子弹
protected virtual void LoadProjectile()
{//。。。_currentProjectileLoaded.damage = Damge;
}

新增TurretUpgrade

public class TurretUpgrade : MonoBehaviour
{[SerializeField]private int upgradeInitialCost; // 升级初始费用[SerializeField]private int upgradeCostIncremental; // 升级费用递增值[SerializeField]private float damageIncremental; // 伤害递增值private TurretProjectile _turretProjectile;private void Start(){_turretProjectile = GetComponent<TurretProjectile>(); // 获取 TurretProjectile 组件}private void Update(){if (Input.GetKeyDown(KeyCode.D)){UpgradeTurret(); // 测试升级炮塔}}private void UpgradeTurret(){_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值}
}

配置参数测试
在这里插入图片描述

效果,按一次D,伤害增加2
在这里插入图片描述

升级缩短攻击间隔

修改TurretProjectile

public float DelayPerShot { get; set; }DelayPerShot = delayBtwAttacks;protected virtual void Update()
{if (Time.time > _nextAttackTime){// 。。。_nextAttackTime = Time.time + DelayPerShot;}
}

修改TurretUpgrade

[SerializeField, Header("攻击间隔递减值")]
private float delayReduce;private void UpgradeTurret()
{_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值_turretProjectile.DelayPerShot -= delayReduce;// 减少炮塔的攻击间隔
}

测试配置
在这里插入图片描述
效果
在这里插入图片描述

添加货币

添加

public class CurrencySystem : Singleton<CurrencySystem>
{[SerializeField]private int coinTest; // 测试用的初始金币数量private string CURRENCY_SAVE_KEY = "MYGAME_CURRENCY";public int TotalCoins { get; set; } // 总金币数量private void Start(){PlayerPrefs.DeleteKey(CURRENCY_SAVE_KEY);//开始清空金币数据LoadCoins(); // 加载金币数量}private void LoadCoins(){TotalCoins = PlayerPrefs.GetInt(CURRENCY_SAVE_KEY, coinTest); // 从 PlayerPrefs 中读取金币数量,默认为 coinTest 的值}public void AddCoins(int amount){TotalCoins += amount; // 增加金币数量PlayerPrefs.SetInt(CURRENCY_SAVE_KEY, TotalCoins); // 将金币数量保存到 PlayerPrefsPlayerPrefs.Save(); // 保存 PlayerPrefs 的修改}public void RemoveCoins(int amount){if (TotalCoins >= amount){TotalCoins -= amount; // 减少金币数量PlayerPrefs.SetInt(CURRENCY_SAVE_KEY, TotalCoins); // 将金币数量保存到 PlayerPrefsPlayerPrefs.Save(); // 保存 PlayerPrefs 的修改}}
}

修改TurretUpgrade

public int UpgradeCost { get; set; }private void Start()
{//。。。UpgradeCost = upgradeInitialCost;
}private void UpgradeTurret()
{if (CurrencySystem.Instance.TotalCoins >= UpgradeCost){_turretProjectile.Damge += damageIncremental; // 增加炮塔的伤害值_turretProjectile.DelayPerShot -= delayReduce;// 减少炮塔的攻击间隔UpdateUpgrade();}
}private void UpdateUpgrade()
{CurrencySystem.Instance.RemoveCoins(UpgradeCost);//扣除金额UpgradeCost += upgradeCostIncremental;//加下次升级金额
}

配置,升级消耗10,每次升级消耗+20
在这里插入图片描述

效果,升级扣除了金额
在这里插入图片描述

杀死敌人获取金币

修改CurrencySystem,每杀一个敌人加一金币

private void AddCoins(Enemy enemy)
{AddCoins(1);
}private void OnEnable()
{EnemyHealth.OnEnemyKilled += AddCoins;
}
private void OnDisable()
{EnemyHealth.OnEnemyKilled -= AddCoins;
}

效果,杀死敌人金额增加
在这里插入图片描述

源码

见本项目最后一节

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/156725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

el-checkbox 对勾颜色调整

对勾默认是白色 改的时候一直在试着改color人&#xff0c;其实不对。我用的是element ui 的复选框 /* 对勾颜色调整 */ .el-checkbox__inner::after{/* 是改这里的颜色 */border: 2px solid #1F7DFD; border-left: 0;border-top: 0;}

ssm+vue的药店药品信息管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的药店药品信息管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

无法安装ensp ?各种报错解决方案(virtualbox无法运行,启动AR失败,错误代码40等)

安装eNSP。发现软件中路由器无法启动。 VirtualBox是华为eNSP使用的必须运行环境&#xff0c;它提供虚拟网卡设备作为服务器为软件提供运行环境。 1. 提示出现VirtualBox Oracle无法在此项目运行。 我根据百度上的方法&#xff0c;重新还原系统。无果。 之前计算机中安装Vi…

基于混沌博弈算法优化概率神经网络PNN的分类预测 - 附代码

基于混沌博弈算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于混沌博弈算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于混沌博弈优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

【JavaEE初阶】 JavaScript基础语法——壹

文章目录 &#x1f38b;初识JavaScript&#x1f6a9;JavaScript 是什么&#x1f6a9;JavaScript 和 HTML 和 CSS 之间的关系&#x1f6a9;JavaScript 运行过程&#x1f6a9;JavaScript 的组成 &#x1f38d;前置知识&#x1f6a9;第一个JS程序&#x1f6a9;JavaScript 的书写形…

Linux JumpServer 堡垒机远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f516;系列专栏&#xff1a; C语言、Linux、Cpolar ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5.…

VMware——WindowServer2012R2环境安装mysql5.7.14解压版_主从复制(图解版)

目录 一、服务器信息二、192.168.132.33主服务器上安装mysql&#xff08;主&#xff09;2.1、环境变量配置2.2、安装2.2.1、修改配置文件内容2.2.2、初始化mysql并指定超级用户密码2.2.3、安装mysql服务2.2.4、启动mysql服务2.2.5、登录用户管理及密码修改2.2.6、开启远程访问 …

Redis 9 数据库

4 设置键的生存时间或过期时间 通过EXPIRE命令或者PEXPIRE命令&#xff0c;客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间&#xff08;TimeToLive&#xff0c;TTL&#xff09;&#xff0c;在经过指定的秒数或者毫秒数之后&#xff0c;服务器就会自动删除生存时间…

SPASS-时间序列预测

时间序列的建立和平稳化 填补缺失值 时间序列分析中的缺失值不能采用通常删除的办法来解决&#xff0c;因为这样会导致原有时间序列周期性的破坏&#xff0c;而无法得到正确的分析结果。按“转换→替换缺失值”打开“替换缺失值”对话框。 定义日期变量 展示效果如下

Vue3 customRef自定义ref 实现防抖

防抖就是防止在input 框中每输入一个字符就要向服务器请求一次&#xff0c;只要在用户输入完成过一段时间再读取用户输入的内容就能解决这个问题&#xff0c;减小服务器的压力。 1. 自定义ref是一个函数&#xff0c;可以接受参数。 比如我们自定义一个myRef&#xff1a; setu…

全网最全jmeter接口测试/接口自动化测试看这篇文章就够了:跨线程组传递jmeter变量及cookie的处理

setUp线程组 setUp thread group&#xff1a; 一种特殊类型的线程组&#xff0c;用于在执行常规线程组之前执行一些必要的操作。 在 setup线程组下提到的线程行为与普通线程组完全相同。不同的是执行顺序--- 它会在普通线程组执行之前被触发&#xff1b; 应用场景举例&#xf…

转录组学习第三弹-下载SRR数据并转成fastq

下载数据 前面已经安装好了需要的软件&#xff0c;那么我们现在需要下载我们练习需要用到的sra数据。从 SRA 数据库下载数据有多种方法。可以用ascp快速的来下载 sra 文件&#xff0c;也可以用wget或curl等传统命令从 FTP 服务器上下载 sra 文件。另外sra-tools的prefetch也支…

Java精品项目源码基于SpringBoot的樱花短视频平台(v66)

Java精品项目源码基于SpringBoot的樱花短视频平台(v66) 大家好&#xff0c;小辰今天给大家介绍一个樱花短视频平台&#xff0c;演示视频公众号&#xff08;小辰哥的Java&#xff09;对号查询观看即可 文章目录 Java精品项目源码基于SpringBoot的樱花短视频平台(v66)难度指数&…

基于单片机加热炉多参数检测和PID炉温系统

**单片机设计介绍&#xff0c; 基于单片机加热炉多参数检测和PID炉温系统 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的公交安全预警系统可以被设计成能够实时监测公交车辆的行驶状态&#xff0c;并在发生异常情况…

Softing mobiLink助力过程自动化——兼容HART、FF、PA的多协议接口工具

由于全球人口增加和气候变化等因素&#xff0c;“水”比以往任何时候都更具有价值。与此同时&#xff0c;环境法规和水处理标准也变得愈加严格。在这一大环境下&#xff0c;自来水公司不得不应对一些新的挑战&#xff0c;例如&#xff0c;更好地提高能源效率、最大程度地减少资…

HP惠普暗影精灵7Plus笔记本OMEN 17.3英寸游戏本17-ck0000恢复原厂Windows11预装OEM系统

链接&#xff1a;https://pan.baidu.com/s/1ukMXI2V3D0c-kVmIQSkbYQ?pwd2rbr 提取码&#xff1a;2rbr hp暗影7P原厂WIN11系统适用型号&#xff1a; 17-ck0056TX&#xff0c; 17-ck0055TX&#xff0c; 17-ck0054TX &#xff0c;17-ck0059TX 自带所有驱动、出厂时主题壁纸、…

vue-quill-editor 使用

vue-quill-editor 安装 npm install vue-quill-editor -S 使用 .....<quill-editorstyle"padding-left: 0;padding-top: .0px;margin-top: 30px;"ref"editorRef" v-model"params.content" class"ql-editor" :options"editor…

大数据:SAS数据分析1,数据步,和过程步

大数据&#xff1a;SAS数据分析 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&#xff0c;尤其sql…

java.lang.UnsupportedOperationException 关于Arrays.asList问题解决

解析String 字符串为List集合ArrayList<String> itemsList Arrays.asList(items.split("\\|")List<String> itemsList Arrays.asList(items.split("\\|")final Iterator<String> iterator itemsList.iterator();while (iterator.hasNex…

自定义类型之结构体

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…