# Milestone 7: Game Integration **Status**: Not Started **Goal**: Production-ready Unity and Unreal plugins for seamless VR phone integration. --- ## Overview Game engine plugins enable: - Rendering the phone in VR scenes - Touch/raycast interaction - Virtual hardware provision (camera, mic, speaker) - Event callbacks to game code --- ## Current State | Platform | Location | Status | |----------|----------|--------| | Unity | `D:\Dev\Mosis\Mosis Unity` | Basic Binder client | | Unreal | `D:\Dev\Mosis\Mosis Unreal` | WIP | --- ## Unity Plugin ### Package Structure ``` com.mosis.phone/ ├── package.json ├── Runtime/ │ ├── MosisPhone.cs # Main component │ ├── MosisService.cs # Binder client │ ├── MosisInputHandler.cs # Touch/raycast │ ├── MosisHardwareProvider.cs # Virtual hardware │ └── Native/ │ ├── MosisNative.cs # P/Invoke declarations │ └── Plugins/ │ └── Android/ │ └── libmosis-client.so ├── Prefabs/ │ ├── MosisPhone.prefab # Ready-to-use phone │ └── MosisPhoneVR.prefab # VR-optimized version ├── Samples~/ │ └── BasicIntegration/ │ └── PhoneDemo.unity └── Documentation~/ └── integration-guide.md ``` ### Main Component **File**: `MosisPhone.cs` ```csharp using UnityEngine; using UnityEngine.Events; namespace Mosis { public class MosisPhone : MonoBehaviour { [Header("Display")] [SerializeField] private MeshRenderer phoneScreen; [SerializeField] private Vector2Int resolution = new Vector2Int(540, 960); [Header("Input")] [SerializeField] private bool enableRaycast = true; [SerializeField] private LayerMask raycastLayers; [Header("Virtual Hardware")] [SerializeField] private Camera virtualCamera; [SerializeField] private AudioSource virtualMicrophone; [SerializeField] private AudioSource virtualSpeaker; [Header("Events")] public UnityEvent onPhoneReady; public UnityEvent onNavigate; public UnityEvent onMessageReceived; public UnityEvent onCallStarted; private MosisService service; private RenderTexture screenTexture; private Material screenMaterial; void Awake() { // Create render texture for phone screen screenTexture = new RenderTexture(resolution.x, resolution.y, 0); screenMaterial = new Material(Shader.Find("Unlit/Texture")); screenMaterial.mainTexture = screenTexture; phoneScreen.material = screenMaterial; } void Start() { service = new MosisService(); service.OnFrameAvailable += OnFrameAvailable; service.OnServiceInitialized += () => onPhoneReady?.Invoke(); service.Connect(); } void Update() { if (enableRaycast) { HandleRaycastInput(); } } private void HandleRaycastInput() { // VR controller raycast if (OVRInput.GetDown(OVRInput.Button.PrimaryIndexTrigger)) { var ray = new Ray( OVRInput.GetLocalControllerPosition(OVRInput.Controller.RTouch), OVRInput.GetLocalControllerRotation(OVRInput.Controller.RTouch) * Vector3.forward ); if (Physics.Raycast(ray, out var hit, 10f, raycastLayers)) { if (hit.collider.gameObject == phoneScreen.gameObject) { var uv = hit.textureCoord; var x = uv.x * resolution.x; var y = (1 - uv.y) * resolution.y; service.SendTouchDown(x, y); } } } } private void OnFrameAvailable(byte[] pixels) { // Update screen texture screenTexture.LoadRawTextureData(pixels); screenTexture.Apply(); } // Public API public void NavigateTo(string screen) { service.Navigate(screen); } public void SendMessage(string to, string text) { service.SendMessage(to, text); } public void MakeCall(string number) { service.MakeCall(number); } void OnDestroy() { service?.Disconnect(); Destroy(screenTexture); Destroy(screenMaterial); } } } ``` ### Hardware Provider **File**: `MosisHardwareProvider.cs` ```csharp namespace Mosis { public class MosisHardwareProvider : MonoBehaviour { [SerializeField] private MosisPhone phone; [SerializeField] private Camera gameCamera; [SerializeField] private AudioListener audioListener; private RenderTexture cameraTexture; void Start() { // Setup virtual camera cameraTexture = new RenderTexture(640, 480, 0); gameCamera.targetTexture = cameraTexture; // Register with phone service phone.Service.SetCameraProvider(() => { var pixels = new byte[cameraTexture.width * cameraTexture.height * 4]; // Read pixels from RenderTexture return pixels; }); phone.Service.SetMicrophoneProvider(() => { // Get audio from AudioListener return audioSamples; }); } public void PlayAudio(float[] samples) { // Play on virtual speaker var audioSource = GetComponent(); audioSource.clip = AudioClip.Create("phone", samples.Length, 1, 44100, false); audioSource.clip.SetData(samples, 0); audioSource.Play(); } } } ``` ### VR Interaction **File**: `MosisInputHandler.cs` ```csharp namespace Mosis { public class MosisInputHandler : MonoBehaviour { [SerializeField] private MosisPhone phone; [SerializeField] private Transform pointerOrigin; [SerializeField] private LineRenderer laserPointer; private bool isTouching; private Vector2 lastTouchPos; void Update() { // Update laser pointer var ray = new Ray(pointerOrigin.position, pointerOrigin.forward); if (Physics.Raycast(ray, out var hit, 10f)) { laserPointer.SetPosition(1, hit.point); if (hit.collider.GetComponent() != null) { var uv = hit.textureCoord; var touchPos = new Vector2( uv.x * phone.Resolution.x, (1 - uv.y) * phone.Resolution.y ); HandleTouch(touchPos); } } } private void HandleTouch(Vector2 pos) { bool triggerDown = OVRInput.Get(OVRInput.Button.PrimaryIndexTrigger); if (triggerDown && !isTouching) { phone.Service.SendTouchDown(pos.x, pos.y); isTouching = true; } else if (triggerDown && isTouching) { if (Vector2.Distance(pos, lastTouchPos) > 2f) { phone.Service.SendTouchMove(pos.x, pos.y); } } else if (!triggerDown && isTouching) { phone.Service.SendTouchUp(pos.x, pos.y); isTouching = false; } lastTouchPos = pos; } } } ``` --- ## Unreal Plugin ### Module Structure ``` MosisPhone/ ├── MosisPhone.uplugin ├── Source/ │ ├── MosisPhone/ │ │ ├── MosisPhone.Build.cs │ │ ├── Public/ │ │ │ ├── MosisPhoneActor.h │ │ │ ├── MosisService.h │ │ │ └── MosisHardwareProvider.h │ │ └── Private/ │ │ ├── MosisPhoneActor.cpp │ │ ├── MosisService.cpp │ │ └── MosisHardwareProvider.cpp │ └── ThirdParty/ │ └── MosisClient/ │ └── libmosis-client.so ├── Content/ │ ├── BP_MosisPhone.uasset │ └── M_PhoneScreen.uasset └── Documentation/ └── integration-guide.md ``` ### Main Actor **File**: `MosisPhoneActor.h` ```cpp #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "MosisPhoneActor.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnPhoneReady); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNavigate, FString, Screen); UCLASS() class MOSISPHONE_API AMosisPhoneActor : public AActor { GENERATED_BODY() public: AMosisPhoneActor(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Display") FIntPoint Resolution = FIntPoint(540, 960); UPROPERTY(BlueprintAssignable, Category = "Events") FOnPhoneReady OnPhoneReady; UPROPERTY(BlueprintAssignable, Category = "Events") FOnNavigate OnNavigate; UFUNCTION(BlueprintCallable, Category = "Mosis") void NavigateTo(const FString& Screen); UFUNCTION(BlueprintCallable, Category = "Mosis") void SendMessage(const FString& To, const FString& Text); UFUNCTION(BlueprintCallable, Category = "Mosis") void MakeCall(const FString& Number); protected: virtual void BeginPlay() override; virtual void Tick(float DeltaTime) override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; private: UPROPERTY() UTextureRenderTarget2D* ScreenTexture; UPROPERTY() UMaterialInstanceDynamic* ScreenMaterial; TSharedPtr Service; void HandleRaycastInput(); void OnFrameAvailable(const TArray& Pixels); }; ``` ### Blueprint Integration ```cpp // Blueprint callable functions for easy integration UFUNCTION(BlueprintCallable, Category = "Mosis", meta = (WorldContext = "WorldContextObject")) static AMosisPhoneActor* SpawnMosisPhone( UObject* WorldContextObject, FTransform SpawnTransform, FIntPoint Resolution = FIntPoint(540, 960) ); ``` --- ## Hardware Button Simulation ### Physical Buttons | Button | Function | |--------|----------| | Power | Lock/wake screen | | Volume Up | Increase volume | | Volume Down | Decrease volume | | Home (soft) | Go to home screen | | Back (soft) | Navigation back | ### Unity Implementation ```csharp public class MosisPhoneButtons : MonoBehaviour { [SerializeField] private MosisPhone phone; [SerializeField] private Collider powerButton; [SerializeField] private Collider volumeUp; [SerializeField] private Collider volumeDown; void Update() { // Check for button presses via physics overlap if (IsButtonPressed(powerButton)) { phone.Service.SendButton("power"); } // ... } } ``` ### Unreal Implementation ```cpp // In Blueprint or C++ UFUNCTION(BlueprintCallable) void PressButton(EMosisButton Button) { switch (Button) { case EMosisButton::Power: Service->SendButton("power"); break; case EMosisButton::VolumeUp: Service->SendButton("volume_up"); break; case EMosisButton::VolumeDown: Service->SendButton("volume_down"); break; } } ``` --- ## Implementation Plan ### Phase 1: Unity Core - [ ] Package structure - [ ] MosisPhone component - [ ] Basic touch input - [ ] Frame rendering ### Phase 2: Unity VR - [ ] VR raycast interaction - [ ] Laser pointer visualization - [ ] Controller haptics - [ ] Two-handed support ### Phase 3: Unity Hardware - [ ] Camera provider (RenderTexture) - [ ] Microphone provider - [ ] Speaker output - [ ] Vibration ### Phase 4: Unreal Core - [ ] Plugin structure - [ ] MosisPhoneActor - [ ] Blueprint integration - [ ] Touch input ### Phase 5: Unreal VR - [ ] Motion controller input - [ ] Widget interaction - [ ] Hardware providers ### Phase 6: Documentation - [ ] Quick start guide - [ ] API reference - [ ] Sample scenes - [ ] Troubleshooting --- ## Testing ### Unity Test Scene 1. Phone mounted in VR scene 2. Touch interaction works 3. Virtual camera shows game view 4. Audio plays through virtual speaker ### Unreal Test Level 1. Blueprint phone actor 2. Motion controller interaction 3. Event callbacks work 4. Performance acceptable --- ## Performance Targets | Metric | Target | |--------|--------| | Frame latency | < 16ms | | Touch latency | < 50ms | | Memory usage | < 100MB | | Draw calls | < 5 per phone | --- ## Acceptance Criteria ### Unity - [ ] One-click prefab placement - [ ] VR raycast touch works - [ ] Events fire correctly - [ ] Camera feed from game - [ ] Audio bidirectional ### Unreal - [ ] Blueprint spawnable - [ ] Motion controller support - [ ] Blueprint events - [ ] Hardware providers ### Documentation - [ ] Integration guide - [ ] API docs - [ ] Example projects - [ ] Video tutorial