Unity的C#编程教程_60_抽象类和方法 Abstract Classes and Methods 详解及应用练习

C# Abstract Classes and Methods

  • 抽象类和方法

    • 抽象类可以强迫子类遵循特定的程序要求,便于程序管理
    • 创建一个局部的模版 partial template
    • 然后由继承的子类来实现该部分的功能
  • 假设我们现在要创建一个 Enemy 的类

    • 包含 3 种敌人:wolf,bear,spider
    • 都包含 3 种属性:速度,血量,价值(杀死后获得的金币)
    • 都包含 1 种方法:攻击

为了避免设计 3 个不同的类导致重复劳动,我们可以设计一个 Enemy 的(Abstract)类,然后把 3 种 Enemy 都继承这个类。同时强迫每一个子类都必须实现攻击方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public int speed;
    public int health;
    public int gold;

    public void Attack()
    {

    }
}

public class Wolf : Enemy
{

}

一旦我们把父类 Enemy 设定为 Abstract,这个脚本不能挂载到任何游戏对象下面,同时也无法进行直接的实例化,必须由子类进行继承后,才能将子类进行实例化。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Enemy : MonoBehaviour // 虚拟类无法实例化,该脚本无法挂载到游戏对象下
{
    public int speed;
    public int health;
    public int gold;

    public void Attack()
    {

    }
}

public class Wolf : Enemy
{
    // 这个时候 Attack 方法并不是强制实现的
}

抽象类 Enemy 的抽象方法相当于制定了一个模版,我们所有的子类 Enemy 需要遵照这个模版进行生成:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Enemy : MonoBehaviour // 虚拟类无法实例化,该脚本无法挂载到游戏对象下
{
    public int speed;
    public int health;
    public int gold;

    public abstract void Attack();
    // 抽象方法仅能出现在抽象类中
    // 抽象方法在子类中进行实现,而且是强迫的
}

public class Wolf : Enemy
{
    public override void Attack() // 子类必须实现该方法
    {
        throw new System.NotImplementedException();
    }
}

同时我们可以在抽象类中同时使用抽象方法和虚拟方法 virtual method。

比如不同的 Enemy 攻击方式不同,所以需要各自进行实现,但是每个 Enemy 的死亡方式是一致的,所以可以在父类中使用虚拟方法来统一制定:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Enemy : MonoBehaviour // 虚拟类无法实例化,该脚本无法挂载到游戏对象下
{
    public int speed;
    public int health;
    public int gold;

    public abstract void Attack();
    // 抽象方法仅能出现在抽象类中
    // 抽象方法在子类中进行实现,而且是强迫的

    public virtual void Die() // 定义死亡方式
    {
        Destroy(this.gameObject); // 销毁游戏对象
    }
}

public class Wolf : Enemy
{
    public override void Attack() // 子类必须实现该方法
    {
        throw new System.NotImplementedException();
    }

    public override void Die()
    {
        //
        // 在这里可以添加不同种类 Enemy 专属的死亡场景代码,比如特有的动作,爆炸等
        //
        base.Die(); // 使用父类的代码
    }
}

注意:虚方法在子类中的重写并不是强制的。

Challenge: Employee Experience

  • 任务说明:
    • 设计一个工会成员的抽象类
    • 包含属性:玩家姓名,所属组织(工会)
    • 包含方法:经验值提升
    • 子类:VIP工会成员,普通工会成员
    • VIP工会成员,每月获得固定的经验值提升
    • 普通工会成员,按照登陆时长,和单位时长的经验值提升量,计算经验值提升
    • 子类必须实现经验值提升的方法

设定父类 Member:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class Member : MonoBehaviour
{
    public string memberName;
    public string union;
    public int exp;

    public abstract void Reward();
}

创建游戏对象 VIPMember,挂载同名脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VIPMember : Member
{
    public int rewardPerMonth;

    // Start is called before the first frame update
    void Start()
    {
        // 初始化属性
        memberName = "Mike";
        union = "King's land";
        exp = 0;
        rewardPerMonth = 10;
        
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 按下空格键表示过了一个月
        {
            Reward(); // 每月获得一次贡献度提升
        }
        
    }

    public override void Reward()
    {
        exp += rewardPerMonth;
    }
}

创建游戏对象 NormalMember:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NormalMember : Member
{
    public int hours;
    public int rewardPerHour;

    // Start is called before the first frame update
    void Start()
    {
        // 初始化属性:
        memberName = "Jack";
        union = "North land";
        exp = 0;
        hours = 0;
        rewardPerHour = 1;

        StartCoroutine(GetReward()); // 开启协程

    }

    // Update is called once per frame
    void Update()
    {

    }

    public override void Reward() // 定义经验获取方法
    {
        exp += hours * rewardPerHour;
    }

    IEnumerator GetReward() // 定义协程
    {
        while (true)
        {
            yield return new WaitForSeconds(5); // 等待 5 秒钟当作一个月

            hours = Random.Range(1, 10); // 每月登陆1~10小时
            Reward(); // 获得奖励
        }    
    }
}

上面用了两种不同的时间指示形式作为参考。

协程的方法在实际运用中非常普遍。

C# Interfaces

1.Interfaces Made Easy

  • 接口
    • 和抽象类很像,可以强制实现某些方法
    • 仅可使用属性和方法
    • 多态性 polymorphism 的体现

创建一个 universal health system,player 和 enemy 进行不同的实现,但是我们希望其都实现展示血量和获得伤害的 method,使用 interface 来实现。

首先创建脚本 IDamagable:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IDamagable
{
    int Health { get; set; }
    // 这里不能写成 int health;

    void Damage(int damageAmount);
    // 这里不能写具体实现,与抽象方法类似

}

注意:接口的命名方式都是以大写的 “I” 加上大写字母开头的单词组成。后面的单词经常会以 -able 结尾,比如 IFixable,IHealable 等,这是一种惯例,并非强制要求。

另外,在 interface 中没有 public 和 private 等 field 的区别,统一都为 public。接口不能包含实例字段,所以不能用一般的定义变量的方式,需要使用 property。

创建游戏对象 Player,挂载同名脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour, IDamagable // 使用接口
{
    // 接口下面的属性和方法都必须实现,否则报错
    public int Health { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

    public void Damage(int damageAmount)
    {
        Health -= damageAmount; // 受到伤害扣血
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }


}

同样可以设置 Enemy,挂载同名脚本:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour, IDamagable
{
    public int Health { get; set; }

    public void Damage(int damageAmount)
    {
        Health -= damageAmount * 10;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

这里还体现了多重继承的概念,Player 继承了 MonoBehaviour 以后,就不能同时继承别的class,但是可以同时继承接口,比如这里的 IDamagable,而且可以同时继承多个接口。

当我们创建一个新的 Enemy 时候,我们需要添加一些额外的方法,这个时候项目负责人需要定义一个接口告知程序猿,以便准确地实现该方法。

2.Generic Interfaces

  • 接口的多态性

比如我们之前的 IDamagable 案例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IDamagable
{
    int Health { get; set; }
    // 这里不能写成 int health;

    void Damage(int damageAmount);
    // 这里不能写具体实现,与抽象方法类似

    void Damage(float damageAmount);
    // 这里展示了多态性
    // 实现的时候,输入 int 或者 float 会调用不同的方法

}

当然对应继承后需要都实现:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour, IDamagable // 使用接口
{
    // 接口下面的属性和方法都必须实现,否则报错
    public int Health { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
    int IDamagable.Health { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

    public void Damage(int damageAmount)
    {
        Health -= damageAmount; // 受到伤害扣血
    }

    public void Damage(float damageAmount)
    {
        Health -= (int)damageAmount; // 受到伤害扣血
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }


}

这样写会显得比较累赘,我们可以利用 grneric type:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IDamagable<T> // 这里的 T 就表示 generic type
{
    int Health { get; set; }

    void Damage(T damageAmount);
    // 这里就不用指定是 int 还是 float 之类的了

}

对应的 Player:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour, IDamagable<int> // 在这里指定 T 为 int
{
    // 接口下面的属性和方法都必须实现,否则报错
    public int Health { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

    public void Damage(int damageAmount)
    {
        Health -= damageAmount; // 受到伤害扣血
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }


}

对应的 Enemy:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour, IDamagable<float> // 在这里指定为 float
{
    public int Health { get; set; }

    public void Damage(float damageAmount)
    {
        Health -= (int)damageAmount * 10;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

这就好比项目管理者确定需要实现的方法,而落实到具体的案例上,由程序员自己决定用那种 type。

C# Polymorphism

  • 什么是多态性

    • 使用接口一个非常便利的地方在于其多态性
  • 任务说明:

    • 点击 Player 和 Enemy 的时候产生不同的伤害

创建一个主程序 Main:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class Main : MonoBehaviour
{

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0)) // 按下鼠标左键
        {
            Ray rayOrigin = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;

            if(Physics.Raycast(rayOrigin,out hitInfo))
            {
                if(hitInfo.collider.name == "Player")
                {
                    hitInfo.collider.GetComponent<Player>().Damage(100);
                }
                else if(hitInfo.collider.name == "Enemy")
                {
                    hitInfo.collider.GetComponent<Enemy>().Damage(100);
                }
            }
        }
    }
}

Player:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour, IDamagable<int> // 在这里指定 T 为 int
{
    // 接口下面的属性和方法都必须实现,否则报错
    public int Health { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }

    public void Damage(int damageAmount)
    {
        GetComponent<MeshRenderer>().material.color = Color.red;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }


}

Enemy:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour, IDamagable<float> // 在这里指定为 float
{
    public int Health { get; set; }

    public void Damage(float damageAmount)
    {
        GetComponent<MeshRenderer>().material.color = Color.red;
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

当我们点击 Player 和 Enemy 的时候,变成红色,表示造成了伤害。

但是这种做法下,如果我们有很多游戏对象,写起来就很麻烦,所以可以利用接口的多态性。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值