Semester in Alternate Realities

Semester in Alternate Realities (SIAR) ist ein Programm, in dem interdisziplinäre Teams reale Probleme mit xR-Technologien angehen. Mit der Design-Herausforderung „VR4good“ entwickelten wir transformative Erlebnisse mit positivem gesellschaftlichem Impact.


Semester in Alternate Realities

Typ

VR-Spiel

Jahr

2019

Angewandte Fähigkeiten

  • VR
  • Spielentwicklung
  • Projektmanagement
  • UX / UI

Team

  • Eigenes Projekt
<h2>Über das Programm</h2>
<p>Semester in Alternate Realities (SIAR) unterschied sich von allen anderen Kursen, die ich während meiner Zeit an der SFU belegt habe. Ein Team aus zwei Dozenten startete mit einer kleinen Klasse, um Extended-Reality-Projekte (xR) mit positivem gesellschaftlichem Impact zu entwickeln. Der Kurs bot nicht nur eine großartige Gelegenheit, VR-Design und -Entwicklung zu erlernen, sondern zeichnete sich vor allem durch seinen interdisziplinären und kollaborativen Charakter aus. Zu den wichtigsten Erkenntnissen gehörten Soft Skills wie agiles Projektmanagement, Teambuilding, Pitching und Präsentation sowie Hard Skills wie Unity-VR-Entwicklung. Mehr Informationen finden sich auf dieser <a href="http://ispace.iat.sfu.ca/riecke/teaching/siar" target="_blank" title="iSpace Lab: Semester in Alternate Realities">Infoseite</a>. Abschließend möchte ich unseren Professoren Bernhard Riecke und Patrick Pennefather für diese großartige Erfahrung danken.</p>
Marshmallow-Challenge als Teambuilding-Übung
Interdisziplinäre Zusammenarbeit mit konzeptionellen Diskussionen
(L) Die Marshmallow-Challenge war eine unserer Teambuilding-Übungen. (R) Die interdisziplinäre Zusammenarbeit beinhaltete viele konzeptionelle Diskussionen.

Rising Waters

Ein VR-Projekt für die Oculus Go, das sich dem Thema Klimawandel widmet. In einer futuristischen Setting erkunden Nutzer die Stadt Richmond, B.C., nach einer Überschwemmung infolge steigender Meeresspiegel.

Rising Waters ist ein überwiegend passives Erlebnis für das Oculus-Go-Headset. Aufgrund des eingeschränkten 3DOF-Trackings der Oculus Go und der spärlichen Eingabemöglichkeiten entwickelten wir ein einfaches Walkthrough-Erlebnis entlang einer vordefinierten Strecke. Nutzer können den Walkthrough pausieren und fortsetzen und sich umsehen, um die Umgebung zu erkunden. Zunächst werden sie von Dutzenden Bildschirmen umgeben, die Clips zum Thema globale Erwärmung und deren Auswirkungen zeigen. Anschließend werden sie in ein hypothetisches zukünftiges Richmond versetzt, das durch steigende Meeresspiegel überflutet wurde. Die Welt wird aus der Perspektive einer Aufklärungsdrohne erlebt, die Proben sammelt und nach menschlichen Signalen sucht. Diese Datensammelmission führt durch Vororte von Richmond, unter die SkyTrain-Strecke und schließlich auf ein Dach, wo noch ein altes Radio Signale sendet. Unser Ziel ist, das Bewusstsein für den Klimawandel zu schärfen und das Verständnis für mögliche lokale Auswirkungen zu vertiefen.

Meine Rollen bei der Entwicklung von Rising Waters umfassten Concept Design, C#- und Unity-Entwicklung sowie das 3D-Modelling des Großteils der 3D-Assets. In Blender modellierte ich Gebäude sowie lokale Merkmale wie die SkyTrain und Richmond-Banner und versah sie mit online bezogenen Texturen.

Einführung in Rising Waters, Walkthrough und technischer Überblick.

FIT4U

Ein VR-Projekt für die Oculus Rift, das als meditatives Fitness-Erlebnis getarnt ist. Nach der Erfassung einiger persönlicher Informationen konfrontieren wir Nutzer mit einem dramatisierten Hacking-Vorfall, der auf deren persönliche Daten zugeschnitten ist.

Die Projektidee wurde zunächst in einem kleineren Team entwickelt, später aber von der gesamten Klasse in einer Hivemind-Kollaboration fertiggestellt und verfeinert. Obwohl eine eigenständige Version auf Itch.io veröffentlicht wurde, erfordert unser finaler Prototyp manuelle Bedienung. Der Ablauf beginnt mit der Anmeldung von Showcase-Besuchern zu festen Zeitfenstern. Ein Teamkollege und ich durchsuchten anschließend das Web nach öffentlich verfügbaren Daten der Nutzer, typischerweise über deren Social-Media-Profile. Sobald der Nutzer zu seinem Termin erscheint, führt ein Teamkollege in der Rolle eines Yoga-Lehrers sie in einen abgeschirmten Raum und geleitet sie auf sehr entspannte Weise ins Erlebnis. In VR füllen Nutzer ein kurzes Formular aus und beginnen mit Atem- und Dehnübungen. Schon bald wird dies durch das Hacking-Ereignis unterbrochen. Der Raum um den Nutzer beginnt zu flackern und bricht zusammen, um zu enthüllen, dass sie sich in einem virtuellen Raum befinden, der ihre persönlichen Daten allseitig anzeigt. Dieser Datensatz besteht aus den eingegebenen Daten sowie allen öffentlich verfügbaren Informationen, die wir finden konnten. Inspiriert von Matrix bieten wir zwei Optionen: alle Daten löschen oder behalten. Je nach Wahl endet das Erlebnis sofort, oder die Nutzer werden zurück in den Fitnessraum transportiert, der dann von personalisierten Werbeanzeigen überflutet wird. Ziel ist, dass Nutzer besser verstehen, welche Daten sie öffentlich gemacht haben, und reflektieren, ob sie mit ihrem Umgang mit Privatsphäre einverstanden sind.

Bei der Entwicklung von FIT4U spielte ich eine untergeordnete Rolle im Concept Design und eine zentrale Rolle in der C#- und Unity-Entwicklung. Außerdem war ich für den Betrieb des Backends der Live-Performance-Version verantwortlich, bei der ich im Web nach öffentlich verfügbaren Informationen über Teilnehmer suchte und diese in eine separate UI einspeiste, die für den Nutzer nicht sichtbar war. Die korrekte Ausgabe auf den richtigen Bildschirm stellte dabei eine erhebliche Herausforderung dar.

FIT4U-Projektüberblick und Showcase-Aufnahmen.

Der Code

Rising Waters

Die C#- und Unity-Entwicklung von Rising Waters teilten sich Vlad Ryzhov und ich. Meine Verantwortlichkeiten umfassten die Walkthrough-Bewegung und -Steuerung. Da dies eines meiner ersten Unity-Projekte war, bot es eine gründliche Einführung in Vektoren und Quaternionen in der Unity-API. Ein sehr hilfreiches Paket war der Hermite Spline Controller, mit dem sich Position und Orientierung eines GameObjects entlang eines einfach definierten Pfads sanft interpolieren ließen. Im Beispiel unten nutze ich die Methoden GetHermiteInternal() und GetSquad() des Pakets, um den Spieler entlang des vordefinierten Walkthrough-Pfads zu positionieren und auszurichten sowie an Waypoint-Knoten des Pfads anzuhalten.

SplineInterpolator.cs
// CODE SNIPPET, NOT THE FULL FILE //void Update(){  //Don't move  if (mState == "Reset" || mState == "Stopped" || mNodes.Count < 4) return;  /////////////////////// Control movement along path ///////////////////////////  MonitorInput();  if (!paused) mCurrentTime += Time.deltaTime;  // We advance to next point in the path  if (mCurrentTime >= mNodes[mCurrentIdx + 1].Time)  {    if (mCurrentIdx < mNodes.Count - 3)    {      mCurrentIdx++;    }    else    {      if (mState != "Loop")      {        mState = "Stopped";        // We stop right in the end point        transform.position = mNodes[mNodes.Count - 2].Point;        if (mRotations)          transform.rotation = mNodes[mNodes.Count - 2].Rot;        // We call back to inform that we are ended        if (mOnEndCallback != null)          mOnEndCallback();      }      else      {        mCurrentIdx = 1;        mCurrentTime = 0;      }    }  }  // We are in motion -> Interpolate position and orientation along path  if (mState != "Stopped")  {    // Calculates the t param between 0 and 1    float param = (mCurrentTime - mNodes[mCurrentIdx].Time) / (mNodes[mCurrentIdx + 1].Time - mNodes[mCurrentIdx].Time);    // Smooth the param    param = MathUtils.Ease(param, mNodes[mCurrentIdx].EaseIO.x, mNodes[mCurrentIdx].EaseIO.y);    transform.position = GetHermiteInternal(mCurrentIdx, param);    if (mRotations)    {      transform.rotation = GetSquad(mCurrentIdx, param);    }  }}

View gist on GitHub

FIT4U

Als einziger C#- und Unity-Entwickler bei FIT4U hatte ich viele Verantwortlichkeiten. Eines meiner ersten abgeschlossenen Features war ein Proof of Concept mit mehreren Kameras: Spielerkameras rendern auf das HMD, eine separate UI-Kamera auf einen für den Spieler unsichtbaren Computerbildschirm. So konnten wir Nutzerdaten eingeben, während das Erlebnis lief. Anschließend implementierte ich die (temporäre) Nutzerdatenbank und mehrere UI-Systeme, die Nutzerdaten basierend auf Eingaben speicherten. Der letzte Schritt war Code, der die einzelnen Bildschirme verknüpfte und den Datenleck-Ablauf sequenzierte – implementiert als Coroutine. Das Beispiel unten ist ein Auszug aus Ads.cs, einem Skript, das die Wände um den Nutzer mit generischen und zielgerichteten Werbeanzeigen füllt, wenn sie am Ende ihre Daten nicht löschen. Der Auszug prüft, welche Daten zu verfügbaren Anzeigen passen, definiert entsprechend ein Array von Anzeigenbildern, mischt es und startet eine Coroutine, die den gesamten Raum schrittweise mit Anzeigen füllt.

Ads.cs
// CODE SNIPPET, NOT THE FULL FILE //void Start(){    origTimeLeft = timeLeft;    timeLeft = timeLeft * 3;    imageObjectGOs = new GameObject[allImageObjects.Length];    ui_handler.dataTable = ui_handler.gimmeDatabase();    //check what data is available for targeted ads    ArrayList availableData = new ArrayList();    if (((string)ui_handler.dataTable["Drugs_consumed"]).Contains("Caffeine")) availableData.Add((Texture)coffeeAd);    if (((string)ui_handler.dataTable["Drugs_consumed"]).Contains("Cannabis")) availableData.Add((Texture)cannabisAd);    if (((string)ui_handler.dataTable["Drugs_consumed"]).Contains("Tobacco")) availableData.Add((Texture)tobaccoAd);    if (((string)ui_handler.dataTable["Favorite_Sportsbrands"]).Contains("Northface")) availableData.Add((Texture)northFaceAd);    if (((string)ui_handler.dataTable["Favorite_Sportsbrands"]).Contains("Puma")) availableData.Add((Texture)pumaAd);    //creat boolean array to see which ads should be targeted    bool[] replaceWithTargetedAd = new bool[allImageObjects.Length];    if (availableData.Count > 0)    {        //calculate how many targeted ads there should be in total        int numberOfAdsToTarget = mapNumber(availableData.Count, 1, 5, (int)allImageObjects.Length/6, (int)allImageObjects.Length / 2);        int amountOfIndexesSet = 0;        //randomly assign indexes of the random ads (so they are spread equally throughout the panels)        while (amountOfIndexesSet < numberOfAdsToTarget)        {            int randomVal = Random.Range((int)0, (int)replaceWithTargetedAd.Length);            if (!replaceWithTargetedAd[randomVal])            {                replaceWithTargetedAd[randomVal] = true;                amountOfIndexesSet++;            }        }    }    for (int i = 0; i < allImageObjects.Length; i++)    {        //load random image from the generic Ads pool, assign to current RawImage        if (replaceWithTargetedAd[i])        {            // -> targeted            allImageObjects[i].texture = (Texture)availableData[Random.Range(0, availableData.Count - 1)];        }        else {            // -> generic            allImageObjects[i].texture = genericAds[(int)(Random.Range(0, genericAds.Length - 1))];        }        //correct scale, so it has correct aspect ratio and it's scaled down        allImageObjects[i].SetNativeSize();        int w = (int)(allImageObjects[i].rectTransform.rect.width / 5);        int h = (int)(allImageObjects[i].rectTransform.rect.height / 5);        allImageObjects[i].rectTransform.sizeDelta = new Vector2(w, h);        //asign GameObjects for each component        imageObjectGOs[i] = allImageObjects[i].gameObject;    }    //shuffle Array, so images are loaded in random order    shuffleArray(imageObjectGOs);    for (int i = 0; i < imageObjectGOs.Length; i++)    {        //deactivate all ads        imageObjectGOs[i].SetActive(false);    }    //Start coroutine to begin showing ads    startTimer();}

View gist on GitHub

Diese Portfolio-Website wurde entwickelt mit Astro. Urheberrecht © 2026 Robert Michels. Alle Rechte vorbehalten.
Lebenslauf Datenschutz