Unity的C#编程教程_60_抽象类和方法 Abstract Classes and Methods 详解及应用练习
目录:
Challenge: Employee Experience
C# Interfaces
1.Interfaces Made Easy
2.Generic Interfaces
C# Polymorphism
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 的时候,变成红色,表示造成了伤害。
但是这种做法下,如果我们有很多游戏对象,写起来就很麻烦,所以可以利用接口的多态性。