许多应用程序需要在Magic Leap One上读写用户数据、记录日志、游戏分数等。所以本篇文章展示如何使用应用程序文件系统存储用户数据。在应用程序的私有沙箱文件系统中,可以根据安全设置将文件保存到不同的位置。无论具体位置如何,任何应用程序的私有文件夹都是:
documents/C1/ (writable_dir_path_locked_and_unlocked)
documents/C2/ (writable_dir_path)
tmp/ (tmp_dir_path)
关于这部分我们需要掌握:
使用Lumin Runtime Editor将UITextEdit对象(用于输入文本的区域)应用到项目中。
从系统键盘接收输入数据。
检索应用程序将写入的路径。
从文件中读取数据。
当用户点击控件的bumper按钮时,将数据存储到文件中。
实现效果预览:
从Package Manager启动Lumin Runtime Editor。
在Lumin Runtime Editor中创建一个新项目。
将其命名为AppStorage,然后单击Create Project。
在场景层次结构下,右键单击根节点。
单击 Insert > UI Nodes > UITextEdit。UITextEdit提供了一种使用系统键盘输入文本的简单方法。
将UITextEdit的名称更改为TextEdit,并应用以下设置。
设置Scale值为 (3.0, 3.0, 3.0)。
保存场景。
场景效果如下图所示:
启动Microsoft Visual Studio。
点击File > New > Import Magic Leap Mabu Projects。
点击Import。
在解决方案资源管理器下,AppStorage项目应该如下图所示:
打开Visual Studio Code。
点击左侧的图标。
在Lumin SDK窗口标题中,单击图标,设置Lumin SDK的路径(如果还没有设置)。通常是:/Users/user/MagicLeap/mlsdk/v0.x.x。
在签名证书窗口标题中,单击图标,然后设置.cert包签名证书文件的路径(如果还没有设置)。
回到Lumin Runtime Editor,在项目菜单上,单击Code Generation > Open code in External Editor。
出现下面窗口时单击OK即可。
项目文件夹如下图:
在Source Files文件夹(或Visual Studio code中的code/src文件夹)下,打开AppStorage.cpp脚本。Lumin Runtime已经为我们生成了大量的代码。下面我们将重点讨论需要修改的部分。
Directives, Namespaces and Globals
在脚本的顶部添加以下指令,在最后一个#include指令之后:
#include
#include
#include
接下来,添加以下指令和声明:
using namespace lumin;
using namespace lumin::ui;
using namespace std;
namespace {
UiTextEdit* textEdit;
}
命名空间lumin、lumin::ui和std简化了代码。
UiTextEdit* textEdit会将我们场景的TextEdit链接到脚本中的textEdit对象。
glm::vec3 AppStorage::getInitialPrismSize()更改为:
const glm::vec3 AppStorage::getInitialPrismSize() const {
return glm::vec3(2.0f, 2.0f, 0.5f);
}
初始化
int AppStorage::init() {
ML_LOG(Debug, "AppStorage Initializing.");
createInitialPrism();
lumin::ui::Cursor::SetScale(prism_, 0.03f);
spawnInitialScenes();
//cast the TextEdit node
textEdit = static_cast(prism_->findNode("TextEdit", prism_->getRootNode()));
//retrieve the writeable path and the prepare the data.txt file to be read
ifstream myfilein(BaseApp::getWritablePath() + "data.txt");
string line, text;
text = "";
//if the file is open read its contents line by line
if (myfilein.is_open()) {
while (getline(myfilein, line)) {
text += line + '\n';
}
myfilein.close();
}
//set the text of the textEdit to become the file's contents
textEdit->setText(text);
return 0;
}
createInitialPrism()创建Prism。
ui::Cursor::SetScale(prism_, 0.03f)设置Prism (prism_)光标的比例。
spawnInitialScenes() 实例化我们唯一的场景。
textEdit = static_cast
BaseApp::getWritablePath()检索应用程序写入的私有路径。
while (getline(myfilein, line))循环读取文件的内容,并逐渐将它们添加到文本字符串中。
textEdit->setText(text)将textEdit的文本更改为存储在文件中的内容,如果文件不存在,则更改为空字符串。
注意:不要在应用程序中硬编码路径。路径会随着安全设置的不同而变化。使用getWritablePath()回调即可。
bool AppStorage::eventListener(lumin::ServerEvent* anEvent)方法在运行时监听事件并接收bumper按钮点击。
bool AppStorage::eventListener(lumin::ServerEvent* anEvent) {
if (anEvent->isInputEventType()) {
InputEventData* inputEventData = static_cast(anEvent);
KeyInputEventData* keyEventData = static_cast(inputEventData);
if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_BUMPER) {
ofstream myfileout(BaseApp::getWritablePath() + "data.txt");
myfileout << textEdit->getText() + "\n";
myfileout.close();
}
}
return false;
}
此方法将每个事件捕获为ServerEvent* anEvent。如果事件是InputEvent(引用输入的事件),则执行以下操作:
将InputEventData转换为InputEventData。
将相关的KeyInputEventData转换为keyEventData。
检查keyEventData是否引用AKEYCODE_EX_BUMPER事件。
如果bumper被点击了,则:
ofstream myfileout(BaseApp::getWritablePath() + "data.txt")备编写相同的data.txt文件。
myfileout << textEdit->getText() + "\n"将用户的输入写入文件。
myfileout.close()关闭文件。
构建签名.mpk文件并将其安装到设备上的过程取决于使用的IDE。
Visual Studio (Windows)
Visual Studio Code (Windows / macOS)
如果这是你第一次在设备上部署应用程序,则会打开一个安装证书的通知。在这种情况下,接受证书安装应用程序即可。
一旦证书被接受,地球模型就会出现。用OK手势让它旋转,或者用手指手势(看到你指尖上的一个小立方体)。长时间按住触发器移动整个Prism的位置。
如果应用程序无法识别手的姿势,关闭所有应用程序(长时间按住Home按钮),重新启动我们的应用程序。
如果你仍然什么也没看到,看看你周围的一切,因为设备启动时的方向决定了它在世界上的起始方向。
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace lumin;
using namespace lumin::ui;
using namespace std;
//using namespace lumin
namespace {
UiTextEdit* textEdit;
}
AppStorage::AppStorage() {
ML_LOG(Debug, "AppStorage Constructor.");
// Place your constructor implementation here.
}
AppStorage::~AppStorage() {
ML_LOG(Debug, "AppStorage Destructor.");
// Place your destructor implementation here.
}
const glm::vec3 AppStorage::getInitialPrismSize() const {
return glm::vec3(2.0f, 2.0f, 0.5f);
}
void AppStorage::createInitialPrism() {
prism_ = requestNewPrism(getInitialPrismSize());
if (!prism_) {
ML_LOG(Error, "AppStorage Error creating default prism.");
abort();
}
prismSceneManager_ = new PrismSceneManager(prism_);
}
int AppStorage::init() {
ML_LOG(Debug, "AppStorage Initializing.");
createInitialPrism();
lumin::ui::Cursor::SetScale(prism_, 0.03f);
spawnInitialScenes();
//cast the TextEdit node
textEdit = static_cast(prism_->findNode("TextEdit", prism_->getRootNode()));
//retrieve the writeable path and the prepare the data.txt file to be read
ifstream myfilein(BaseApp::getWritablePath() + "data.txt");
string line, text;
text = "";
//if the file is open read its contents line by line
if (myfilein.is_open()) {
while (getline(myfilein, line)) {
text += line + '\n';
}
myfilein.close();
}
//set the text of the textEdit to become the file's contents
textEdit->setText(text);
return 0;
}
int AppStorage::deInit() {
ML_LOG(Debug, "AppStorage Deinitializing.");
// Place your deinitialization here.
return 0;
}
void AppStorage::spawnInitialScenes() {
// Iterate over all the exported scenes
for (auto& exportedSceneEntry : scenes::externalScenes ) {
// If this scene was marked to be instanced at app initialization, do it
const SceneDescriptor &sd = exportedSceneEntry.second;
if (sd.getInitiallySpawned()) {
lumin::Node* const spawnedRoot = prismSceneManager_->spawn(sd);
if (spawnedRoot) {
if (!prism_->getRootNode()->addChild(spawnedRoot)) {
ML_LOG(Error, "AppStorage Failed to add spawnedRoot to the prism root node");
abort();
}
}
}
}
}
bool AppStorage::updateLoop(float fDelta) {
// Place your update here.
// Return true for your app to continue running, false to terminate the app.
return true;
}
bool AppStorage::eventListener(lumin::ServerEvent* anEvent) {
if (anEvent->isInputEventType()) {
InputEventData* inputEventData = static_cast(anEvent);
KeyInputEventData* keyEventData = static_cast(inputEventData);
if (keyEventData->keyCode() == input::KeyCodes::AKEYCODE_EX_BUMPER) {
ofstream myfileout(BaseApp::getWritablePath() + "data.txt");
myfileout << textEdit->getText() + "\n";
myfileout.close();
}
}
return false;
}
------AR Portal(AR开发者社区)整理
关注微信公众号(AR开发者交流社区,提供AR开发干货,推动AR内容发展):AR开发者社区