C# 2D平台游戏:用组合替代继承重构代码及敌人受击逻辑实现
2D平台游戏重构(继承转组合)+ 玩家踩敌头顶伤害逻辑实现
一、用组合重构继承代码
现有代码通过Character基类继承实现玩家与敌人的通用逻辑,但随着游戏规模扩大,继承会导致耦合过重、层级臃肿。用组合模式拆分不同职责为独立组件,每个组件只负责单一功能,后续扩展更灵活。
拆分后的组件实现
1. HealthComponent(生命值管理)
using UnityEngine; public class HealthComponent : MonoBehaviour { public int maxHealth; public int currentHealth; public HealthBar healthBar; private void Start() { currentHealth = maxHealth; healthBar?.SetMaxHealth(maxHealth); } public void TakeDamage(int damage) { currentHealth = Mathf.Max(currentHealth - damage, 0); healthBar?.SetHealth(currentHealth); } }
2. DamageCooldownComponent(受伤冷却控制)
using UnityEngine; public class DamageCooldownComponent : MonoBehaviour { [SerializeField] private float damageInterval = 2f; private float currentDamageInterval; private void Update() { if (currentDamageInterval >= 0) currentDamageInterval -= Time.deltaTime; } public bool CanTakeDamage() { return currentDamageInterval < 0; } public void ResetCooldown() { currentDamageInterval = damageInterval; } }
3. KnockbackComponent(被击击飞逻辑)
using UnityEngine; public class KnockbackComponent : MonoBehaviour { private Rigidbody2D rb; private void Awake() { rb = GetComponent<Rigidbody2D>(); } public void ApplyKnockback(float yVelocity) { rb.velocity = new Vector2(rb.velocity.x, yVelocity); } }
重构后的玩家与敌人代码
玩家类(Player)
using UnityEngine; using System.Collections; public class Player : MonoBehaviour { [SerializeField] private Transform groundCheck; [SerializeField] private LayerMask hazardLayer; [SerializeField] private float knockbackYVelocity = 15f; private Color[] colors = { Color.red, Color.white }; private Coroutine damageFlash; private SpriteRenderer sr; private HealthComponent health; private DamageCooldownComponent damageCooldown; private KnockbackComponent knockback; private void Awake() { sr = GetComponent<SpriteRenderer>(); health = GetComponent<HealthComponent>(); damageCooldown = GetComponent<DamageCooldownComponent>(); knockback = GetComponent<KnockbackComponent>(); } private void Start() { health.maxHealth = 10; health.currentHealth = health.maxHealth; health.healthBar.SetMaxHealth(health.maxHealth); } private void Update() { if (damageCooldown.CanTakeDamage()) { if (Physics2D.OverlapCircle(groundCheck.position, 0.5f, hazardLayer)) { TakeDamage(1); } } if (Input.GetKeyDown(KeyCode.H)) { TakeDamage(1); } } public void TakeDamage(int damage) { if (!damageCooldown.CanTakeDamage()) return; health.TakeDamage(damage); knockback.ApplyKnockback(knockbackYVelocity); damageCooldown.ResetCooldown(); if (damageFlash != null) StopCoroutine(damageFlash); damageFlash = StartCoroutine(DamageFlashing(1f, 0.1f)); } IEnumerator DamageFlashing(float duration, float interval) { int index = 0; var wait = new WaitForSeconds(interval); for (float elapsedTime = 0; elapsedTime < duration; elapsedTime += interval) { sr.color = colors[index % 2]; index++; yield return wait; } sr.color = Color.white; damageFlash = null; } }
敌人类(Enemy)
using UnityEngine; public class Enemy : MonoBehaviour { [SerializeField] private LayerMask playerLayer; [SerializeField] private float knockbackYVelocity = 15f; [SerializeField] private Transform topDamageCheck; [SerializeField] private float checkRadius = 0.3f; private HealthComponent health; private DamageCooldownComponent damageCooldown; private KnockbackComponent knockback; private void Awake() { health = GetComponent<HealthComponent>(); damageCooldown = GetComponent<DamageCooldownComponent>(); knockback = GetComponent<KnockbackComponent>(); } private void Start() { health.maxHealth = 3; health.currentHealth = health.maxHealth; health.healthBar.SetMaxHealth(health.maxHealth); } private void Update() { if (damageCooldown.CanTakeDamage()) { Collider2D playerCollider = Physics2D.OverlapCircle(topDamageCheck.position, checkRadius, playerLayer); if (playerCollider != null) { Rigidbody2D playerRb = playerCollider.GetComponent<Rigidbody2D>(); if (playerRb != null && playerRb.velocity.y < 0) { TakeDamage(1); playerRb.velocity = new Vector2(playerRb.velocity.x, 10f); } } } } public void TakeDamage(int damage) { health.TakeDamage(damage); knockback.ApplyKnockback(knockbackYVelocity); damageCooldown.ResetCooldown(); Debug.Log("Enemy taking damage"); } }
组合模式的优势
- 每个组件职责单一,修改某一功能(比如调整击飞逻辑)只需要修改对应组件,不影响其他逻辑
- 避免继承的“脆弱基类问题”,后续新增角色类型(比如NPC、Boss)可以自由组合需要的组件
- 代码更易维护和测试,降低耦合度
二、玩家踩敌人头顶仅敌人受伤的实现
核心逻辑
实现该功能需要两个关键判断:
- 玩家是否接触到敌人的头顶区域
- 玩家是否处于下落状态(避免侧面接触误触发)
具体实现步骤
- 在Enemy对象下创建空物体
topDamageCheck,放置在敌人Collider的顶部偏上位置(确保只有玩家踩上去才会触发) - 用
Physics2D.OverlapCircle检测该点是否接触到玩家,同时判断玩家的y轴速度是否小于0(下落) - 满足条件时,调用Enemy的
TakeDamage,同时给玩家一个向上的弹跳力(符合平台游戏手感)
替代方案:用Trigger碰撞体实现
如果不想用代码检测点,可以给敌人添加一个小型的Trigger碰撞体(比如CapsuleCollider2D),放在敌人头顶区域,然后修改Enemy的代码:
private void OnTriggerStay2D(Collider2D other) { if (damageCooldown.CanTakeDamage() && other.CompareTag("Player")) { Rigidbody2D playerRb = other.GetComponent<Rigidbody2D>(); if (playerRb != null && playerRb.velocity.y < 0) { TakeDamage(1); playerRb.velocity = new Vector2(playerRb.velocity.x, 10f); damageCooldown.ResetCooldown(); } } }
注意事项
- 确保Player和Enemy的Layer设置正确,检测时使用对应的LayerMask
- 调整
checkRadius和弹跳速度数值,适配你的游戏手感 - 受伤冷却逻辑必须生效,避免敌人被连续秒杀
内容的提问来源于stack exchange,提问作者Natalia DaLomba
相关产品推荐
相关产品推荐

