HoloLens (1. Generation) und Azure 305: Funktionen und Speicher
Hinweis
Die Tutorials der Mixed Reality Academy wurden im Hinblick auf HoloLens (1. Gen.) und immersive Mixed Reality-Headsets entworfen. Daher halten wir es für wichtig, diese Tutorials für Entwickler verfügbar zu halten, die noch nach Anleitung beim Entwickeln für diese Geräte suchen. Diese Tutorials werden nicht mit den neuesten Toolsets oder Interaktionen aktualisiert, die für HoloLens 2 verwendet werden. Sie werden gewartet, um weiterhin auf den unterstützten Geräten zu funktionieren. Es wird eine neue Reihe von Tutorials geben, die in Zukunft veröffentlicht werden, die zeigen, wie für HoloLens 2 entwickelt werden kann. Dieser Hinweis wird mit einem Link zu diesen Tutorials aktualisiert, wenn sie veröffentlicht werden.
In diesem Kurs erfahren Sie, wie Sie Azure Functions und Daten mit einer Azure Storage-Ressource in einer Mixed Reality-Anwendung erstellen und verwenden.
Azure Functions ist ein Microsoft-Dienst, mit dem Entwickler kleine Codeteile, "Funktionen", in Azure ausführen können. Dies bietet eine Möglichkeit, Arbeit an die Cloud zu delegieren, anstatt an Ihre lokale Anwendung, was viele Vorteile haben kann. Azure Functions unterstützt mehrere Entwicklungssprachen, darunter C#, F#, Node.js, Java und PHP. Weitere Informationen finden Sie im artikel Azure Functions.
Azure Storage ist ein Microsoft-Clouddienst, mit dem Entwickler Daten speichern können, mit der Versicherung, dass sie hochverfügbar, sicher, dauerhaft, skalierbar und redundant sind. Dies bedeutet, dass Microsoft alle Wartungs- und kritischen Probleme für Sie übernimmt. Weitere Informationen finden Sie im Artikel Azure Storage.
Nach Abschluss dieses Kurses verfügen Sie über eine immersive Mixed Reality-Headset-Anwendung, die folgendes ausführen kann:
- Erlauben Sie dem Benutzer, eine Szene zu betrachten.
- Lösen Sie das Spawning von Objekten aus, wenn der Benutzer eine 3D-Schaltfläche anschaut.
- Die gespawnten Objekte werden von einer Azure-Funktion ausgewählt.
- Während jedes Objekt erstellt wird, speichert die Anwendung den Objekttyp in einer Azure-Datei in Azure Storage.
- Beim zweiten Laden werden die Azure-Dateidaten abgerufen und zum Wiedergeben der Spawningaktionen aus dem vorherigen instance der Anwendung verwendet.
In Ihrer Anwendung liegt es bei Ihnen, wie Sie die Ergebnisse in Ihr Design integrieren. In diesem Kurs erfahren Sie, wie Sie einen Azure-Dienst in Ihr Unity-Projekt integrieren. Es ist Ihre Aufgabe, das Wissen, das Sie aus diesem Kurs gewinnen, zu nutzen, um Ihre Mixed Reality-Anwendung zu verbessern.
Geräteunterstützung
Kurs | HoloLens | Immersive Headsets |
---|---|---|
MR und Azure 305: Funktionen und Speicher | ✔️ | ✔️ |
Hinweis
Während sich dieser Kurs hauptsächlich auf Windows Mixed Reality immersiven Headsets (VR) konzentriert, können Sie das, was Sie in diesem Kurs lernen, auch auf Microsoft HoloLens anwenden. Während Sie den Kurs befolgen, werden Ihnen Notizen zu allen Änderungen angezeigt, die Sie möglicherweise zur Unterstützung von HoloLens anwenden müssen.
Voraussetzungen
Hinweis
Dieses Tutorial richtet sich an Entwickler, die über grundlegende Erfahrungen mit Unity und C# verfügen. Bitte beachten Sie auch, dass die Voraussetzungen und schriftlichen Anweisungen in diesem Dokument das darstellen, was zum Zeitpunkt des Schreibens (Mai 2018) getestet und überprüft wurde. Sie können die neueste Software verwenden, wie im Artikel Installieren der Tools aufgeführt, aber es sollte nicht davon ausgegangen werden, dass die Informationen in diesem Kurs perfekt dem entsprechen, was Sie in neuerer Software finden, als die unten aufgeführten.
Wir empfehlen die folgende Hard- und Software für diesen Kurs:
- Ein Entwicklungs-PC, kompatibel mit Windows Mixed Reality für die Entwicklung von immersiven (VR)-Headsets
- Windows 10 Fall Creators Update (oder höher) mit aktiviertem Entwicklermodus
- Das neueste Windows 10 SDK
- Unity 2017.4
- Visual Studio 2017
- Ein Windows Mixed Reality immersives Headset (VR) oder Microsoft HoloLens mit aktiviertem Entwicklermodus
- Ein Abonnement für ein Azure-Konto zum Erstellen von Azure-Ressourcen
- Internetzugriff für Azure-Setup und Datenabruf
Vorbereitung
Um Probleme beim Erstellen dieses Projekts zu vermeiden, wird dringend empfohlen, das in diesem Tutorial erwähnte Projekt in einem Stamm- oder Fast-Root-Ordner zu erstellen (lange Ordnerpfade können zur Buildzeit Zu Problemen führen).
Kapitel 1: Azure-Portal
Um den Azure Storage-Dienst verwenden zu können, müssen Sie ein Speicherkonto im Azure-Portal erstellen und konfigurieren.
Melden Sie sich beim Azure-Portal an.
Hinweis
Wenn Sie noch nicht über ein Azure-Konto verfügen, müssen Sie ein Azure-Konto erstellen. Wenn Sie dieses Tutorial in einer Unterrichts- oder Labsituation befolgen, bitten Sie Ihren Kursleiter oder einen der Verantwortlichen um Hilfe beim Einrichten Ihres neuen Kontos.
Nachdem Sie angemeldet sind, klicken Sie in der oberen linken Ecke auf Neu , suchen Sie nach Speicherkonto, und klicken Sie auf DIE EINGABETASTE.
Hinweis
Das Wort Neu wurde in neueren Portalen möglicherweise durch Ressource erstellen ersetzt.
Die neue Seite enthält eine Beschreibung des Azure Storage-Kontodiensts . Wählen Sie unten links in dieser Eingabeaufforderung die Schaltfläche Erstellen aus, um eine Zuordnung zu diesem Dienst zu erstellen.
Nachdem Sie auf Erstellen geklickt haben:
Fügen Sie einen Namen für Ihr Konto ein, und beachten Sie, dass dieses Feld nur Zahlen und Kleinbuchstaben akzeptiert.
Wählen Sie unter Bereitstellungsmodelldie Option Ressourcen-Manager aus.
Wählen Sie unter Kontoartdie Option Speicher (universell v1) aus.
Bestimmen Sie den Speicherort für Ihre Ressourcengruppe (wenn Sie eine neue Ressourcengruppe erstellen). Der Speicherort befindet sich idealerweise in der Region, in der die Anwendung ausgeführt wird. Einige Azure-Ressourcen sind nur in bestimmten Regionen verfügbar.
Wählen Sie unter Replikationdie Option Read-Access-Geo-Redundant Storage (RA-GRS) aus.
Wählen Sie für Leistung die Option Standard aus.
Lassen Sie sichere Übertragung erforderlich als Deaktiviert.
Wählen Sie ein Abonnementaus.
Wählen Sie eine Ressourcengruppe aus, oder erstellen Sie eine neue. Eine Ressourcengruppe bietet eine Möglichkeit zum Überwachen, Steuern des Zugriffs, Bereitstellen und Verwalten der Abrechnung für eine Sammlung von Azure-Ressourcen. Es wird empfohlen, alle Azure-Dienste, die einem einzelnen Projekt (z. B. diesen Labs) zugeordnet sind, unter einer gemeinsamen Ressourcengruppe zu halten.
Wenn Sie mehr über Azure-Ressourcengruppen erfahren möchten, lesen Sie den Artikel Ressourcengruppe.
Sie müssen auch bestätigen, dass Sie die für diesen Dienst geltenden Geschäftsbedingungen verstanden haben.
Klicken Sie auf Erstellen.
Nachdem Sie auf Erstellen geklickt haben, müssen Sie warten, bis der Dienst erstellt wird. Dies kann eine Minute dauern.
Eine Benachrichtigung wird im Portal angezeigt, sobald der Dienst instance erstellt wurde.
Klicken Sie auf die Benachrichtigungen, um Ihre neue Dienst-instance zu erkunden.
Klicken Sie in der Benachrichtigung auf die Schaltfläche Zu Ressource wechseln, um Ihren neuen Dienst instance zu erkunden. Sie werden zu Ihrem neuen Speicherkontodienst instance weitergeleitet.
Klicken Sie auf Zugriffsschlüssel, um die Endpunkte für diesen Clouddienst anzuzeigen. Verwenden Sie Editor oder ähnliches, um einen Ihrer Schlüssel zur späteren Verwendung zu kopieren. Beachten Sie auch den Wert der Verbindungszeichenfolge , da er in der AzureServices-Klasse verwendet wird, die Sie später erstellen werden.
Kapitel 2: Einrichten einer Azure-Funktion
Sie schreiben jetzt eine Azure-Funktion in den Azure-Dienst.
Sie können eine Azure-Funktion verwenden, um fast alles zu tun, was Sie mit einer klassischen Funktion in Ihrem Code tun würden. Der Unterschied ist, dass jede Anwendung auf diese Funktion zugreifen kann, die über Anmeldeinformationen für den Zugriff auf Ihr Azure-Konto verfügt.
So erstellen Sie eine Azure-Funktion:
Klicken Sie in Ihrem Azure-Portal oben links auf Neu , suchen Sie nach Funktions-App, und klicken Sie auf DIE EINGABETASTE.
Hinweis
Das Wort Neu wurde in neueren Portalen möglicherweise durch Ressource erstellen ersetzt.
Die neue Seite enthält eine Beschreibung des Azure Function App Service. Wählen Sie unten links in dieser Eingabeaufforderung die Schaltfläche Erstellen aus, um eine Zuordnung zu diesem Dienst zu erstellen.
Nachdem Sie auf Erstellen geklickt haben:
Geben Sie einen App-Namen an. Hier können nur Buchstaben und Zahlen verwendet werden (Groß- oder Kleinbuchstaben sind zulässig).
Wählen Sie Ihr bevorzugtes Abonnement aus.
Wählen Sie eine Ressourcengruppe aus, oder erstellen Sie eine neue. Eine Ressourcengruppe bietet eine Möglichkeit zum Überwachen, Steuern des Zugriffs, Bereitstellen und Verwalten der Abrechnung für eine Sammlung von Azure-Ressourcen. Es wird empfohlen, alle Azure-Dienste, die einem einzelnen Projekt (z. B. diesen Labs) zugeordnet sind, unter einer gemeinsamen Ressourcengruppe zu halten.
Wenn Sie mehr über Azure-Ressourcengruppen erfahren möchten, lesen Sie den Artikel Ressourcengruppe.
Wählen Sie für diese Übung Windows als ausgewähltes Betriebssystem aus.
Wählen Sie Verbrauchsplan für den Hostingplan aus.
Bestimmen Sie den Speicherort für Ihre Ressourcengruppe (wenn Sie eine neue Ressourcengruppe erstellen). Der Speicherort befindet sich idealerweise in der Region, in der die Anwendung ausgeführt wird. Einige Azure-Ressourcen sind nur in bestimmten Regionen verfügbar. Um eine optimale Leistung zu erzielen, wählen Sie dieselbe Region wie das Speicherkonto aus.
Wählen Sie unter Speicher die Option Vorhandenes verwenden aus, und suchen Sie dann über das Dropdownmenü nach Ihrem zuvor erstellten Speicher.
Lassen Sie Application Insights für diese Übung deaktiviert.
Klicken Sie auf die Schaltfläche Erstellen .
Nachdem Sie auf Erstellen geklickt haben, müssen Sie warten, bis der Dienst erstellt wird. Dies kann eine Minute dauern.
Eine Benachrichtigung wird im Portal angezeigt, sobald der Dienst instance erstellt wurde.
Klicken Sie auf die Benachrichtigungen, um Ihre neue Dienst-instance zu erkunden.
Klicken Sie in der Benachrichtigung auf die Schaltfläche Zu Ressource wechseln, um Ihren neuen Dienst instance zu erkunden. Sie werden zu Ihrem neuen Funktions-App-Dienst instance weitergeleitet.
Zeigen Sie auf der Dashboard Funktions-App mit dem Mauszeiger auf Funktionen, die sich im bereich auf der linken Seite befindet, und klicken Sie dann auf das Symbol + (plus).
Stellen Sie auf der nächsten Seite sicher, dass Webhook + API ausgewählt ist, und wählen Sie unter Sprache auswählendie Option CSharp aus, da dies die sprache ist, die für dieses Tutorial verwendet wird. Klicken Sie abschließend auf die Schaltfläche Diese Funktion erstellen .
Sie sollten zur Codepage (run.csx) weitergeleitet werden, wenn nicht, klicken Sie auf die neu erstellte Funktion in der Liste Funktionen im Bereich auf der linken Seite.
Kopieren Sie den folgenden Code in Ihre Funktion. Diese Funktion gibt einfach eine zufällige ganze Zahl zwischen 0 und 2 zurück, wenn sie aufgerufen wird. Machen Sie sich keine Sorgen um den vorhandenen Code, fügen Sie ihn oben ein.
using System.Net; using System.Threading.Tasks; public static int Run(CustomObject req, TraceWriter log) { Random rnd = new Random(); int randomInt = rnd.Next(0, 3); return randomInt; } public class CustomObject { public String name {get; set;} }
Wählen Sie Speichern aus.
Das Ergebnis sollte wie in der folgenden Abbildung aussehen.
Klicken Sie auf Funktions-URL abrufen, und notieren Sie sich den angezeigten Endpunkt . Sie müssen es in die AzureServices-Klasse einfügen, die Sie später in diesem Kurs erstellen werden.
Kapitel 3: Einrichten des Unity-Projekts
Das Folgende ist eine typische Einrichtung für die Entwicklung mit Mixed Reality und ist daher eine gute Vorlage für andere Projekte.
Richten Sie Ihr immersives Mixed Reality-Headset ein und testen Sie es.
Hinweis
Für diesen Kurs benötigen Sie keine Bewegungscontroller. Wenn Sie Unterstützung beim Einrichten des immersiven Headsets benötigen, lesen Sie den Artikel Mixed Reality-Einrichtung.
Öffnen Sie Unity, und klicken Sie auf Neu.
Sie müssen nun einen Unity-Projektnamen angeben. Fügen Sie MR_Azure_Functions ein. Stellen Sie sicher, dass der Projekttyp auf 3D festgelegt ist. Legen Sie den Speicherort auf einen für Sie geeigneten Ort fest (denken Sie daran, dass näher an Stammverzeichnissen besser ist). Klicken Sie dann auf Projekt erstellen.
Wenn Unity geöffnet ist, lohnt es sich, zu überprüfen, ob der Standardskript-Editor auf Visual Studio festgelegt ist. Navigieren Sie zu Einstellungen bearbeiten>, und navigieren Sie dann im neuen Fenster zu Externe Tools. Ändern Sie den externen Skript-Editor in Visual Studio 2017. Schließen Sie das Fenster Einstellungen.
Wechseln Sie als Nächstes zuDateierstellungseinstellungen>, und wechseln Sie die Plattform auf Universelle Windows-Plattform, indem Sie auf die Schaltfläche Plattform wechseln klicken.
Wechseln Sie zuDateibuildeinstellungen>, und stellen Sie sicher, dass:
Zielgerät ist auf Beliebiges Gerät festgelegt.
Legen Sie für Microsoft HoloLens Zielgerät auf HoloLens fest.
Buildtyp ist auf D3D festgelegt
SDK ist auf Zuletzt installiert festgelegt.
Visual Studio-Version ist auf Zuletzt installiert festgelegt.
Build and Run ist auf Lokaler Computer festgelegt.
Speichern Sie die Szene, und fügen Sie sie dem Build hinzu.
Wählen Sie dazu Offene Szenen hinzufügen aus. Es wird ein Fenster zum Speichern angezeigt.
Erstellen Sie einen neuen Ordner für diese und jede zukünftige Szene, und wählen Sie dann die Schaltfläche Neuer Ordner aus, um einen neuen Ordner zu erstellen, und nennen Sie ihn Szenen.
Öffnen Sie Den neu erstellten Ordner Scenes , und geben Sie im Textfeld Dateiname:den Namen FunctionsScene ein, und drücken Sie dann Speichern.
Die restlichen Einstellungen in Buildeinstellungen sollten vorerst als Standard beibehalten werden.
Klicken Sie im Fenster Buildeinstellungen auf die Schaltfläche Playereinstellungen . Dadurch wird der zugehörige Bereich in dem Bereich geöffnet, in dem sich der Inspektor befindet.
In diesem Bereich müssen einige Einstellungen überprüft werden:
Auf der Registerkarte Andere Einstellungen :
- Die Skriptlaufzeitversion sollte experimentell (.NET 4.6 Equivalent) sein, was einen Neustart des Editors auslöst.
- Skript-Back-End sollte .NET sein
- API-Kompatibilitätsgrad sollte .NET 4.6 sein
Überprüfen Sie auf der Registerkarte Veröffentlichungseinstellungen unter Funktionen Folgendes:
InternetClient
Aktivieren Sie weiter unten im Bereich unter XR-Einstellungen (unter Veröffentlichungseinstellungen) Das Kontrollkästchen Virtual Reality Wird unterstützt, stellen Sie sicher, dass das Windows Mixed Reality SDK hinzugefügt wurde.
Zurück in den Buildeinstellungenunity C#-Projekte ist nicht mehr ausgegraut. aktivieren Sie das Kontrollkästchen neben diesem.
Schließen Sie das Fenster „Build Settings“ (Buildeinstellungen).
Speichern Sie Ihre Szene und Ihr Projekt (FILE>SAVE SCENE /FILE>SAVE PROJECT).
Kapitel 4: Einrichten der Hauptkamera
Wichtig
Wenn Sie die Unity Setup-Komponenten dieses Kurses überspringen und direkt mit dem Code fortfahren möchten, können Sie dieses UNITY-Paket herunterladen und als benutzerdefiniertes Paket in Ihr Projekt importieren. Dies enthält auch die DLLs aus dem nächsten Kapitel. Fahren Sie nach dem Import aus Kapitel 7 fort.
Im Hierarchiebereich finden Sie ein Objekt namens Hauptkamera. Dieses Objekt stellt Ihren "Kopf"-Standpunkt dar, sobald Sie sich "in" Ihrer Anwendung befinden.
Wählen Sie mit dem Unity-Dashboard vor Ihnen das Hauptkamera-GameObject aus. Sie werden feststellen, dass im Inspektorbereich (in der Regel rechts im Dashboard) die verschiedenen Komponenten dieses GameObject angezeigt werden, wobei die Transformation oben, gefolgt von der Kamera und einigen anderen Komponenten, angezeigt wird. Sie müssen die Transformation der Hauptkamera zurücksetzen, damit sie richtig positioniert ist.
Wählen Sie hierzu das Zahnradsymbol neben der Transformationskomponente der Kamera und dann Zurücksetzen aus.
Aktualisieren Sie dann die Transformationskomponente so, dass sie wie folgt aussieht:
Transformieren – Position
X | J | Z |
---|---|---|
0 | 1 | 0 |
Transformieren – Drehung
X | J | Z |
---|---|---|
0 | 0 | 0 |
Transformieren – Skalieren
X | J | Z |
---|---|---|
1 | 1 | 1 |
Kapitel 5: Einrichten der Unity-Szene
Klicken Sie mit der rechten Maustaste in einen leeren Bereich des Hierarchiebereichs, und fügen Sie unter 3D-Objekt eine Ebene hinzu.
Wenn das Plane-Objekt ausgewählt ist, ändern Sie die folgenden Parameter im Inspektorbereich:
Transformieren – Position
X | J | Z |
---|---|---|
0 | 0 | 4 |
Transformieren – Skalieren
X | J | Z |
---|---|---|
10 | 1 | 10 |
Klicken Sie mit der rechten Maustaste in einen leeren Bereich des Hierarchiebereichs, und fügen Sie unter 3D-Objekt einen Cube hinzu.
Benennen Sie den Cube in GazeButton um (wenn der Cube ausgewählt ist, drücken Sie "F2").
Ändern Sie die folgenden Parameter für Transformationsposition im Inspektorbereich:
X J Z 0 3 5 Klicken Sie auf die Dropdownschaltfläche Tag und dann auf Tag hinzufügen , um den Bereich Tags & Ebenen zu öffnen.
Wählen Sie die Schaltfläche + (Plus) aus, geben Sie im Feld Neuer TagnamegazeButton ein, und drücken Sie Speichern.
Klicken Sie im Hierarchiebereich auf das GazeButton-Objekt, und weisen Sie im Inspektorbereich das neu erstellte GazeButton-Tag zu.
Klicken Sie im Hierarchiebereich mit der rechten Maustaste auf das GazeButton-Objekt, und fügen Sie ein leeres GameObject hinzu (das als untergeordnetes Objekt hinzugefügt wird).
Wählen Sie das neue Objekt aus, und benennen Sie es in ShapeSpawnPoint um.
Ändern Sie die folgenden Parameter für Transformationsposition im Inspektorbereich:
X J Z 0 -1 0
Als Nächstes erstellen Sie ein 3D-Text-Objekt, um Feedback zur status des Azure-Diensts zu geben.
Klicken Sie im Hierarchiebereich erneut mit der rechten Maustaste auf gazeButton , und fügen Sie ein 3D-Objekt>3D Text-Objekt als untergeordnetes Objekt hinzu.
Benennen Sie das 3D Text-Objekt in AzureStatusText um.
Ändern Sie das Transformationsposition des AzureStatusText-Objekts wie folgt:
X J Z 0 0 -0,6 Ändern Sie das AzureStatusText-ObjektTransform Scale wie folgt: | X | Y | Z | | :---: | :---: | :---: | | 0.1 | 0.1 | 0.1 |
Hinweis
Machen Sie sich keine Sorgen, wenn es nicht zentral zu sein scheint, da dies behoben wird, wenn die folgende Text mesh-Komponente aktualisiert wird.
Ändern Sie die Text mesh-Komponente so, dass sie mit dem folgenden übereinstimmt:
Tipp
Die hier ausgewählte Farbe ist Hexadezimalfarbe: 000000FF, obwohl Sie sich frei entscheiden können, stellen Sie sicher, dass sie lesbar ist.
Die Struktur des Hierarchiebereichs sollte nun wie folgt aussehen:
Ihre Szene sollte jetzt wie folgt aussehen:
Kapitel 6: Importieren von Azure Storage für Unity
Sie verwenden Azure Storage für Unity (das selbst das .NET SDK für Azure nutzt). Weitere Informationen hierzu finden Sie im Artikel Azure Storage für Unity.
Es gibt derzeit ein bekanntes Problem in Unity, das erfordert, dass Plug-Ins nach dem Import neu konfiguriert werden müssen. Diese Schritte (4 bis 7 in diesem Abschnitt) sind nicht mehr erforderlich, nachdem der Fehler behoben wurde.
Um das SDK in Ihr eigenes Projekt zu importieren, stellen Sie sicher, dass Sie das neueste ".unitypackage" von GitHub heruntergeladen haben. Gehen Sie nun wie folgt vor:
Fügen Sie unitypackage-Datei zu Unity hinzu, indem Sie die Menüoption Assets>Import Package>Custom Package (Benutzerdefiniertes Paket importieren) verwenden.
Im eingeblendeten Feld Unity-Paket importieren können Sie unter Plug-In-Speicher> alles auswählen. Deaktivieren Sie alles andere, da es für diesen Kurs nicht benötigt wird.
Klicken Sie auf die Schaltfläche Importieren , um die Elemente zu Ihrem Projekt hinzuzufügen.
Wechseln Sie in der Projektansicht unter Plugins zum Ordner Storage, und wählen Sie nur die folgenden Plug-Ins aus:
Microsoft.Data.Edm
Microsoft.Data.OData
Microsoft.WindowsAzure.Storage
Newtonsoft.Json
System.Spatial
Wenn Diese spezifischen Plug-Ins ausgewählt sind, deaktivieren SieBeliebige Plattform , und deaktivieren SieWSAPlayer , und klicken Sie dann auf Übernehmen.
Hinweis
Wir markieren diese speziellen Plug-Ins, die nur im Unity-Editor verwendet werden sollen. Dies liegt daran, dass es verschiedene Versionen der gleichen Plug-Ins im WSA-Ordner gibt, die verwendet werden, nachdem das Projekt aus Unity exportiert wurde.
Wählen Sie im Ordner Storage-Plug-In nur Folgendes aus:
Microsoft.Data.Services.Client
Aktivieren Sie das Kontrollkästchen Nicht verarbeiten unter Plattformeinstellungen , und klicken Sie auf Übernehmen.
Hinweis
Wir markieren dieses Plug-In mit "Nicht verarbeiten", da der Unity-Assemblypatcher Probleme mit der Verarbeitung dieses Plug-Ins hat. Das Plug-In funktioniert weiterhin, obwohl es nicht verarbeitet wird.
Kapitel 7: Erstellen der AzureServices-Klasse
Die erste Klasse, die Sie erstellen, ist die AzureServices-Klasse .
Die AzureServices-Klasse ist für Folgendes verantwortlich:
Speichern von Azure-Kontoanmeldeinformationen.
Aufrufen der Azure-App-Funktion.
Der Upload und Download der Datendatei in Azure Cloud Storage.
So erstellen Sie diese Klasse:
Klicken Sie mit der rechten Maustaste auf den Ressourcenordner, der sich im Projektbereich ordner erstellen>befindet. Nennen Sie den Ordner Skripts.
Doppelklicken Sie auf den soeben erstellten Ordner, um ihn zu öffnen.
Klicken Sie mit der rechten Maustaste in den OrdnerC#-Skript erstellen>. Rufen Sie das Skript AzureServices auf.
Doppelklicken Sie auf die neue AzureServices-Klasse , um sie mit Visual Studio zu öffnen.
Fügen Sie die folgenden Namespaces am Anfang der AzureServices hinzu:
using System; using System.Threading.Tasks; using UnityEngine; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.File; using System.IO; using System.Net;
Fügen Sie die folgenden Inspektorfelder in der AzureServices-Klasse hinzu:
/// <summary> /// Provides Singleton-like behavior to this class. /// </summary> public static AzureServices instance; /// <summary> /// Reference Target for AzureStatusText Text Mesh object /// </summary> public TextMesh azureStatusText;
Fügen Sie dann die folgenden Membervariablen in der AzureServices-Klasse hinzu:
/// <summary> /// Holds the Azure Function endpoint - Insert your Azure Function /// Connection String here. /// </summary> private readonly string azureFunctionEndpoint = "--Insert here you AzureFunction Endpoint--"; /// <summary> /// Holds the Storage Connection String - Insert your Azure Storage /// Connection String here. /// </summary> private readonly string storageConnectionString = "--Insert here you AzureStorage Connection String--"; /// <summary> /// Name of the Cloud Share - Hosts directories. /// </summary> private const string fileShare = "fileshare"; /// <summary> /// Name of a Directory within the Share /// </summary> private const string storageDirectory = "storagedirectory"; /// <summary> /// The Cloud File /// </summary> private CloudFile shapeIndexCloudFile; /// <summary> /// The Linked Storage Account /// </summary> private CloudStorageAccount storageAccount; /// <summary> /// The Cloud Client /// </summary> private CloudFileClient fileClient; /// <summary> /// The Cloud Share - Hosts Directories /// </summary> private CloudFileShare share; /// <summary> /// The Directory in the share that will host the Cloud file /// </summary> private CloudFileDirectory dir;
Wichtig
Stellen Sie sicher, dass Sie den Endpunkt und Verbindungszeichenfolge Werte durch die Werte aus Ihrem Azure-Speicher ersetzen, die sie im Azure-Portal finden.
Code für die Methoden Awake() und Start() muss jetzt hinzugefügt werden. Diese Methoden werden aufgerufen, wenn die -Klasse initialisiert wird:
private void Awake() { instance = this; } // Use this for initialization private void Start() { // Set the Status text to loading, whilst attempting connection to Azure. azureStatusText.text = "Loading..."; } /// <summary> /// Call to the Azure Function App to request a Shape. /// </summary> public async void CallAzureFunctionForNextShape() { }
Wichtig
Wir werden den Code für CallAzureFunctionForNextShape() in einem zukünftigen Kapitel ausfüllen.
Löschen Sie die Update() -Methode, da diese Klasse sie nicht verwendet.
Speichern Sie Ihre Änderungen in Visual Studio, und kehren Sie dann zu Unity zurück.
Klicken Sie auf die AzureServices-Klasse , und ziehen Sie sie aus dem Ordner Skripts in das Objekt Hauptkamera im Hierarchiebereich.
Wählen Sie die Hauptkamera aus, greifen Sie dann unter dem GazeButton-Objekt nach dem untergeordneten AzureStatusText-Objekt, und platzieren Sie es im Verweiszielfeld AzureStatusText im Inspektor, um den Verweis auf das AzureServices-Skript bereitzustellen.
Kapitel 8: Erstellen der ShapeFactory-Klasse
Das nächste zu erstellende Skript ist die ShapeFactory-Klasse . Die Rolle dieser Klasse besteht darin, bei Bedarf ein neues Shape zu erstellen und einen Verlauf der in einer Shape-Verlaufsliste erstellten Shapes zu speichern. Jedes Mal, wenn ein Shape erstellt wird, wird die Liste Des Shape-Verlaufs in der AzureService-Klasse aktualisiert und dann in Ihrem Azure Storage gespeichert. Wenn die Anwendung gestartet wird und eine gespeicherte Datei in Azure Storage gefunden wird, wird die Liste Des Shape-Verlaufs abgerufen und wiedergegeben, wobei das 3D Text-Objekt angibt, ob die generierte Form aus dem Speicher stammt oder neu.
So erstellen Sie diese Klasse:
Wechseln Sie zum Ordner Skripts, den Sie zuvor erstellt haben.
Klicken Sie mit der rechten Maustaste in den OrdnerC#-Skript erstellen>. Rufen Sie das Skript ShapeFactory auf.
Doppelklicken Sie auf das neue ShapeFactory-Skript , um es mit Visual Studio zu öffnen.
Stellen Sie sicher, dass die ShapeFactory-Klasse die folgenden Namespaces enthält:
using System.Collections.Generic; using UnityEngine;
Fügen Sie der ShapeFactory-Klasse die unten gezeigten Variablen hinzu, und ersetzen Sie die Funktionen Start() und Awake() durch die folgenden:
/// <summary> /// Provide this class Singleton-like behaviour /// </summary> [HideInInspector] public static ShapeFactory instance; /// <summary> /// Provides an Inspector exposed reference to ShapeSpawnPoint /// </summary> [SerializeField] public Transform spawnPoint; /// <summary> /// Shape History Index /// </summary> [HideInInspector] public List<int> shapeHistoryList; /// <summary> /// Shapes Enum for selecting required shape /// </summary> private enum Shapes { Cube, Sphere, Cylinder } private void Awake() { instance = this; } private void Start() { shapeHistoryList = new List<int>(); }
Die CreateShape()- Methode generiert die primitiven Shapes basierend auf dem angegebenen ganzzahligen Parameter. Der boolesche Parameter wird verwendet, um anzugeben, ob die aktuell erstellte Form aus dem Speicher oder neu ist. Platzieren Sie den folgenden Code in Ihrer ShapeFactory-Klasse unter den vorherigen Methoden:
/// <summary> /// Use the Shape Enum to spawn a new Primitive object in the scene /// </summary> /// <param name="shape">Enumerator Number for Shape</param> /// <param name="storageShape">Provides whether this is new or old</param> internal void CreateShape(int shape, bool storageSpace) { Shapes primitive = (Shapes)shape; GameObject newObject = null; string shapeText = storageSpace == true ? "Storage: " : "New: "; AzureServices.instance.azureStatusText.text = string.Format("{0}{1}", shapeText, primitive.ToString()); switch (primitive) { case Shapes.Cube: newObject = GameObject.CreatePrimitive(PrimitiveType.Cube); break; case Shapes.Sphere: newObject = GameObject.CreatePrimitive(PrimitiveType.Sphere); break; case Shapes.Cylinder: newObject = GameObject.CreatePrimitive(PrimitiveType.Cylinder); break; } if (newObject != null) { newObject.transform.position = spawnPoint.position; newObject.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f); newObject.AddComponent<Rigidbody>().useGravity = true; newObject.GetComponent<Renderer>().material.color = UnityEngine.Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f); } }
Speichern Sie ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.
Klicken Sie zurück im Unity-Editor auf die ShapeFactory-Klasse , und ziehen Sie sie aus dem Ordner Skripts in das Hauptkameraobjekt im Hierarchiebereich.
Wenn die Hauptkamera ausgewählt ist, werden Sie feststellen, dass der ShapeFactory-Skriptkomponente der Verweis auf den Spawn-Punkt fehlt. Um dies zu beheben, ziehen Sie das ShapeSpawnPoint-Objekt aus dem Hierarchiebereich auf das Verweisziel Spawn-Punkt .
Kapitel 9: Erstellen der Gaze-Klasse
Das letzte Skript, das Sie erstellen müssen, ist die Gaze-Klasse .
Diese Klasse ist dafür verantwortlich, einen Raycast zu erstellen, der von der Hauptkamera nach vorne projiziert wird, um zu erkennen, welches Objekt der Benutzer betrachtet. In diesem Fall muss raycast erkennen, ob der Benutzer das GazeButton-Objekt in der Szene betrachtet, und ein Verhalten auslösen.
So erstellen Sie diese Klasse:
Wechseln Sie zum Ordner Skripts, den Sie zuvor erstellt haben.
Klicken Sie mit der rechten Maustaste im Projektbereich,C#-Skript erstellen>. Rufen Sie das Skript Gaze auf.
Doppelklicken Sie auf das neue Gaze-Skript, um es mit Visual Studio zu öffnen.
Stellen Sie sicher, dass der folgende Namespace oben im Skript enthalten ist:
using UnityEngine;
Fügen Sie dann die folgenden Variablen der Gaze-Klasse hinzu:
/// <summary> /// Provides Singleton-like behavior to this class. /// </summary> public static Gaze instance; /// <summary> /// The Tag which the Gaze will use to interact with objects. Can also be set in editor. /// </summary> public string InteractibleTag = "GazeButton"; /// <summary> /// The layer which will be detected by the Gaze ('~0' equals everything). /// </summary> public LayerMask LayerMask = ~0; /// <summary> /// The Max Distance the gaze should travel, if it has not hit anything. /// </summary> public float GazeMaxDistance = 300; /// <summary> /// The size of the cursor, which will be created. /// </summary> public Vector3 CursorSize = new Vector3(0.05f, 0.05f, 0.05f); /// <summary> /// The color of the cursor - can be set in editor. /// </summary> public Color CursorColour = Color.HSVToRGB(0.0223f, 0.7922f, 1.000f); /// <summary> /// Provides when the gaze is ready to start working (based upon whether /// Azure connects successfully). /// </summary> internal bool GazeEnabled = false; /// <summary> /// The currently focused object. /// </summary> internal GameObject FocusedObject { get; private set; } /// <summary> /// The object which was last focused on. /// </summary> internal GameObject _oldFocusedObject { get; private set; } /// <summary> /// The info taken from the last hit. /// </summary> internal RaycastHit HitInfo { get; private set; } /// <summary> /// The cursor object. /// </summary> internal GameObject Cursor { get; private set; } /// <summary> /// Provides whether the raycast has hit something. /// </summary> internal bool Hit { get; private set; } /// <summary> /// This will store the position which the ray last hit. /// </summary> internal Vector3 Position { get; private set; } /// <summary> /// This will store the normal, of the ray from its last hit. /// </summary> internal Vector3 Normal { get; private set; } /// <summary> /// The start point of the gaze ray cast. /// </summary> private Vector3 _gazeOrigin; /// <summary> /// The direction in which the gaze should be. /// </summary> private Vector3 _gazeDirection;
Wichtig
Einige dieser Variablen können im Editor bearbeitet werden.
Code für die Methoden Awake() und Start() muss jetzt hinzugefügt werden.
/// <summary> /// The method used after initialization of the scene, though before Start(). /// </summary> private void Awake() { // Set this class to behave similar to singleton instance = this; } /// <summary> /// Start method used upon initialization. /// </summary> private void Start() { FocusedObject = null; Cursor = CreateCursor(); }
Fügen Sie den folgenden Code hinzu, mit dem beim Start ein Cursorobjekt erstellt wird, zusammen mit der Update() -Methode, die die Raycast-Methode ausführen wird, und dort, wo gazeEnabled boolean umschaltbar ist:
/// <summary> /// Method to create a cursor object. /// </summary> /// <returns></returns> private GameObject CreateCursor() { GameObject newCursor = GameObject.CreatePrimitive(PrimitiveType.Sphere); newCursor.SetActive(false); // Remove the collider, so it doesn't block raycast. Destroy(newCursor.GetComponent<SphereCollider>()); newCursor.transform.localScale = CursorSize; newCursor.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Diffuse")) { color = CursorColour }; newCursor.name = "Cursor"; newCursor.SetActive(true); return newCursor; } /// <summary> /// Called every frame /// </summary> private void Update() { if(GazeEnabled == true) { _gazeOrigin = Camera.main.transform.position; _gazeDirection = Camera.main.transform.forward; UpdateRaycast(); } }
Fügen Sie als Nächstes die UpdateRaycast() -Methode hinzu, die einen Raycast projiziert und das Trefferziel erkennt.
private void UpdateRaycast() { // Set the old focused gameobject. _oldFocusedObject = FocusedObject; RaycastHit hitInfo; // Initialise Raycasting. Hit = Physics.Raycast(_gazeOrigin, _gazeDirection, out hitInfo, GazeMaxDistance, LayerMask); HitInfo = hitInfo; // Check whether raycast has hit. if (Hit == true) { Position = hitInfo.point; Normal = hitInfo.normal; // Check whether the hit has a collider. if (hitInfo.collider != null) { // Set the focused object with what the user just looked at. FocusedObject = hitInfo.collider.gameObject; } else { // Object looked on is not valid, set focused gameobject to null. FocusedObject = null; } } else { // No object looked upon, set focused gameobject to null. FocusedObject = null; // Provide default position for cursor. Position = _gazeOrigin + (_gazeDirection * GazeMaxDistance); // Provide a default normal. Normal = _gazeDirection; } // Lerp the cursor to the given position, which helps to stabilize the gaze. Cursor.transform.position = Vector3.Lerp(Cursor.transform.position, Position, 0.6f); // Check whether the previous focused object is this same // object. If so, reset the focused object. if (FocusedObject != _oldFocusedObject) { ResetFocusedObject(); if (FocusedObject != null) { if (FocusedObject.CompareTag(InteractibleTag.ToString())) { // Set the Focused object to green - success! FocusedObject.GetComponent<Renderer>().material.color = Color.green; // Start the Azure Function, to provide the next shape! AzureServices.instance.CallAzureFunctionForNextShape(); } } } }
Fügen Sie abschließend die ResetFocusedObject() -Methode hinzu, die die aktuelle Farbe der GazeButton-Objekte umschaltet und angibt, ob eine neue Form erstellt wird oder nicht.
/// <summary> /// Reset the old focused object, stop the gaze timer, and send data if it /// is greater than one. /// </summary> private void ResetFocusedObject() { // Ensure the old focused object is not null. if (_oldFocusedObject != null) { if (_oldFocusedObject.CompareTag(InteractibleTag.ToString())) { // Set the old focused object to red - its original state. _oldFocusedObject.GetComponent<Renderer>().material.color = Color.red; } } }
Speichern Sie Ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.
Klicken Sie auf die Gaze-Klasse , und ziehen Sie sie aus dem Ordner Skripts in das Objekt Hauptkamera im Hierarchiebereich.
Kapitel 10: Abschließen der AzureServices-Klasse
Nachdem die anderen Skripts vorhanden sind, ist es jetzt möglich, die AzureServices-Klasseabzuschließen. Dies wird erreicht durch:
Hinzufügen einer neuen Methode mit dem Namen CreateCloudIdentityAsync() zum Einrichten der Authentifizierungsvariablen, die für die Kommunikation mit Azure erforderlich sind.
Diese Methode prüft auch, ob eine zuvor gespeicherte Datei vorhanden ist, die die Shape-Liste enthält.
Wenn die Datei gefunden wird, wird die Benutzeranzeige deaktiviert und die Erstellung von Formen entsprechend dem Muster der Formen ausgelöst, wie sie in der Azure Storage-Datei gespeichert sind. Der Benutzer kann dies sehen, da das Textgitter abhängig vom Ursprung der Formen die Anzeige "Speicher" oder "Neu" bereitstellt.
Wenn keine Datei gefunden wird, wird das Gaze aktiviert, sodass der Benutzer Formen erstellen kann, wenn er das GazeButton-Objekt in der Szene betrachtet.
/// <summary> /// Create the references necessary to log into Azure /// </summary> private async void CreateCloudIdentityAsync() { // Retrieve storage account information from connection string storageAccount = CloudStorageAccount.Parse(storageConnectionString); // Create a file client for interacting with the file service. fileClient = storageAccount.CreateCloudFileClient(); // Create a share for organizing files and directories within the storage account. share = fileClient.GetShareReference(fileShare); await share.CreateIfNotExistsAsync(); // Get a reference to the root directory of the share. CloudFileDirectory root = share.GetRootDirectoryReference(); // Create a directory under the root directory dir = root.GetDirectoryReference(storageDirectory); await dir.CreateIfNotExistsAsync(); //Check if the there is a stored text file containing the list shapeIndexCloudFile = dir.GetFileReference("TextShapeFile"); if (!await shapeIndexCloudFile.ExistsAsync()) { // File not found, enable gaze for shapes creation Gaze.instance.GazeEnabled = true; azureStatusText.text = "No Shape\nFile!"; } else { // The file has been found, disable gaze and get the list from the file Gaze.instance.GazeEnabled = false; azureStatusText.text = "Shape File\nFound!"; await ReplicateListFromAzureAsync(); } }
Der nächste Codeausschnitt stammt aus der Start() -Methode. wobei ein Aufruf der CreateCloudIdentityAsync() -Methode erfolgt. Kopieren Sie ihre aktuelle Start()- Methode mit folgendem Text:
private void Start() { // Disable TLS cert checks only while in Unity Editor (until Unity adds support for TLS) #if UNITY_EDITOR ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; #endif // Set the Status text to loading, whilst attempting connection to Azure. azureStatusText.text = "Loading..."; //Creating the references necessary to log into Azure and check if the Storage Directory is empty CreateCloudIdentityAsync(); }
Geben Sie den Code für die Methode CallAzureFunctionForNextShape() ein. Sie verwenden die zuvor erstellte Azure-Funktions-App , um einen Shape-Index anzufordern. Sobald die neue Form empfangen wurde, sendet diese Methode das Shape an die ShapeFactory-Klasse , um das neue Shape in der Szene zu erstellen. Verwenden Sie den folgenden Code, um den Text von CallAzureFunctionForNextShape() zu vervollständigen.
/// <summary> /// Call to the Azure Function App to request a Shape. /// </summary> public async void CallAzureFunctionForNextShape() { int azureRandomInt = 0; // Call Azure function HttpWebRequest webRequest = WebRequest.CreateHttp(azureFunctionEndpoint); WebResponse response = await webRequest.GetResponseAsync(); // Read response as string using (Stream stream = response.GetResponseStream()) { StreamReader reader = new StreamReader(stream); String responseString = reader.ReadToEnd(); //parse result as integer Int32.TryParse(responseString, out azureRandomInt); } //add random int from Azure to the ShapeIndexList ShapeFactory.instance.shapeHistoryList.Add(azureRandomInt); ShapeFactory.instance.CreateShape(azureRandomInt, false); //Save to Azure storage await UploadListToAzureAsync(); }
Fügen Sie eine Methode hinzu, um eine Zeichenfolge zu erstellen, indem Sie die ganzen Zahlen verketten, die in der Shape-Verlaufsliste gespeichert sind, und speichern Sie sie in Ihrer Azure Storage-Datei.
/// <summary> /// Upload the locally stored List to Azure /// </summary> private async Task UploadListToAzureAsync() { // Uploading a local file to the directory created above string listToString = string.Join(",", ShapeFactory.instance.shapeHistoryList.ToArray()); await shapeIndexCloudFile.UploadTextAsync(listToString); }
Fügen Sie eine Methode hinzu, um den text abzurufen, der sich in der Datei in Ihrer Azure Storage-Datei befindet, und deserialisieren Sie ihn in einer Liste.
Sobald dieser Prozess abgeschlossen ist, aktiviert die Methode den Blick erneut, sodass der Benutzer der Szene weitere Shapes hinzufügen kann.
///<summary> /// Get the List stored in Azure and use the data retrieved to replicate /// a Shape creation pattern ///</summary> private async Task ReplicateListFromAzureAsync() { string azureTextFileContent = await shapeIndexCloudFile.DownloadTextAsync(); string[] shapes = azureTextFileContent.Split(new char[] { ',' }); foreach (string shape in shapes) { int i; Int32.TryParse(shape.ToString(), out i); ShapeFactory.instance.shapeHistoryList.Add(i); ShapeFactory.instance.CreateShape(i, true); await Task.Delay(500); } Gaze.instance.GazeEnabled = true; azureStatusText.text = "Load Complete!"; }
Speichern Sie Ihre Änderungen in Visual Studio, bevor Sie zu Unity zurückkehren.
Kapitel 11: Erstellen der UWP-Lösung
So starten Sie den Buildvorgang:
Wechseln Sie zuDateibuildeinstellungen>.
Klicken Sie auf Erstellen. Unity startet ein Explorer Fenster, in dem Sie einen Ordner erstellen und dann auswählen müssen, in dem die App erstellt werden soll. Erstellen Sie diesen Ordner jetzt, und nennen Sie ihn App. Drücken Sie dann, wenn der Ordner App ausgewählt ist, Ordner auswählen.
Unity beginnt mit dem Erstellen Ihres Projekts im Ordner App .
Sobald Unity das Erstellen abgeschlossen hat (es kann einige Zeit dauern), öffnet es ein Explorer Fenster am Speicherort Ihres Builds (überprüfen Sie Ihre Taskleiste, da es möglicherweise nicht immer über Ihren Fenstern angezeigt wird, Sie aber über das Hinzufügen eines neuen Fensters benachrichtigt).
Kapitel 12: Bereitstellen Ihrer Anwendung
So stellen Sie Ihre Anwendung bereit:
Navigieren Sie zum Ordner App , der im letzten Kapitel erstellt wurde. Es wird eine Datei mit dem Namen Ihrer Apps mit der Erweiterung ".sln" angezeigt, auf die Sie doppelklicken sollten, um sie in Visual Studio zu öffnen.
Wählen Sie auf der Lösungsplattformdie Option x86, Lokaler Computer aus.
Wählen Sie in der Projektmappenkonfigurationdebuggen aus.
Für die Microsoft HoloLens ist es möglicherweise einfacher, dies auf Remotecomputer festzulegen, sodass Sie nicht an Ihren Computer angebunden werden. Sie müssen jedoch auch die folgenden Schritte ausführen:
- Kennen Sie die IP-Adresse Ihrer HoloLens, die Sie im Einstellungsnetzwerk>& Internet>Wi-Fi>Advanced Options finden können. IPv4 ist die Adresse, die Sie verwenden sollten.
- Stellen Sie sicher , dass der Entwicklermodusaktiviert ist. finden Sie unter Einstellungen>Update & Sicherheit>für Entwickler.
Wechseln Sie zum Menü Erstellen , und klicken Sie auf Lösung bereitstellen , um die Anwendung auf Ihren Computer querzuladen.
Ihre App sollte jetzt in der Liste der installierten Apps angezeigt werden, die zum Starten und Testen bereit sind!
Ihre fertige Azure Functions- und Speicheranwendung
Herzlichen Glückwunsch! Sie haben eine Mixed Reality-App erstellt, die sowohl die Azure Functions- als auch azure Storage-Dienste nutzt. Ihre App kann auf gespeicherten Daten zeichnen und basierend auf diesen Daten eine Aktion bereitstellen.
Zusatzübungen
Übung 1
Erstellen Sie einen zweiten Spawnpunkt und einen Datensatz, aus dem ein Spawnpunkt ein Objekt erstellt wurde. Wenn Sie die Datendatei laden, geben Sie die Shapes wieder, die von dem ursprünglich erstellten Speicherort abgerufen werden.
Übung 2
Erstellen Sie eine Möglichkeit, die App neu zu starten, anstatt sie jedes Mal erneut öffnen zu müssen. Das Laden von Szenen ist ein guter Ausgangspunkt. Erstellen Sie anschließend eine Möglichkeit, die gespeicherte Liste in Azure Storage zu löschen, damit sie problemlos von Ihrer App zurückgesetzt werden kann.