• Unity 2D 游戏学习笔记(6)


    官方教程:世界交互 - 可收集对象 - Unity Learn

            这次我们实现收集对象、伤害区域。

    四、收集对象

            做一个吃草莓加血的功能,首先Ruby得有血量,顺便把速度也改成可调整的,注释后加*的代表改动或新的代码。

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class RubyControl2 : MonoBehaviour
    5. {
    6. //总血量*
    7. public int maxHealth = 5;
    8. //当前血量*
    9. int currentHealth;
    10. //速度*
    11. public float speed = 3.0f;
    12. //创建一个刚体变量
    13. Rigidbody2D rigidbody2d;
    14. //因为要在不同函数中使用这两个变量,所以要声明在最外侧
    15. float horizontal;
    16. float vertical;
    17. // Start is called before the first frame update
    18. void Start()
    19. {
    20. //将脚本中的Rigidbody2D中的属性赋值给rigidbody2d
    21. rigidbody2d = GetComponent();
    22. //开始先满血*
    23. currentHealth = maxHealth;
    24. }
    25. void Update()
    26. {
    27. //利用GetAxis函数,调用用户输入的轴的值
    28. horizontal = Input.GetAxis("Horizontal");
    29. vertical = Input.GetAxis("Vertical");
    30. }
    31. private void FixedUpdate()
    32. {
    33. //得到脚本中Ruby刚体的位置信息
    34. Vector2 position = rigidbody2d.position;
    35. //每一帧,x的位置的加速度乘以用户输入移动的量*
    36. position.x += speed * horizontal * Time.deltaTime;
    37. position.y += speed * vertical * Time.deltaTime;
    38. //将移动的距离返回给Ruby刚体
    39. rigidbody2d.MovePosition(position);
    40. }
    41. //生命值变化*
    42. void ChangeHealth(int amount)
    43. {
    44. //使用函数Mathf中的方法clamp,让第一个参数的值小于第二个参数的值时,等于第二个参数;
    45. //第一个参数的值大于第三个参数的值时,等于第三个参数。
    46. currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
    47. //在控制台显示血量
    48. Debug.Log(currentHealth + "/" + maxHealth);
    49. }
    50. }

            Ruby的血量有了,接下来需要找到草莓,在Art-->Sprites-->VRX中的CollectibleHealth,将它拖入Hierarchy中,给它添加Box Collider 2D,勾选Is Trigger(触发器),这样就可以记录触碰但是可以穿过草莓。

            我们给草莓编写一个加血脚本,在Scripts文件夹中创建一个新C# Script,命名为HealthCollectible,

            你希望脚本检测 Ruby 与可收集的生命值游戏对象发生碰撞的情况,并向她提供一些生命值。为此,请使用以下特殊函数名称:

    void OnTriggerEnter2D(Collider2D other)

            提示:确保函数名称和参数类型的拼写正确,因为 Unity 在需要调用函数时会使用这些名称在脚本中“查找”函数。

            与 Unity 每帧调用 Update 函数的方式相同,当检测到新的刚体进入触发器时,Unity 将在第一帧调用此 OnTriggerEnter2D 函数。名为 other 的参数将包含刚进入触发器的碰撞体。记得要将RubyController中的ChangeHealth前加上public。

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class HealthCollectible : MonoBehaviour
    5. {
    6. private void OnTriggerEnter2D(Collider2D other)
    7. {
    8. //访问进入触发器的碰撞体的游戏对象上的RubyController组件
    9. RubyControl2 controller = other.GetComponent();
    10. //因为触发条件是有物体碰触,所以当不是Ruby碰触时,Controller为空
    11. if(controller != null)
    12. {
    13. //加1血
    14. controller.ChangeHealth(1);
    15. //吃完草莓消失
    16. Destroy(gameObject);
    17. }
    18. }
    19. }

            将血量改成1血,运行游戏吃草莓发现控制台返回2/5,并且草莓消失。

            但是当Ruby满血时,不让它吃草莓防止浪费。实现很简单只需要这样

    在RubyControl中:

    1. public int health { get { return currentHealth; } }
    2. //当前血量
    3. int currentHealth;

    在HealthCollectible中: 

    1. if(controller != null)
    2. {
    3. if(controller.health < controller.maxHealth)
    4. {
    5. //加1血
    6. controller.ChangeHealth(1);
    7. //吃完草莓消失
    8. Destroy(gameObject);
    9. }
    10. }

            为什么要这么写?因为游戏很多属性不适合用public公开,这样可能与其他脚本里重复或者很容易被其他人更改,所以我们以只读的方式在RubyControl中重新定义了个health,这样它既无法被更改,又可以被其他脚本读取到。

            现在运行游戏,会发现满血时触碰草莓就没反应了。

    五、伤害区域

            这次做一个进到刺里持续掉血的功能。首先找到游戏里的Assets > Art > Sprites > Environment 中为区域找到一个名为 Damageable 的精灵,将其拖进Hierarchy窗口中,给它添加Box Collider 2D,勾选Is Trigger。

            然后我们在Scripts文件夹中创建新C# Script编写它的脚本。

    1. public class DamageZone : MonoBehaviour
    2. {
    3. //要注意一定是2D
    4. private void OnTriggerEnter2D(Collider2D other)
    5. {
    6. //获取到控制Ruby的组件
    7. RubyControl2 Controller = other.GetComponent();
    8. //如果Ruby进入到伤害范围内
    9. if(Controller != null)
    10. {
    11. //让它血-1
    12. Controller.ChangeHealth(-1);
    13. }
    14. }
    15. }

            运行后就可以实现进入掉血了,但是没有持续掉血效果。通过设定无敌时间来实现该效果。

            可以通过将函数名称从 OnTriggerEnter2D 更改为 OnTriggerStay2D 来解决此问题。刚体在触发器内的每一帧都会调用此函数,而不是在刚体刚进入时仅调用一次。

            但是按照帧率减血,一下就死了,而且你可能还注意到,当你停止移动 Ruby 时,你在控制台上不会收到任何消息,因此 Ruby 站着不动时不会受到伤害。

            要解决最后这个问题,你需要打开角色预制件,然后在 Rigidbody2D 组件中将 Sleeping Mode 设置为 Never Sleep

             解决掉血过快,让Ruby有两秒的无敌时间就行了。代码和注释如下,更改代码用*标注。

    1. public class RubyControl2 : MonoBehaviour
    2. {
    3. //无敌时间长度*
    4. public float timeInvincible = 2.0f;
    5. //判断是否无敌*
    6. bool isInvincible;*
    7. //当前消耗的无敌时间(计时器)*
    8. float invincibleTimer;
    9. //总血量
    10. public int maxHealth = 5;
    11. //速度
    12. public float speed = 3.0f;
    13. public int health { get { return currentHealth; } }
    14. //当前血量
    15. int currentHealth;
    16. //创建一个刚体变量
    17. Rigidbody2D rigidbody2d;
    18. //因为要在不同函数中使用这两个变量,所以要声明在最外侧
    19. float horizontal;
    20. float vertical;
    21. // Start is called before the first frame update
    22. void Start()
    23. {
    24. //将脚本中的Rigidbody2D中的属性赋值给rigidbody2d
    25. rigidbody2d = GetComponent();
    26. //开始先满血
    27. currentHealth = maxHealth;
    28. }
    29. void Update()
    30. {
    31. //利用GetAxis函数,调用用户输入的轴的值
    32. horizontal = Input.GetAxis("Horizontal");
    33. vertical = Input.GetAxis("Vertical");
    34. //如果是无敌的*
    35. if(isInvincible)
    36. {
    37. //减时间
    38. invincibleTimer -= Time.deltaTime;
    39. //当无敌时间消耗光,让是否无敌改为否
    40. if(invincibleTimer < 0)
    41. {
    42. isInvincible = false;
    43. }
    44. }
    45. }
    46. private void FixedUpdate()
    47. {
    48. //得到脚本中Ruby刚体的位置信息
    49. Vector2 position = rigidbody2d.position;
    50. //每一帧,x的位置的加速度乘以用户输入移动的量
    51. position.x += speed * horizontal * Time.deltaTime;
    52. position.y += speed * vertical * Time.deltaTime;
    53. //将移动的距离返回给Ruby刚体
    54. rigidbody2d.MovePosition(position);
    55. }
    56. public void ChangeHealth(int amount)
    57. {
    58. //如果是减血,则判断是否无敌时间*
    59. if(amount < 0)
    60. {
    61. //如果是无敌,就不减血了,返回
    62. if (isInvincible)
    63. return;
    64. //如果不是无敌时间,把状态改为无敌,
    65. isInvincible = true;
    66. //添加无敌时间
    67. invincibleTimer = timeInvincible;
    68. }
    69. //使用函数Mathf中的方法clamp,让第一个参数的值小于第二个参数的值时,等于第二个参数;
    70. //第一个参数的值大于第三个参数的值时,等于第三个参数。
    71. currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
    72. //在控制台显示血量
    73. Debug.Log(currentHealth + "/" + maxHealth);
    74. }
    75. }

            更改过后,发现已经实现了无敌时间。

            如果想扩大碰触的面积,直接拉伸图形会导致图形变大变丑失去比例等等,点击Damageable,在他的Inspector窗口中将Draw Mode更改为Tiled,并且将Tile Mode修改为Adaptive。

             这时会看到有黄色的警告,在project的Environment文件夹中点击Damageable,将Mesh Type更改为Full Rect即可。

    六、敌人

            先在官方教程中下载敌人的图片Bot,存入Ruby图片的那个文件夹。

            敌人跟主角一样,都需要碰撞和移动,所以给它添加Rigidbody2D 和 BoxCollider2D。记得也要给它FreezeRotation  Z,防止它歪了。

            修改一下它的碰撞体在脚底,重力Gravity scale设为0。怪物一般都来回移动,搞个脚本给它,新建C#Script在Scripts文件夹里,命名为EnemyController。

            左右移动的脚本如下:

    1. public class EnemyController : MonoBehaviour
    2. {
    3. public float speed = 3.0f;
    4. //用来选择是纵向移动还是横向移动
    5. public bool vertical;
    6. //计时器
    7. public float changeTime = 3.0f;
    8. //计时器的当前值
    9. float timer;
    10. //用来控制方向
    11. int direction = 1;
    12. Rigidbody2D robot;
    13. // Start is called before the first frame update
    14. void Start()
    15. {
    16. //获得机器人的组件对象
    17. robot = GetComponent();
    18. //开始时给计时器固定的时间
    19. timer = changeTime;
    20. }
    21. private void Update()
    22. {
    23. //计时
    24. timer -= Time.deltaTime;
    25. //当计时结束,反向,重新计时
    26. if(timer < 0)
    27. {
    28. direction = -direction;
    29. timer = changeTime;
    30. }
    31. }
    32. // Update is called once per frame
    33. void FixedUpdate()
    34. {
    35. //机器人刚体的位置信息给position
    36. Vector2 position = robot.position;
    37. //如果勾选了vertical,就纵向移动
    38. if(vertical)
    39. {
    40. position.y += Time.deltaTime * speed * direction;
    41. }
    42. else
    43. {
    44. position.x += Time.deltaTime * speed * direction;
    45. }
    46. robot.MovePosition(position);
    47. }
    48. }

             接下来给敌人增加伤害

    1. private void OnCollisionEnter2D(Collision2D other)
    2. {
    3. RubyControl2 player = other.gameObject.GetComponent();
    4. if(player != null)
    5. {
    6. player.ChangeHealth(-1);
    7. }
    8. }

            另一个变化是,没有使用 other.GetComponent,而是使用的 other.gameObject.GetComponent。这是因为此处的 other 类型是 Collision2D,而不是 Collider2D。Collision2D 没有 GetComponent 函数,但是它包含大量有关碰撞的数据,例如与敌人碰撞的游戏对象。因此,在这个游戏对象上调用 GetComponent。

            这样,敌人就设置好了,把它拖进Prefab里变成预制件。

  • 相关阅读:
    04 后端增删改查【小白入门SpringBoot + Vue3】
    Java项目利用Redisson实现真正生产可用高并发秒杀功能 支持分布式高并发秒杀
    动态渲染 echarts 饼图(vue 2 + axios + Springboot)
    HUAWEI 华为交换机 配置 MAC 地址漂移检测示例
    js-promise-resolve应用(3)
    全国所有地级市环境污染、企业、公路、固定资产、外商投资-最新面板数据
    华为配置CAPWAP双栈覆盖业务示例
    CRM销售管理系统是如何进行数据分析的
    一个分布在多次Softmax后,会趋于相同
    I/O系统:I/O设备,I/O接口,I/O端口的编址,I/O指令、通道指令,I/O控制方式 ,补充:中断
  • 原文地址:https://blog.csdn.net/weixin_63484669/article/details/126172965