如何在 LibGDX 中实现 Sugar ORM

How can I implement Sugar ORM in LibGDX

我们是一群法国学生。我们正在开发一款需要数据库的游戏。为了简化代码,我们使用 LibGdx。 但是,Sugar ORM 似乎并未与应用程序绑定。我们不能扩展 SugarRecord。

我放了AndroidManifest.xml和build.gradle的代码(模块:Android)。请问我们如何解决这个问题?

编辑:我们在 Android 文件夹中创建 类。 Sugar Orm 未在核心中定义。

<application
    android:name="com.orm.SugarApp"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/GdxTheme">
    <meta-data
        android:name="DATABASE"
        android:value="AppName.db" />
    <meta-data
        android:name="VERSION"
        android:value="1" />
    <meta-data
        android:name="QUERY_LOG"
        android:value="true" />
    <meta-data
        android:name="DOMAIN_PACKAGE_NAME"
        android:value="com.AppName" />

build.gradle(模块:Android):

dependencies {
    compile 'com.github.satyan:sugar:1.3'
    //other dependencies
}

谢谢!

您好!我将做一些假设(如果他们错了请纠正我,我们将在下面完善答案):

  1. 问题是您正在尝试扩展 SugarRecord<T>,而您的 compiler/IDE 看不到 class。
  2. 您尝试扩展 SugarRecord<T> 的 class 位于 core/src/...,而不是 android/src/...

有什么问题?

当您编写软件时(如果是游戏则无关紧要),您需要注意如何将软件划分为多个部分以及这些部分如何交互。特别是,您希望能够知道对一个部分的更改何时会破坏另一部分。

libGDX 根据平台将其代码分成几部分(这就是为什么你有一个 core 项目和一个 desktop 项目和一个 android 项目)。 core 应该拥有独立于平台的所有代码(即无论您在 PC 还是移动设备上都是相同的东西),而您的其他项目负责平台具体内容。

Sugar ORM 是 android 特定的东西,因此您(正确地)将其放入 android gradle 项目依赖项中。但是,这意味着只有 android/src 文件夹下的代码知道 Sugar ORM 并可以使用它。我很确定这就是导致您出现问题的原因。问题是,你要保存的 classes 几乎肯定在 core/src 下,它们 属于 那里,那么我们如何解决它?

修复的简单方法

如果您只打算让您的程序在 android 上 运行(并且您的项目的截止日期很紧 :wink: ),您可以将依赖项从 Android gradle 项目到您的核心 gradle 项目。这将允许您在任何地方使用那些 classes,但是当您尝试构建 Desktop/iOS/HTML 项目时,这将意味着麻烦。

修复的正确方法

如果你想以 正确的 方式修复它(也许你疯狂的编码能力会给你的教授留下深刻印象),你需要使用一种叫做 Dependency Injection 的东西。依赖注入是当我们获取代码需要的东西,并在 运行 时将其提供给代码。这意味着我们可以即时决定是传递 android 数据库还是 iOS 数据库。正如 @Arctic45 在评论中所说,libGDX Wiki 对这项技术进行了简要概述,但我们会更详细地介绍。

对于这个例子,我假设一个简单的游戏 Monster class 看起来像这样:

// Lives in core
public class Monster {
    public String name;     // prénom
    public int hitpoints;   // points de dommage

    public Monster() {
        this.name = "Tim the Unnamed";
        this.hitpoints = 1;
    }
    public Monster(String name, int hitpoints) {
        this.name = name;
        this.hitpoints = hitpoints;
    }
    @Override
    public String toString() {
        return String.format("{name: '%s', hitpoints: %n}", this.name, this.hitpoints);
    }

    public void attack(Monster other) {
        // Game specific logic...
    }
}

现在我们希望能够将其保存到数据库中,但我们不知道它是 Android 数据库还是 iOS 数据库,甚至可能是一个数据库在网络上的某个地方(比如 Firebase)。我们如何处理?

我们所做的是给核心一个DatabaseWrapper接口。这个接口提供了我们需要的方法,但不包括它们是如何实现的——它就像一个承诺。 core 可以计划使用这些方法,然后我们会在知道我们在哪个平台上后提供它们。下面是一个示例应用程序,它说明了这种技术:

// Lives in core
// Replace with your application
public class LibGDXTestbed extends ApplicationAdapter {
    DatabaseWrapper database;

    public LibGDXTestbed() { } // For platforms that don't have databases to inject.

    public LibGDXTestbed(DatabaseWrapper database) {
        this.database = database;
    }

    /**
     * For demo purposes, add a new randomized monster to the database, then log a list of all the
     * monsters created to date.
     */
    @Override
    public void create () {
        if(database != null) {
            createMonster();
            printMonsters();
        } else {
            Gdx.app.error("WARNING", "No database provided. Load/Save Functionality Disabled.");
        }
    }

    // Helper method
    private void createMonster() {
        // Create a set of names we can use for new monsters.
        String[] names = {"Fred", "Mary", "Jean", "Tim"};

        String randomName = new Array<String>(names).random();
        int randomHP = MathUtils.random(100);
        database.saveMonster(new Monster(randomName, randomHP));
    }

    // Helper method
    private void printMonsters() {
        for(Monster monster : database.getMonsters()) {
            Gdx.app.log("DEBUG", monster.toString());
        }
    }
}

请注意,以上内容对 Sugar ORM 一无所知,也不对数据库的工作方式做出任何假设。

包装器本身看起来像这样:

// Located in core
public interface DatabaseWrapper {
    public void saveMonster(Monster monster);
    public List<Monster> getMonsters();
}

现在这有点做作(可以重构为更通用),但它说明了这一点。

接下来,我们创建实现此数据库所需的 android 特定代码。首先,我们将创建一个扩展 SugarRecordSugarMonster class(因为我们不想用我们的核心 Monster class 本身来做):

// Lives in android/src
public class SugarMonster extends SugarRecord<SugarMonster> {
    public String name;     // prénom
    public int hitpoints;   // points de dommage

    public SugarMonster() {
    }

    public SugarMonster(Monster monster) {
        this.name = monster.name;
        this.hitpoints = monster.hitpoints;
    }
}

我们还需要一个 SugarWrapper class 来实现我们的 DatabaseWrapper class 在幕后使用 Sugar ORM:

// Lives in android/src
public class SugarWrapper implements DatabaseWrapper {
    @Override
    public void saveMonster(Monster monster) {
        SugarMonster data = new SugarMonster(monster);
        data.save();
    }

    @Override
    public List<Monster> getMonsters() {
        List<SugarMonster> records = SugarMonster.listAll(SugarMonster.class);
        ArrayList<Monster> monsters = new ArrayList<>();
        for(SugarMonster record : records) {
            monsters.add(new Monster(record.name, record.hitpoints));
        }
        return monsters;
    }
}

最后,我们需要更新 AndroidLauncher class 以注入我们的数据库包装器:

// Lives in android/src
public class AndroidLauncher extends AndroidApplication {
    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
        initialize(new LibGDXTestbed(new SugarWrapper()), config);
    }
}

奖金

另一件很酷的事情是,如果您以 "right" 方式实现它,它会为测试提供一些很酷的可能性。如果您想针对您的代码编写单元测试,您可以创建一个 TestWrapper 实现 DatabaseWrapper 并使用静态数据模拟数据库功能:

public class TestWrapper implements DatabaseWrapper {
    List<Monster> monsters;
    public TestWrapper() {
        this.monsters = new ArrayList<>();
        this.monsters.add(new Monster("Tim the Tester", 123));
    }
    @Override
    public void saveMonster(Monster monster) {
        this.monsters.add(monster);
    }

    @Override
    public List<Monster> getMonsters() {
        return this.monsters;
    }
}