Unity预制体变体(Prefab Variants)、接口(Interface)、抽象类(Abstract Class)、枚举(Enumeration)

一、预制体变体(Prefab Variants)

预制体变体是什么?

预制体变体是指从同一个基础预制体派生出来的不同版本的预制体。这些变体可以包含不同的组件配置、属性值、子对象或者行为,但它们共享一些共同的基础结构和特性。通过创建预制体变体,开发者可以重用基础预制体的代码和结构,同时为不同的游戏情境或需求定制特定的细节。

特点:

1. 重用性:预制体变体允许开发者重用基础预制体的代码和结构,减少重复工作,提高开发效率。

2. 灵活性:通过创建不同的变体,开发者可以灵活地调整预制体的属性和行为,以适应游戏中的不同需求。

3. 组织性:预制体变体可以帮助开发者更好地组织和管理游戏中的对象,通过区分不同的变体来简化资源管理。

4. 可维护性:当基础预制体更新时,所有基于该预制体的变体也会自动更新,这有助于保持代码的一致性和可维护性。

应用场景

1. 角色变体:在游戏中,你可能需要创建多个角色变体,每个变体都有不同的外观、装备或能力。例如,一个基础角色预制体可以派生出战士、法师、弓箭手等变体。

2. 环境对象变体:在游戏环境中,你可能需要创建多个环境对象变体,每个变体都有不同的纹理、材质或装饰。例如,一个基础树木预制体可以派生出不同季节或不同种类的树木变体。

3. 道具变体:在游戏中,你可能需要创建多个道具变体,每个变体都有不同的属性、效果或外观。例如,一个基础武器预制体可以派生出不同等级或不同类型的武器变体。

4. 敌人变体:在游戏中,你可能需要创建多个敌人变体,每个变体都有不同的行为、攻击方式或防御能力。例如,一个基础敌人预制体可以派生出不同难度或不同类型的敌人变体。

二、接口(Interface)

接口是什么?

接口是一个契约,它规定了实现该接口的类需要提供哪些方法和事件。这样,接口的使用者无需关心具体的实现细节,只需要依赖接口定义的方法和事件。

特征:

接口不能包含字段,但可以定义常量(即只读的静态字段)。接口只能包含抽象方法和属性,不能包含具体实现。这意味着接口定义了一组行为规范,但不提供任何实现细节。一个类可以实现多个接口。这允许类具有多种行为,而不仅仅是继承自一个基类。

1. 抽象定义:接口仅定义方法、属性、事件和索引器的签名,而不提供具体的实现细节。这种抽象定义允许接口作为一组行为规范的契约。

// 定义一个接口 IDrawable,它要求实现者提供一个 Draw 方法
interface IDrawable {
    void Draw();
}

// 一个实现了 IDrawable 接口的类
class Circle : IDrawable {
    void Draw() {
        // 提供具体的绘制圆形的实现
    }
}

2. 常量定义:尽管接口不能包含字段,但可以定义常量,即只读的静态字段。这些常量通常用于提供配置值或定义接口中方法的参数。

// 定义一个接口 IConfigurable,它提供了一个配置值常量
interface IConfigurable {
    constant int DEFAULT_TIMEOUT = 100;
}

// 一个实现了 IConfigurable 接口的类
class Service : IConfigurable {
    // 使用接口中定义的常量
    void Initialize() {
        timeout = DEFAULT_TIMEOUT;
    }
}

3. 多重实现:一个类可以实现多个接口,从而继承多种行为。这种多重实现的能力使得类可以具有多种行为,而不仅仅是继承自一个基类。

// 定义两个接口 IRenderable 和 IUpdatable
interface IRenderable {
    void Render();
}

interface IUpdatable {
    void Update();
}

// 一个类同时实现了 IRenderable 和 IUpdatable 接口
class GameComponent : IRenderable, IUpdatable {
    void Render() {
        // 提供具体的渲染实现
    }

    void Update() {
        // 提供具体的更新实现
    }
}

4. 契约机制:接口作为一种契约,确保实现它的类遵循特定的方法和属性。这种契约机制有助于维护代码的一致性和可预测性。

// 定义一个接口 IPlayable,它要求实现者提供播放和暂停的方法
interface IPlayable {
    void Play();
    void Pause();
}

// 一个实现了 IPlayable 接口的类
class MusicPlayer : IPlayable {
    void Play() {
        // 提供具体的播放音乐的实现
    }

    void Pause() {
        // 提供具体的暂停音乐的实现
    }
}

5. 多态性支持:接口支持多态性,允许在运行时动态地使用不同的实现。这种动态使用不同实现的能力增强了代码的灵活性和可扩展性。

// 定义一个接口 IAnimal
interface IAnimal {
    void MakeSound();
}

// 实现 IAnimal 接口的两个类 Dog 和 Cat
class Dog : IAnimal {
    void MakeSound() {
        // 提供狗叫的实现
    }
}

class Cat : IAnimal {
    void MakeSound() {
        // 提供猫叫的实现
    }
}

// 使用 IAnimal 接口的数组来存储不同的动物对象
IAnimal[] animals = [Dog(), Cat()];

// 遍历数组并调用 MakeSound 方法,展示多态性
foreach animal in animals {
    animal.MakeSound();
}

6. 代码解耦:接口有助于将系统的不同部分解耦,提高代码的可维护性和可扩展性。通过定义接口,可以将实现细节与使用这些实现的代码分离,从而降低依赖性。

// 定义一个名为 CookingProcess 的类,它实现了 IProgress 接口
public class CookingProcess : IProgress
{
    // 声明一个事件,该事件在进度改变时触发
    // EventHandler 是事件处理程序的类型,ProgressChangedEventArgs 是事件的参数类型
    public event EventHandler OnProgressChanged;
    
    // 定义一个方法,用于更新进度
    public void UpdateProgress(float progress)
    {
        // 使用条件操作符(?.)来检查 OnProgressChanged 事件是否有订阅者(即是否有对象注册了该事件)
        // 如果有订阅者,则触发 OnProgressChanged 事件
        // 传递当前对象(this)作为事件源,以及一个新的 ProgressChangedEventArgs 实例作为事件参数
        // ProgressChangedEventArgs 实例中的 ProgressNormalized 属性被设置为传入的 progress 参数值
        OnProgressChanged?.Invoke(this, new ProgressChangedEventArgs { ProgressNormalized = progress });
    }
}

在上面的代码中,CookingProcess 只关心如何更新进度,而不需要知道谁在订阅它的进度事件。这样可以有效减少类之间的耦合。 

应用场景

1. 玩家控制:定义玩家控制的角色必须实现的方法,如移动、拾取物品、烹饪等。

2. 环境交互:定义环境对象(如柜台、炉灶)必须实现的方法,如放置物品、烹饪进度等。

3. 食材处理:定义食材必须实现的方法,如切割、烹饪、组合等。

4. 游戏事件处理:定义游戏事件(如订单完成、时间流逝)的响应方法。

5. UI交互:定义UI元素(如菜单、提示信息)必须实现的方法,如显示、隐藏、更新信息等。

三、抽象类(Abstract Class)

抽象类是一种不能被直接实例化的类,它用于定义一个通用的模板,该模板可以被继承以创建具体的子类。抽象类可以包含抽象方法和具体实现的方法,字段以及属性。

什么是抽象类?

抽象类是一种特殊的类,它提供了一个模板,定义了子类必须实现的方法和属性,同时也可能包含一些已经实现的方法和属性。

特征:

1.抽象方法:抽象类可以包含抽象方法,这些方法没有具体的实现,需要在派生类中提供具体实现。

// 定义一个抽象类 Shape,包含一个抽象方法 Draw
abstract class Shape {
    abstract void Draw();
}

// 派生类 Circle 继承自 Shape,并实现 Draw 方法
class Circle : Shape {
    void Draw() {
        // 提供绘制圆形的具体实现
    }
}

2.具体实现:抽象类也可以包含具体实现的方法和字段,这些可以直接被派生类使用或重写。

// 定义一个抽象类 Animal,包含一个具体实现的方法 Speak 和一个抽象方法 Eat
abstract class Animal {
    void Speak() {
        // 提供动物发声的具体实现
    }
    
    abstract void Eat();
}

// 派生类 Dog 继承自 Animal,并重写 Speak 方法,同时实现 Eat 方法
class Dog : Animal {
    void Speak() {
        // 提供狗叫的具体实现,重写基类方法
    }
    
    void Eat() {
        // 提供狗吃食的具体实现
    }
}

3.继承限制:一个类只能继承自一个抽象类,这有助于避免复杂的继承结构,保持继承关系的清晰。

// 定义一个抽象类 Vehicle,包含一个抽象方法 Move
abstract class Vehicle {
    abstract void Move();
}

// 尝试定义一个类 Car,继承自两个抽象类,这在支持单一继承的语言中是不允许的
class Car : Vehicle, AnotherAbstractClass {
    // ...
}

4.基类模板:抽象类通常用作基类,为派生类提供一个通用的模板,包括共同的行为和属性。

// 定义一个抽象类 GameCharacter,作为游戏角色的基类模板
abstract class GameCharacter {
    int health;
    void TakeDamage(int amount) {
        // 提供角色受到伤害的具体实现
    }
    
    abstract void Attack();
}

// 派生类 Warrior 继承自 GameCharacter,并实现 Attack 方法
class Warrior : GameCharacter {
    void Attack() {
        // 提供战士攻击的具体实现
    }
}

应用场景

1. 基础行为定义:当你想要定义一个类的基础行为,并且希望派生类能够扩展或修改这些行为时,抽象类是一个很好的选择。

2. 代码重用:抽象类可以在派生类之间共享通用的代码,减少重复代码,提高代码的可维护性。

3. 强制实现:通过抽象方法,可以强制派生类实现特定的功能,确保所有派生类都遵循相同的接口。

4. 设计模式实现:在实现某些设计模式(如模板方法模式、工厂模式等)时,抽象类可以用来定义模式的框架。

---------------------------------------------------------------------------------------------------------------------------------

接口(Interface)

像是一个合同:接口就像是你和另一个人签订的合同,规定了你必须做的事情,比如“你必须会走路”、“你必须会说话”,但是具体你怎么走路、怎么说话,合同里不关心。

可以同时有多个:你可以同时签订多个合同,也就是说,一个类可以实现多个接口,继承多种行为。

只能定义不能实现:接口只规定要做什么,不规定具体怎么做,具体怎么做由实现接口的类来完成。

抽象类(Abstract Class)

像是半成品:抽象类就像是一个半成品,它可能已经实现了一些功能,但是还有一些功能没有实现,需要派生类来完成。

只能继承一个:你只能有一个直接的祖先,也就是说,一个类只能继承自一个抽象类。

可以包含具体实现:抽象类可以包含一些已经实现的方法,这些方法派生类可以直接使用,也可以根据需要重写。

区别

继承关系:抽象类是一种“是一个”(is-a)的关系,比如“狗是一种动物”;接口则更像是“能做什么”(can-do)的关系,比如“能跑”、“能跳”。

实现方式:抽象类可以提供一些具体实现,接口则完全不提供实现。

数量限制:一个类可以实现多个接口,但只能继承自一个抽象类。

四、枚举(Enumeration)

枚举是一种数据类型,它由一组命名的整数常量组成。在Unity中,枚举通常用于表示一组相关的选项,如游戏状态、角色类型、物品种类等。

特征:

1. 命名常量:枚举中的每个成员都是一个命名常量,这些常量通常用于提高代码的可读性和可维护性。

public enum Day
{
    Monday,    // 表示星期一
    Tuesday,   // 表示星期二
    Wednesday, // 表示星期三
    // ... 以此类推
}

2. 整数值:枚举成员背后都有一个整数值,通常是从0开始递增的,但也可以显式指定。

public enum Status { Idle = 0, Busy = 1, Error = 2 }

3. 类型安全:枚举提供了类型安全,这意味着你不能将一个枚举类型的值赋给不兼容的变量类型,除非进行显式转换。

Day today = Day.Monday;
int dayNumber = (int)today; // 需要显式转换

拓展一下

enum Color { Red, Green, Blue }
//显式转换需要明确地指明转换的类型。
// 显式转换:从枚举转换为整数
Color myColor = Color.Green;
int colorValue = (int)myColor; // 强制类型转换

// 显式转换:从整数转换为枚举
int intValue = 2; // 假设2对应于枚举中的Blue
Color colorFromInt = (Color)intValue; // 强制类型转换

//隐式转换是自动发生的,不需要任何特殊语法
// 隐式转换:从枚举转换为整数
Color anotherColor = Color.Red;
int anotherColorValue = anotherColor; // 自动转换

// 隐式转换:从整数转换为枚举
int anotherIntValue = 1; // 假设1对应于枚举中的Red
Color anotherColorFromInt = anotherIntValue; // 自动转换

4. 易于管理:枚举使得管理一组相关的常量变得更加容易,因为它们被封装在一个类型中。

public enum NotificationType
{
    Info,
    Warning,
    Error
}

应用场景

1. 状态管理:在《胡闹厨房》中,可以使用枚举来管理游戏状态,如“闲置”、“烹饪中”、“已完成”等。

2. 事件处理:枚举可以用来定义事件类型,如“玩家拾取物品”、“订单完成”等,以便在事件系统中使用。

你可能感兴趣的:(unity,游戏引擎,c#,开发语言)