Bootstrap

自由学习记录(26)

streamingAsset在ab包的参与的总结

意思是我正在做一个游戏,我目前就相当于在做种子库的ab包,最后游戏上线之后,在玩家那边,加载ab包,肯定会优先判断这个种子库,而我后期要改的话,就传新的ab包(这个新的是修改过的种子库) 然后unity就会判断要加载最新的我传的ab包

至于为什么偏要放stream里,随便放项目的哪里不是都一样的问题, 完全是因为放别的地方是存在可修改空间的,stream里只读的特性

你的理解基本正确,不过在某些细节上可以再细化一些,特别是关于 StreamingAssets 的作用和限制,以及热更新时资源加载优先级的实现逻辑。下面我针对你的问题和思路进行梳理:


StreamingAssets 的角色

你提到的 “偏要放在 StreamingAssets” 和它的只读特性,是准确的,但可以进一步解释为什么选这个目录,而不是项目中的其他地方:

(1)StreamingAssets 是安装包的一部分
  • StreamingAssets 是 Unity 提供的一个特定目录,内容会被原样打包进游戏的安装包。
  • 其他项目目录(如 AssetsResources)里的内容,在构建游戏时会被 Unity 转换成打包格式,不再是文件的原始形式,无法直接按文件路径访问
(2)放在 StreamingAssets 的好处

StreamingAssets 是 Unity 内置支持的跨平台目录,加载路径 Application.streamingAssetsPath 在不同平台上适配了文件读取方式(如安卓的 APK 内部访问)。


热更新机制与 StreamingAssets

热更新的核心在于动态资源加载和替换,而 StreamingAssets 仅仅是起点(种子库)的一部分:

流程描述:

种子库(StreamingAssets)提供初始资源

在安装包中放入最基础的 AB 包,这些包能满足游戏启动和基本功能运行的需要。

游戏启动时,向服务器查询资源版本。如果检测到新版本的 AB 包(或资源),从服务器下载到 persistentDataPath


为什么种子库一定要放 StreamingAssets,而不能放其他地方?

  • 必须放 StreamingAssets 的原因

    • 项目中的其他目录(如 AssetsResources)在打包后会变成游戏的内嵌数据,不能以文件的形式访问。
    • StreamingAssets 是唯一能在最终的安装包中直接以原始文件形式保留的目录,并且 Unity 提供了跨平台的支持。
  • 如果放到其他地方

    • 比如 Assets 目录,最终的安装包中这些资源会被 Unity 压缩成内部格式,无法通过路径加载文件。

代码示例:AB 包加载逻辑

using UnityEngine;
using System.IO;

public class HotUpdateManager : MonoBehaviour
{
    public string bundleName = "examplebundle";

    void Start()
    {
        // 路径设置
        string persistentPath = Path.Combine(Application.persistentDataPath, bundleName);
        string streamingPath = Path.Combine(Application.streamingAssetsPath, bundleName);

        AssetBundle bundle = null;

        // 优先加载可写目录的资源
        if (File.Exists(persistentPath))
        {
            Debug.Log("Loading from persistentDataPath...");
            bundle = AssetBundle.LoadFromFile(persistentPath);
        }
        else if (File.Exists(streamingPath))
        {
            Debug.Log("Loading from StreamingAssets...");
            bundle = AssetBundle.LoadFromFile(streamingPath);
        }

        if (bundle != null)
        {
            // 加载资源
            var prefab = bundle.LoadAsset<GameObject>("RedCube");
            Instantiate(prefab);
            bundle.Unload(false);
        }
        else
        {
            Debug.LogError("Failed to load AssetBundle!");
        }
    }
}

被转换成打包格式意味着里面原本具体的每一个图片,代码文件,都经过一种特殊处理揉成一团, 但这最后的一团却可以完美展示你想要表现的所有功能, 而ab包绑定资源并且放在stream ,是考虑到不要让里面的资源被揉成一团,完全找不了, 而另一方面,这样把完好的资源放在streamAsset里面,也方便了后期想对资源的修改(也就是热更新)

你的总结基本正确!在 Unity 中,默认情况下,AssetsResources 目录的内容会在打包时被 Unity 转换为一种高效的内部格式(通常是打包成 .assets 文件),并整合到最终的游戏运行包(如 .apk.exe 等)中。这种转换和整合带来了以下结果:


1. Unity 的打包过程:

(1)非 StreamingAssets 内容的处理
  • Unity 会对非 StreamingAssets 的资源进行打包压缩,这种方式带来两大特性:
    1. 不可直接访问:
      • 被打包进游戏运行文件后,这些资源不再以原始文件形式存在。
      • 运行时无法通过文件路径访问这些资源(比如代码中无法直接指定路径加载某个图片文件),只能通过 Unity 提供的 API(如 Resources.Load 或场景加载机制)来间接使用
    2. 更高性能和安全性:
      • 打包的资源整合在运行包中,可以更快地被加载到内存,并且对玩家不可见,不容易被直接修改或窃取。
(2)StreamingAssets 的特殊处理
  • StreamingAssets 是一个例外,它是 Unity 特别设计的目录:
    • 不会参与资源压缩或整合
      • 文件以原始形式打包并保留在游戏安装包中。
    • 运行时可直接访问
      • 可以通过文件路径访问资源(Application.streamingAssetsPath),甚至可以用标准的文件读取操作加载文件

2. 为什么 AB 包适合放在 StreamingAssets 中?

AssetBundle(AB 包)的核心特点是可以在运行时动态加载特定资源,而不需要这些资源被揉成一团,这就要求资源必须以原始形式存在。放在 StreamingAssets 中有以下优势:

(1)避免被 Unity 转换
  • 如果将 AB 包放在其他目录(如 AssetsResources),它们会被 Unity 的打包流程整合,无法以文件形式存在。
  • 放在 StreamingAssets 中,资源保持独立,能够按需动态加载
(2)方便热更新
  • 热更新的核心思想是下载并替换单个资源文件,而非重新生成整个游戏运行包。
  • 如果资源被揉成一团(如打包成 .assets 文件),替换一个单独的图片或模型几乎不可能
  • 使用 AB 包 + StreamingAssets 的方式,可以:
    1. 将游戏初始资源打包到 AB 包。
    2. 后期直接通过网络替换 AB 包文件,达到热更新的目的。

3. 放在 StreamingAssets 的资源如何加载?

AB 包存放在 StreamingAssets 后,你可以通过 AssetBundle.LoadFromFile 加载它们:

string path = Path.Combine(Application.streamingAssetsPath, "mybundle");
AssetBundle bundle = AssetBundle.LoadFromFile(path);

if (bundle != null)
{
    GameObject prefab = bundle.LoadAsset<GameObject>("MyPrefab");
    Instantiate(prefab);
    bundle.Unload(false);
}
else
{
    Debug.LogError("Failed to load AssetBundle!");
}

4. 总结

你提到的两点非常精确:

  1. 避免被揉成一团:
    • Unity 默认会将非 StreamingAssets 的资源压缩整合,但放在 StreamingAssets 的资源可以保持原始文件形式,方便动态加载。
  2. 方便资源修改:
    • 放在 StreamingAssets 中的资源可以作为初始版本,后期通过热更新机制,下载并替换它们,达到动态更新游戏内容的目的。

取消将 AB 包放到 StreamingAssets,就意味着:

  • 游戏中没有一个便于动态加载的初始资源库。
  • 任何资源的修改都需要重新打包整个游戏,而非下载单独的资源

虽然理论上,你可以在其他地方模拟类似的机制(比如手动下载初始资源),但这样就完全失去了 Unity 的内置支持。

eg:不copy to streamingAsset的情况(完全依赖在线资源)

  • 游戏首次启动时就从服务器下载所有 AB 包,而不是从 StreamingAssets 中加载。
  • 缺点是首次加载时间可能很长,且玩家必须联网才能运行。

对于大多数中大型项目,尤其是有长期维护计划的游戏,热更新是必需的,而放弃 StreamingAssets 基本等于放弃了热更新。

总结

除非你的游戏明确不需要热更新,或者资源加载完全依赖服务器,否则建议始终勾选 “Copy to StreamingAssets”。StreamingAssets 是热更新的基石,它的初始定位决定了整个热更新体系是否健全!

StreamingAssets 文件夹只是热更新的过程没有直接关系

严格来说,StreamingAssets 文件夹和热更新本身没有直接关系,但它可以作为热更新的一部分参与资源管理。以下是更明确的解释:


1. StreamingAssets 文件夹的特性

  • 静态资源

    • StreamingAssets 是一个静态目录,其内容会被原样打包到最终的游戏包(如 .exe.apk.ipa)。
    • 游戏安装后,StreamingAssets 内的文件通常不可修改(尤其在移动端,如安卓和 iOS 平台)。
    • 在运行时,StreamingAssets 文件只读,不能改。

2. 为什么 StreamingAssets 和热更新没有直接关系

热更新的核心是动态下载和替换资源文件,而 StreamingAssets 本身不支持动态修改。

  • 在热更新机制中,更新后的资源通常存放在 Unity 的 可写目录Application.persistentDataPath)中,因为这是唯一可以动态读写的文件夹
  • StreamingAssets 更像是一个初始资源的“种子库”

3. StreamingAssets 在热更新中的间接作用

尽管 StreamingAssets 不直接参与动态更新,它可以作为热更新的一部分提供以下支持:

(1)初始资源提供:兜底机制
  • 游戏首次运行时,可以从 StreamingAssets 中加载初始的 AB 包。
  • 如果热更新下载失败,仍然可以从 StreamingAssets 中加载资源,保证游戏的基本运行。

示例场景

  • 游戏包中包含一个初始场景的 AssetBundle,放在 StreamingAssets 里。
  • 当玩家启动游戏时,如果没有联网或更新失败,仍然可以通过 StreamingAssets 加载这个初始场景。
(2)本地缓存热更新资源
  • 在某些情况下,热更新的资源可以先通过 StreamingAssets 提供,然后再复制到可写目录。
  • 这样可以避免在首次运行时从服务器下载初始资源。

4. StreamingAssets 和热更新的典型逻辑

一个常见的热更新流程如下:

  1. 初始资源存储在 StreamingAssets:

    • 游戏安装包中包含一些必要的 AB 包(比如 UI 资源、初始场景资源等)。
    • 这些资源放在 StreamingAssets 里,供游戏首次运行时加载
  2. 更新资源从远程服务器下载:

    • 启动游戏后,检查服务器是否有更新的资源版本。
    • 如果有更新,从服务器下载新的 AB 包,并存储到 Application.persistentDataPath
  3. 优先从可写目录加载:

    • 加载资源时,首先检查 Application.persistentDataPath 是否有最新版本。
    • 如果没有,则回退到 StreamingAssets 中的初始资源。
  4. 热更新覆盖逻辑:

    • 载完成的新资源将存储在可写目录中,而不是 StreamingAssets
    • 下次运行时会直接加载新的资源,而不再使用旧的 StreamingAssets 文件

5. 具体代码示例

以下代码展示了一个热更新的加载优先级逻辑:

using UnityEngine;

public class HotUpdateLoader : MonoBehaviour
{
    public string bundleName = "examplebundle"; // AB 包名称

    void Start()
    {
        string persistentPath = Application.persistentDataPath + "/" + bundleName;
        string streamingPath = Application.streamingAssetsPath + "/" + bundleName;

        AssetBundle bundle = null;

        // 优先从可写目录加载资源
        if (System.IO.File.Exists(persistentPath))
        {
            Debug.Log("Loading updated bundle from persistentDataPath...");
            bundle = AssetBundle.LoadFromFile(persistentPath);
        }
        else
        {
            Debug.Log("Loading original bundle from StreamingAssets...");
            bundle = AssetBundle.LoadFromFile(streamingPath);
        }

        if (bundle != null)
        {
            // 加载资源
            GameObject prefab = bundle.LoadAsset<GameObject>("RedCube");
            Instantiate(prefab);
            bundle.Unload(false);
        }
        else
        {
            Debug.LogError("Failed to load AssetBundle!");
        }
    }
}

6. 总结

  • StreamingAssets 和热更新没有直接关系,因为它是静态的,只能用于只读操作。
  • 它在热更新机制中的作用更多是:
    • 提供初始资源,确保游戏在没有更新文件时仍然能运行。
    • 在首次运行时作为基础资源的种子库
  • 热更新的核心在于下载和动态替换资源,这依赖 persistentDataPath 或其他可写目录,而不是 StreamingAssets。

Clear FoldersCopy to StreamingAssets

AssetBundle BrowserBuild 面板中,

Clear FoldersCopy to StreamingAssets 是两个可选的勾选项


1. Clear Folders(清理文件夹)

作用:

勾选此选项后,打包时会清空 AB 包的输出目录,确保生成的 AB 包文件夹中没有残留的旧文件

为什么要勾选:
  • 避免文件冗余
    • 如果你不清空文件夹,可能会出现旧的 AB 包文件没有被覆盖的问题。例如,你修改了资源或改变了 AB 包的配置,但旧的文件仍然存在,可能会导致运行时加载到错误的资源。
  • 减少错误和混乱
    • 清理文件夹可以确保生成的 AB 包文件夹只包含最新的打包内容,方便调试和分发。
什么时候不勾选:
  • 如果你希望保留之前的打包结果(例如用于对比文件差异或版本备份),可以不勾选此选项

2. Copy to StreamingAssets(复制到 StreamingAssets 文件夹)

作用:

勾选此选项后,打包完成的 AB 包会自动复制到项目的 StreamingAssets 文件夹中,方便在运行时加载。

为什么要勾选:
  • 方便运行时加载
    • Unity 的 StreamingAssets 文件夹是一个特殊目录,打包后会直接包含在最终的应用程序中,可以通过文件路径直接访问,无需下载。例如:
      string path = Application.streamingAssetsPath + "/examplebundle";
      AssetBundle bundle = AssetBundle.LoadFromFile(path);
      
  • 便于调试
    • 将 AB 包放在 StreamingAssets 文件夹中,可以在编辑器和构建后的项目中以相同方式加载,方便测试。
什么时候不勾选:
  • 如果你的项目需要通过网络下载 AB 包,而不是直接从本地加载,则不需要将包复制到 StreamingAssets
  • 如果你已经有其他脚本或流程处理 AB 包的移动和存储,也可以不勾选。

总结:两者搭配的意义

  1. Clear Folders 确保打包目录干净,避免旧文件混淆,保证输出的 AB 包内容是最新且准确的。
  2. Copy to StreamingAssets 直接把 AB 包复制到应用的内置资源目录中,方便本地加载和调试。

建议大多数情况下,两者都应该勾选,这样可以简化打包流程并减少运行时加载出错的可能。

从零开始做ab包

从零开始举一个具体的例子,展示如何在 Unity 中打包 AssetBundle(AB 包),并加载其中的资源。这个例子包括创建资源配置打包生成 AB 包加载和使用资源等完整流程。


1. 创建资源

在 Unity 项目中创建一些简单的资源作为示例:

  1. 创建材质和纹理:
    • 右键点击 Assets 文件夹,选择 Create -> Material 创建一个材质,命名为 RedMaterial
    • 给材质添加一个红色颜色或纹理。
  2. 创建预制体:
    • 在场景中放置一个立方体(GameObject -> 3D Object -> Cube),为其添加刚刚创建的材质。
    • 将立方体拖动到 Assets 文件夹中保存为 Prefab,命名为 RedCube.

2. 配置打包标签

Unity 中通过 AssetBundle 标签 决定哪些资源会被打包到一起。以下是配置方法:

  1. 打开 Inspector 面板:
    • 选择刚刚创建的 RedCube.prefab
  2. 设置 AssetBundle 标签:
    • Inspector 面板的右下角找到 AssetBundle,点击下拉框,输入 examplebundle 作为标签名称。

3. 编写脚本进行打包

创建一个 C# 脚本,用于将资源打包成 AB 包:

  1. Assets 文件夹中创建一个名为 Editor 的文件夹(必须放在 Editor 文件夹内,否则无法调用 Editor API)。
  2. Editor 文件夹中创建一个脚本 BuildAssetBundles.cs,内容如下:
using UnityEditor;
using UnityEngine;

public class BuildAssetBundles
{
    [MenuItem("Assets/Build AssetBundles")] // 在 Unity 菜单中添加选项
    public static void BuildAllAssetBundles()
    {
        // 指定 AB 包的输出目录
        string outputPath = "Assets/AssetBundles";

        // 如果目录不存在,则创建
        if (!System.IO.Directory.Exists(outputPath))
        {
            System.IO.Directory.CreateDirectory(outputPath);
        }

        // 构建 AB 包
        BuildPipeline.BuildAssetBundles(
            outputPath,
            BuildAssetBundleOptions.None, // 不使用额外选项
            BuildTarget.StandaloneWindows // 目标平台为 Windows
        );

        Debug.Log("AssetBundles built successfully at: " + outputPath);
    }
}

4. 执行打包

需要打包的资源就可以用browser的inspect快速查看哪个包里有哪些资源,或者要添加哪些资源到哪些包

你可以直观地看到每个 AssetBundle 中包含哪些资源,检查资源是否被正确分配到对应的包。

查看某个资源是否被重复打包,如果资源有依赖项,可以通过 Inspect 功能发现是否配置了合理的共享资源包。

每一个需要加入 AssetBundle 的资源都需要手动选择并分配到指定的包。以下是具体的步骤:

方法 1:使用 AssetBundle Browser
  1. 打开 AssetBundle Browser:

    • 在 Unity 菜单中,选择 Window -> Asset Management -> AssetBundle Browser
  2. 设置 AssetBundle 标签:

    • Configure 标签页中,选中要打包的资源(如 RedCube.prefab)。
    • 在右侧面板中,点击 Assign Bundle,选择或新建一个 AB 包名称。
  3. 查看分配结果:

    • 切换到 Inspect 标签页,可以看到所有已分配的 AB 包及其包含的资源。

方法 2:手动通过 Inspector 面板设置
  1. 选中需要打包的资源(如材质、模型、预制体等)。
  2. Inspector 面板右下角找到 AssetBundle 标签区域。
  3. 选择或新建一个包名称,将资源分配到对应的包中。

在打包完成后,使用 Inspect 功能可以帮助你确认资源是否重复打包包内资源是否完整依赖链是否合理


提高效率的小技巧

  1. 批量设置标签

    • 选中多个资源后,可以一次性为它们设置相同的 AssetBundle 标签。
  2. 动态分配标签

    • 如果需要动态管理打包资源,可以通过脚本遍历资源并设置标签。例如:
      using UnityEditor;
      
      public class BatchSetAssetBundleLabels
      {
          [MenuItem("Assets/Set AssetBundle Labels")]
          static void SetLabels()
          {
              string folderPath = "Assets/ExampleFolder"; // 要打包的资源文件夹
              string bundleName = "examplebundle"; // AB 包名称
      
              var assets = AssetDatabase.FindAssets("", new[] { folderPath });
      
              foreach (var guid in assets)
              {
                  string path = AssetDatabase.GUIDToAssetPath(guid);
                  AssetImporter.GetAtPath(path).assetBundleName = bundleName;
              }
      
              Debug.Log("AssetBundle labels set successfully!");
          }
      }
      

如果不使用 AssetBundle Browser,也可以通过读取 .manifest 文件分析资源依赖关系。 


5. 加载 AB 包并使用资源

创建一个脚本,用于加载刚刚打包的 AB 包并实例化 RedCube

  1. Assets 文件夹中创建一个新的脚本,命名为 LoadAssetBundle.cs

Application.dataPath项目的datapath

// 从文件加载 AB 包
        AssetBundle bundle = AssetBundle.LoadFromFile(bundlePath);

        // 从 AB 包中加载资源
        GameObject prefab = bundle.LoadAsset<GameObject>("RedCube");

        // 卸载 AB 包,但保留已加载的资源
        bundle.Unload(false);

using UnityEngine;

public class LoadAssetBundle : MonoBehaviour
{
    void Start()
    {
        // AB 包的路径
        string bundlePath = Application.dataPath + "/AssetBundles/examplebundle";

        // 从文件加载 AB 包
        AssetBundle bundle = AssetBundle.LoadFromFile(bundlePath);

        if (bundle == null)
        {
            Debug.LogError("Failed to load AssetBundle!");
            return;
        }

        // 从 AB 包中加载资源
        GameObject prefab = bundle.LoadAsset<GameObject>("RedCube");

        if (prefab != null)
        {
            // 实例化资源
            Instantiate(prefab);
        }

        // 卸载 AB 包,但保留已加载的资源
        bundle.Unload(false);
    }
}
  1. 将脚本挂载到场景中的任意空物体上,例如一个空的 GameObject。

6. 运行项目

点击 运行,程序会加载 AB 包中的 RedCube,并在场景中实例化一个带有红色材质的立方体。


扩展功能

  • 跨平台打包: 可以通过修改 BuildTarget 参数(如 BuildTarget.AndroidBuildTarget.iOS)生成特定平台的 AB 包。
  • 优化加载性能: 使用 AssetBundle.LoadAsync 异步加载资源,减少加载卡顿。

ConfigureBuildInspect 作用:

AssetBundle Browser 是 Unity 提供的一个 可视化工具包,主要用来方便地管理、配置和检查 AssetBundle 的打包过程,但它并不是实现 AB 包功能的必要条件。换句话说,即使没有 AssetBundle Browser,你仍然可以通过代码手动实现 AB 包的所有功能


1. Configure(配置)

配置阶段 是设置和定义 AB 包内容的关键步骤。在 Unity 中,资源的打包方式和依赖关系由配置阶段决定。

主要操作:
  • 标记资源(AssetBundle Label)

    • Inspector 窗口中,通过给资源分配 AssetBundle 标签,指定哪些资源会被打包到同一个 AB 包。
    • 不同资源可以共享一个标签(打包到同一个 AB 包),或者每个资源有自己的标签(独立打包)。
  • 配置依赖

    • 如果一个 AB 包中的资源依赖另一个包中的资源,Unity 会自动处理依赖关系(通过 manifest 文件记录)。
  • 优化配置

    • 避免重复资源:将公共资源(如纹理、材质)单独配置为一个 AB 包,减少打包和加载的冗余。
    • 按使用场景划分:根据游戏的场景或模块需求,设置资源打包逻辑。
工具支持:
  • 在 Unity Editor 中AssetBundle Browser 是一个专用工具,用于更直观地管理和配置 AB 包标签。

2. Build(构建)

构建阶段 是将配置的资源正式打包成 AB 包的过程。Unity 会根据配置生成 AB 文件以及相关的依赖信息。

主要操作:
  • BuildPipeline.BuildAssetBundles: 使用 Unity 提供的 BuildPipeline.BuildAssetBundles 方法构建 AB 包。

    BuildPipeline.BuildAssetBundles(
        "Assets/AssetBundles", // 打包输出目录
        BuildAssetBundleOptions.None, // 可选打包设置(如压缩)
        BuildTarget.StandaloneWindows // 构建目标平台
    );
    
  • 打包输出目录构建后的 AB 包会输出到指定文件夹,通常包括:

    • .manifest 文件:记录资源依赖关系
    • AB 文件:实际的资源包
  • 选项配置(BuildAssetBundleOptions): 可以设置打包时的选项,如是否压缩、是否强制重新构建等。例如:

    • UncompressedAssetBundle:不压缩资源。
    • ForceRebuildAssetBundle:强制重新打包所有内容。
  • 目标平台(BuildTarget) AB 包是平台相关的(如 Windows、Android、iOS)。在构建时需要指定目标平台。


3. Inspect(检查)

检查阶段 是验证和分析打包结果的过程,用于确保 AB 包的正确性和优化性。

主要操作:
  • 查看打包内容: 在 AssetBundle Browser 中检查每个 AB 包的内容,验证资源是否正确划分到指定的包中。

  • 分析依赖关系: 检查资源的依赖关系是否被正确处理,避免未预期的重复资源。

  • 校验打包结果

    • 确保资源没有丢失或打包错误。
    • 通过 manifest 文件检查每个资源的依赖树
  • 工具支持

    • Unity 自带的 AssetBundle Browser 提供了直观的内容和依赖关系查看功能。
    • 第三方工具可以深入分析 AB 包内容和性能。

简要总结它们的关系:

阶段描述工具或代码示例
Configure配置资源的打包逻辑,包括分配标签和优化依赖关系。AssetBundle Browser
Build根据配置将资源正式打包成 AB 文件,并生成 .manifest 文件。BuildPipeline.BuildAssetBundles
Inspect检查打包结果的正确性和资源分布,分析依赖关系是否合理。AssetBundle Browser 或自定义分析工具

lua---背包实践的拾疑过程↑↑↑

由Lua的多态延伸出的含c#的疑惑

隐藏还可以调用父类的,功能更广,为什么反而说“不支持多态”

隐藏是静态的,重写是动态的

在功能范围上,隐藏(method hiding)确实可以调用父类的方法,看起来比重写(override)更“灵活”。然而,“不支持多态”指的是隐藏机制无法实现 真正的运行时动态行为,这与多态的定义和目的直接相关。

多态强调的是一种运行时的动态,底层的虚函数表也是有这个意味,可以通过virtual归类虚函数存入表中,子类要重写的话就去这个大虚函数表里找对应的地址然后修改

不仅仅是同名函数在子类和父类中执行不同的功能的简单概括

虚函数表(VTable)是其背后重要的实现机制。这套机制让子类可以在运行时动态替换父类的方法实现,从而实现多态的特性

虚函数表 (VTable) 的工作原理

虚函数表是一个由编译器生成的数据结构,用于支持运行时的动态方法绑定。具体来说:

  • 父类的虚函数表: 包含所有 virtual 方法的地址。

  • 子类的虚函数表: 继承父类的虚函数表,但会用子类重写的方法地址替换父类的对应地址。(这里很有Lua的味道了)

  • 对象的类型指针: 每个对象实例都保存一个指向其对应类型的虚函数表的指针。调用方法时,程序通过这个指针找到对应的 VTable。

对于非虚方法(普通方法):

  1. 编译器直接将方法的地址硬编码到调用点,不会涉及虚函数表。
  2. 这种方法调用是静态绑定的,完全依赖于变量的编译时类型。

具体行为解析

假设父表和子表结构如下:

local Parent = { name = "parent_name" }

function Parent:setName(newName)
    self.name = newName -- 修改的是调用者的 name
end

local Child = { name = "child_name" }
setmetatable(Child, { __index = Parent })

调用分析

  1. 调用方法时,self 指向的是谁?Child:setName("new_child_name") 被调用时:

    • Child 是调用者。
    • self 指向的是 Child 表,而不是 Parent 表。
  2. 具体修改逻辑

    • Lua 会先找到 setName 方法,查找顺序是:
      1. Child 表中找 setName,找不到
      2. 查找 Child 的元表的 __index,定位到 Parent:setName
    • 定位之后,执行 Parent:setName 方法时,参数键name在child表中找-->找到了,然后对该键值进行修改

实际执行效果

print(Child.name) -- 输出 "child_name"
Child:setName("new_child_name")
print(Child.name) -- 输出 "new_child_name"(子表的 name 被修改)
print(Parent.name) -- 输出 "parent_name"(父表未受影响)

这里self.base.Move(self)为什么不用self.base:Move() 

这样就省掉了self的语法糖啊

---因为这里的self是不一样的,:语法糖后面接上的对象表{}是冒号前面的对象,而多态的实现是,调用父类的方法,传入自己的表对象,如果用语法糖了就是传入父类的表对象,省略的参数书写为父{},

C# 的继承与多态实现

(1) 继承的设计规则

C# 是一门强类型语言,继承和多态都是语言直接支持的特性,其规则由编译器和运行时(CLR, Common Language Runtime)共同保障:

  • 继承树:C# 的类结构是一个树形结构,每个类只能有一个直接父类(单继承)。
  • 方法查找顺序
    • 如果子类没有override,则直接调用父类的方法。
    • 如果子类override,则调用覆盖后的版本(典型的多态表现)
(2) 多态的实现方式

在 C# 中,多态依赖于两个关键字:virtualoverride,它们规定了运行时行为:

  • 如果方法被声明为 virtual,表示它支持子类重写。
  • 子类通过 override 关键字重写父类的方法
  • 调用时根据对象的实际类型选择方法,而不是声明类型。

在 C# 中,如果子类父类的方法名相同,没有用 overridenew 明确声明,编译器默认会将其视为使用了 new,即 方法隐藏(method hiding)

多态的大致底层实现逻辑

每个类在编译时,如果包含 virtual 方法,C# 编译器会为该类生成一个 虚函数表 (VTable)。VTable 是一个数据结构,存储了该类所有虚函数的地址。

  • 基类的 VTable: 存储基类的 virtual 方法实现的地址。

  • 子类的 VTable: 如果子类 override 了基类的某个 virtual 方法,则子类的 VTable 中会替换对应的基类方法地址,指向子类方法的实现。

调用过程

当一个对象调用方法时:

  1. 程序会先根据对象的实际类型找到该类型的 VTable。
  2. 再从 VTable 中查找对应的方法地址,并跳转执行。

所以c#的多态在Lua完全是个附带的机制...

a表为空,查里面的元素的顺序是:先己,后元表里的__index对象

自己查不到自然就去查父亲的

自己查到了直接就用自己的

真无语了,这个多态实现的,太随意了,真的给我整麻了

所以本质还是万物皆为表,表{}是Lua的重心,对象实例化和继承才都可以用同一个模子做

Lua 不会自动把 b.name 的值赋给 a.name。当你访问 a.name 时,Lua 只是通过元表的 __index 查找并返回 b.name 的值,但并不会修改 a 本身的内容。

我对设置元表的作用还有一些疑惑,

如果我设了一个空表a,然后绑定了b为元表b.__index=b

现在我的表a仍然是空的

然后b里有一个name=“123”

我现在a.name

Lua先去找a表里,发现没有name这个键,然后顺着__index去b表里找

找到b里的“123”后,会给a表里增加一个name=“123”

然后返回a表的这个“123”(错误)

Lua 太自由了,没有很多强制性的语法规则,这种“开放性”可能让你觉得没有完全掌握它的边界。

什么场景用 : 更好?什么场景用 .

Lua 的“封装”本质上是一种约定,而不是强制。它的机制是开放的,没有“权限检查”之类的强约束,程序员需要自己对代码进行规范和管理。

Lua 的哲学是“程序员需要负责规范”

Lua 的设计哲学非常强调灵活性,这与它作为嵌入式脚本语言的初衷有关。
它更倾向于**“信任程序员”**,而不是通过严格规则限制行为。这种信任带来了巨大的灵活性,但也牺牲了一部分安全性。

你会觉得 Lua 的“封装”很脆弱,因为表中的任何字段都可以被外部轻松修改或删除。而传统 OOP 语言通过严格的语法约束(如私有变量和方法)提供了更高的安全性。

;