The current Opposer VR experience on Roblox is an experimental first-person shooter known for its intense movement and physics-based combat.
Since "scripts" for this game are often sought for either gameplay enhancements (like infinite ammo or fly hacks) or server administration, you can use the following templates to share or request them on forums like Reddit or v3rmillion. 🚀 Showcase Post (Sharing a Script)
Title: [FE] Opposer VR Script – Silent Aim, Infinite Ammo & No RecoilBody:Hey guys, just finished updating this script for the latest Opposer VR patch. It's fully functional with Fluxus and Delta. Features: 🎯 Silent Aim: Hit targets without precise aiming. 🔫 Infinite Ammo: Never reload during intense firefights. 🚫 No Recoil: Keep your sights perfectly steady. 🏃 Speed Hack: Move faster than the default cap. How to use: Open your executor.
Copy the code from the Pastebin link (replace with actual link). Inject and execute while in-game. Use at your own risk; developers are active with bans! 🔍 Request Post (Looking for a Script)
Title: Looking for working Opposer VR script (April 2026)Body:Does anyone have a working script for Opposer VR? Most of the older ones on GitHub seem to be patched after the latest physics update. Specifically looking for: ESP: To see players through walls. Auto-Loot: For crates and vendors. No Spread: For better long-range accuracy.
If anyone has a private script or a reliable source, please drop it below! 🛠️ Developer Post (Coding Help)
Title: Help with Raycasting in Opposer VR ScriptBody:I'm trying to develop a custom ESP/Aimbot for Opposer VR using Luau. Since the game uses a custom physics engine for VR movement, I'm having trouble getting the WorldToViewportPoint to sync correctly with the VR camera.
The Issue:The box draws fine on desktop but offsets when the headset is active.
Has anyone worked with Roblox VR service scripts who can explain how to anchor the UI to the VR head position? 💡 Quick Tips:
Safety First: Never download .exe files; only use .lua or .txt scripts to avoid malware.
Compatibility: Most scripts require an executor like JJSploit, Fluxus, or Hydrogen.
Ban Risk: Opposer VR is an "experimental" game, but the developers do monitor for suspicious movement patterns.
To ensure your headset is properly configured before testing any scripts:
How to Play Roblox VR on Meta Quest 2 (Join the Roblox Quest Beta) Eric Woodie YouTube• Jul 27, 2023
If you have a specific script snippet you're working on, paste it here! I can help you debug the logic or optimize the performance.
using UnityEngine;
using UnityEngine.AI;
using System.Collections;
using Unity.XR.CoreUtils;
public class OpposerVR : MonoBehaviour
[Header("Movement Settings")]
[SerializeField] private float chaseSpeed = 3.5f;
[SerializeField] private float patrolSpeed = 1.5f;
[SerializeField] private float stoppingDistance = 2.0f;
[SerializeField] private float chaseRange = 10.0f;
[SerializeField] private float attackRange = 2.0f;
[SerializeField] private float patrolRadius = 15.0f;
[SerializeField] private float waitTimeAtPatrolPoint = 2.0f;
[Header("Combat Settings")]
[SerializeField] private int maxHealth = 100;
[SerializeField] private int attackDamage = 15;
[SerializeField] private float attackCooldown = 1.5f;
[SerializeField] private float attackAnimDuration = 0.5f;
[SerializeField] private GameObject projectilePrefab;
[SerializeField] private Transform projectileSpawnPoint;
[SerializeField] private bool isRangedAttacker = false;
[Header("Senses")]
[SerializeField] private float fieldOfView = 110f;
[SerializeField] private float hearingRadius = 12f;
[SerializeField] private LayerMask obstructionMask;
[SerializeField] private LayerMask playerLayer;
[Header("Behavior")]
[SerializeField] private bool useCover = true;
[SerializeField] private float coverCheckInterval = 1.5f;
[SerializeField] private float fleeHealthThreshold = 25f;
[Header("Audio")]
[SerializeField] private AudioClip[] attackSounds;
[SerializeField] private AudioClip[] hurtSounds;
[SerializeField] private AudioClip[] deathSounds;
[SerializeField] private AudioClip[] spottedSounds;
[SerializeField] private float audioVolume = 0.7f;
// Components
private NavMeshAgent agent;
private Animator animator;
private AudioSource audioSource;
private Transform player;
private XROrigin xrOrigin;
private int currentHealth;
private bool isDead = false;
private bool isAttacking = false;
private float nextAttackTime = 0f;
private float nextCoverCheck = 0f;
private Vector3 lastKnownPlayerPosition;
private bool playerSpotted = false;
private bool isInvestigating = false;
private Vector3 investigationPoint;
private Vector3 currentCoverPoint;
// Patrol variables
private Vector3 patrolTarget;
private bool isWaiting = false;
// States
private enum AIState Patrol, Chase, Attack, Investigate, Flee, TakeCover
private AIState currentState = AIState.Patrol;
void Start()
// Get components
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
audioSource = GetComponent<AudioSource>();
if (audioSource == null)
audioSource = gameObject.AddComponent<AudioSource>();
// Find player
xrOrigin = FindObjectOfType<XROrigin>();
if (xrOrigin != null)
player = xrOrigin.transform;
currentHealth = maxHealth;
agent.speed = patrolSpeed;
// Set initial patrol point
SetNewPatrolPoint();
// Start coroutines
StartCoroutine(SensePlayerRoutine());
if (useCover)
StartCoroutine(CoverCheckRoutine());
void Update()
if (isDead) return;
// State machine
switch (currentState)
case AIState.Patrol:
PatrolBehavior();
break;
case AIState.Chase:
ChaseBehavior();
break;
case AIState.Attack:
AttackBehavior();
break;
case AIState.Investigate:
InvestigateBehavior();
break;
case AIState.Flee:
FleeBehavior();
break;
case AIState.TakeCover:
TakeCoverBehavior();
break;
// Update animator
UpdateAnimations();
#region Behavior Methods
private void PatrolBehavior()
// Check if reached patrol point
if (!agent.pathPending && agent.remainingDistance < 0.5f && !isWaiting)
StartCoroutine(PatrolWait());
// Transition to chase if player spotted
if (playerSpotted && !isInvestigating)
SwitchState(AIState.Chase);
private void ChaseBehavior()
player == null)
SwitchState(AIState.Investigate);
return;
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
// Update last known position
lastKnownPlayerPosition = player.position;
// Chase the player
agent.speed = chaseSpeed;
agent.SetDestination(player.position);
// Check for attack range
if (distanceToPlayer <= attackRange && Time.time >= nextAttackTime)
SwitchState(AIState.Attack);
// Check if lost player
else if (distanceToPlayer > chaseRange)
playerSpotted = false;
SwitchState(AIState.Investigate);
// Check if health low and should flee
else if (currentHealth <= fleeHealthThreshold)
SwitchState(AIState.Flee);
// Check for cover
else if (useCover && Time.time >= nextCoverCheck && IsUnderFire())
SwitchState(AIState.TakeCover);
private void AttackBehavior()
if (isAttacking) return;
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
if (distanceToPlayer <= attackRange)
StartCoroutine(PerformAttack());
else
SwitchState(AIState.Chase);
private void InvestigateBehavior()
if (!isInvestigating)
isInvestigating = true;
investigationPoint = lastKnownPlayerPosition;
agent.SetDestination(investigationPoint);
agent.speed = patrolSpeed;
// Check if reached investigation point
if (!agent.pathPending && agent.remainingDistance < 0.5f)
StartCoroutine(FinishInvestigation());
// Re-check for player during investigation
if (playerSpotted)
isInvestigating = false;
SwitchState(AIState.Chase);
private void FleeBehavior()
// Find direction away from player
Vector3 fleeDirection = transform.position - player.position;
Vector3 fleePoint = transform.position + fleeDirection.normalized * 15f;
// Ensure flee point is within navmesh
NavMeshHit hit;
if (NavMesh.SamplePosition(fleePoint, out hit, 10f, NavMesh.AllAreas))
agent.SetDestination(hit.position);
agent.speed = chaseSpeed * 1.2f;
// Regain health or return to chase
if (currentHealth > fleeHealthThreshold + 15f && playerSpotted)
SwitchState(AIState.Chase);
else if (!playerSpotted)
SwitchState(AIState.Patrol);
private void TakeCoverBehavior()
if (currentCoverPoint == Vector3.zero
#endregion
#region Helper Methods
private IEnumerator SensePlayerRoutine()
while (!isDead)
if (player != null && !playerSpotted)
if (CanSeePlayer()
yield return new WaitForSeconds(0.2f);
private bool CanSeePlayer()
if (player == null) return false;
Vector3 directionToPlayer = (player.position - transform.position).normalized;
float angleToPlayer = Vector3.Angle(transform.forward, directionToPlayer);
if (angleToPlayer < fieldOfView / 2)
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
if (distanceToPlayer <= chaseRange)
// Raycast to check for obstacles
RaycastHit hit;
if (Physics.Raycast(transform.position + Vector3.up * 1f, directionToPlayer, out hit, chaseRange, obstructionMask))
return false;
private bool CanHearPlayer()
if (player == null) return false;
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
// Check if player is moving fast (running)
CharacterController controller = player.GetComponent<CharacterController>();
if (controller != null && controller.velocity.magnitude > 2f)
return distanceToPlayer < hearingRadius;
return false;
private bool IsUnderFire()
// Simple check - can be expanded with projectile hit detection
return currentHealth < maxHealth * 0.7f;
private void FindBestCover()
// Simple cover finding - looks for objects with "Cover" tag
GameObject[] coverObjects = GameObject.FindGameObjectsWithTag("Cover");
float bestScore = 0f;
Vector3 bestCover = Vector3.zero;
foreach (GameObject cover in coverObjects)
Vector3 coverPos = cover.transform.position;
float distanceToCover = Vector3.Distance(transform.position, coverPos);
float distanceFromPlayer = Vector3.Distance(player.position, coverPos);
// Score based on distance and protection
float score = (100f / distanceToCover) + distanceFromPlayer;
if (score > bestScore && IsCoverValid(coverPos))
bestScore = score;
bestCover = coverPos;
currentCoverPoint = bestCover;
private bool IsCoverValid(Vector3 coverPoint)
if (player == null) return false;
// Check if cover is between enemy and player
Vector3 directionToPlayer = player.position - transform.position;
Vector3 directionToCover = coverPoint - transform.position;
float angleDiff = Vector3.Angle(directionToPlayer, directionToCover);
return angleDiff < 45f;
private IEnumerator PerformAttack()
isAttacking = true;
nextAttackTime = Time.time + attackCooldown;
// Play attack animation
if (animator != null)
animator.SetTrigger("Attack");
PlaySound(attackSounds);
// Wait for attack animation
yield return new WaitForSeconds(attackAnimDuration * 0.5f);
// Deal damage
if (player != null && Vector3.Distance(transform.position, player.position) <= attackRange)
if (isRangedAttacker && projectilePrefab != null && projectileSpawnPoint != null)
GameObject projectile = Instantiate(projectilePrefab, projectileSpawnPoint.position, Quaternion.LookRotation(player.position - projectileSpawnPoint.position));
Projectile projectileScript = projectile.GetComponent<Projectile>();
if (projectileScript != null)
projectileScript.Initialize(attackDamage, player.gameObject);
else
// Melee damage
PlayerHealth playerHealth = player.GetComponent<PlayerHealth>();
if (playerHealth != null)
playerHealth.TakeDamage(attackDamage);
yield return new WaitForSeconds(attackAnimDuration * 0.5f);
isAttacking = false;
SwitchState(AIState.Chase);
private IEnumerator PatrolWait()
isWaiting = true;
agent.isStopped = true;
yield return new WaitForSeconds(waitTimeAtPatrolPoint);
agent.isStopped = false;
SetNewPatrolPoint();
isWaiting = false;
private IEnumerator FinishInvestigation()
yield return new WaitForSeconds(2f);
isInvestigating = false;
playerSpotted = false;
SwitchState(AIState.Patrol);
private IEnumerator WaitInCover()
agent.isStopped = true;
yield return new WaitForSeconds(3f);
agent.isStopped = false;
SwitchState(AIState.Chase);
private IEnumerator CoverCheckRoutine()
while (!isDead)
nextCoverCheck = Time.time + coverCheckInterval;
yield return new WaitForSeconds(coverCheckInterval);
private void SetNewPatrolPoint()
Vector3 randomDirection = Random.insideUnitSphere * patrolRadius;
randomDirection += transform.position;
NavMeshHit hit;
if (NavMesh.SamplePosition(randomDirection, out hit, patrolRadius, NavMesh.AllAreas))
patrolTarget = hit.position;
agent.SetDestination(patrolTarget);
private void SwitchState(AIState newState)
if (currentState == newState) return;
// Exit current state
switch (currentState)
case AIState.Attack:
StopCoroutine(PerformAttack());
isAttacking = false;
break;
currentState = newState;
// Enter new state
switch (currentState)
case AIState.Patrol:
agent.speed = patrolSpeed;
break;
case AIState.Flee:
if (animator != null)
animator.SetTrigger("Flee");
break;
private void UpdateAnimations()
if (animator == null) return;
float speed = agent.velocity.magnitude;
animator.SetFloat("Speed", speed);
animator.SetBool("IsDead", isDead);
private void PlaySound(AudioClip[] clips)
if (clips == null
#endregion
#region Public Methods
public void TakeDamage(int damage)
if (isDead) return;
currentHealth -= damage;
PlaySound(hurtSounds);
if (animator != null)
animator.SetTrigger("Hurt");
if (currentHealth <= 0)
Die();
else
// React to being hit
playerSpotted = true;
lastKnownPlayerPosition = player.position;
SwitchState(AIState.Chase);
private void Die()
isDead = true;
agent.isStopped = true;
PlaySound(deathSounds);
if (animator != null)
animator.SetTrigger("Die");
// Disable collider to prevent further interaction
Collider col = GetComponent<Collider>();
if (col != null)
col.enabled = false;
Destroy(gameObject, 5f);
#endregion
// Optional: Projectile script for ranged attackers
public class Projectile : MonoBehaviour
private int damage;
private GameObject ignoreTarget;
[SerializeField] private float speed = 20f;
[SerializeField] private float lifetime = 5f;
public void Initialize(int dmg, GameObject ignore)
damage = dmg;
ignoreTarget = ignore;
Destroy(gameObject, lifetime);
void Update()
transform.Translate(Vector3.forward * speed * Time.deltaTime);
void OnTriggerEnter(Collider other)
if (other.gameObject == ignoreTarget) return;
if (other.CompareTag("Player"))
PlayerHealth playerHealth = other.GetComponent<PlayerHealth>();
if (playerHealth != null)
playerHealth.TakeDamage(damage);
Destroy(gameObject);
High-level rhythm gamers often suffer from "muscle memory fatigue," where they memorize a song’s pattern rather than actually reading the notes. An Opposer Script randomizes or inverts behaviors, forcing the player to rely purely on reaction time and reflex. It is the ultimate training tool for improving raw skill.
The structure of such a script would depend heavily on the platform (e.g., Unity, Unreal Engine) and the programming languages used (e.g., C#, C++, Blueprints).
For example, in Unity with C#, a simple example of a mechanic could be: $$void opponentMovement()$$ $$$$ $$ // Define movement speed$$ $$ public float speed = 5.0f;$$ $$ // Reference to the opponent's transform$$ $$ private Transform opponent;$$ $$ // Update is called once per frame$$ $$ void Update()$$ $$ $$ $$ // Simple movement$$ $$ opponent.position += new Vector3(0, 0, speed * Time.deltaTime);$$ $$ $$ $$ $$
VR players get frustrated when enemies block forever. Your script needs a stamina/value system.
guardStress float. When it hits 100, the Opposer is forced into a "stumble" state, opening them up for a finisher.| Step | Action |
|------|--------|
| 1 | Add NavMeshSurface to your floor and bake. |
| 2 | Add NavMeshAgent to your opposer model. |
| 3 | Add Animator with parameters: Speed (float), InRange (bool), Attack (trigger), PlayerBlocking (bool). |
| 4 | Add VROpposer script. Assign references. |
| 5 | (Optional) Add collider + rigidbody (kinematic) for physical presence. |
| 6 | Create PlayerHealth script on XR Origin. |
| 7 | Test in VR — adjust attackRange, detectionRange, and attackCooldown. | opposer vr script
An Opposer without sound is a dummy. The script should listen to OnDamaged and OnBlock events to trigger spatialized voice lines (grunts, taunts, pain cries) that react to the intensity of the blow.
Opposer uses a simple, event-driven scripting language, which is based on a state-machine architecture. The language consists of three primary elements:
The perfect Opposer VR Script isn't about winning against the player; it's about creating a conversation. Every block is a "no." Every dodge is a "you missed." Every counter-attack is a "now it's my turn."
When you download or write your script, remember to tune the reactionTime variable carefully. Too fast (0 ms), and the enemy reads your mind—cheating. Too slow (500 ms), and it's a punching bag.
The sweet spot is 150ms. Just slightly slower than the human reflex arc. That way, when the Opposer blocks your swing, you know you weren't fast enough. And when you finally land that hit, the victory is earned.
Start opposing today. Your players are waiting to be challenged.
FAQ: Opposer VR Script
Q: Can I use a standard FPS enemy AI script as an Opposer? A: No. FPS AI expects hitscan bullets. VR Opposer scripts require velocity-based hit detection and 3D spatial awareness.
Q: Does this script require the player to have full body tracking? A: No. A standard VR Opposer script only needs the HMD (head) and the two controllers (hands). Leg tracking is cosmetic.
Q: Where can I find a ready-made template? A: Check the Unity Asset Store for "VR Melee Engine" or "HURST Combat." Alternatively, search GitHub for "VR Opposer AI."
The script for OPPOSER VR is a highly sought-after framework in the Roblox developer community, known for its leading-edge systems for weapon handling, movement, and physical body simulation. Script Highlights and Mechanics
Weapon Systems: Developers praise it as the "leading" system for reloading and shooting. The game features a vast arsenal, including secret and special weapons like RPGs and grenades.
Movement & Combat: Focuses on fast-paced movement, including mechanics like sliding and wall-running.
Physical Presence: The script creates a "body" consisting of a torso and two arms, a feature many developers aim to replicate for other physics-based or horror VR titles.
Controls: On Meta Quest, triggers are used for firing and grabbing, while thumbsticks handle movement, rotation, and menu access. Creating Your Own Story or Game
If you are looking to build a story using this script or a similar one, here is how you can approach it: The ULTIMATE Guide to Mastering OPPOSER VR
Title: "Revolutionizing Virtual Reality Interactions: A Deep Dive into Opposer VR Script"
Introduction
Virtual Reality (VR) has come a long way since its inception, transforming the way we interact with digital environments. One crucial aspect of enhancing VR experiences is the development of sophisticated scripts that enable more intuitive and immersive interactions. Among these, the Opposer VR script stands out for its innovative approach to handling user interactions within virtual spaces. In this blog post, we'll explore the Opposer VR script, its functionalities, and how it's changing the landscape of VR development. The current Opposer VR experience on Roblox is
What is Opposer VR Script?
The Opposer VR script is a specialized script designed for Unity, one of the most popular game engines used in VR development. This script is tailored to facilitate advanced opponent or NPC (Non-Player Character) interactions within VR environments. By integrating the Opposer VR script into their projects, developers can create more realistic and engaging experiences, allowing users to interact with virtual entities in a more lifelike manner.
Key Features of Opposer VR Script
Advanced Movement and Navigation: The Opposer VR script enables NPCs to navigate through virtual environments with greater realism. This includes avoiding obstacles, climbing stairs, and even responding to player movements in a more organic way.
Interactive Dialogue Systems: One of the standout features of the Opposer VR script is its ability to integrate complex dialogue systems. This allows for more engaging interactions between the player and NPCs, with conversations that can dynamically change based on player choices.
Emotional and Facial Expressions: The script supports the creation of detailed emotional and facial expressions for NPCs, adding a layer of realism to interactions. This can significantly enhance the emotional impact of scenes and make the virtual world feel more alive.
Seamless Integration with VR Environments: The Opposer VR script is designed to work seamlessly with VR environments, ensuring that interactions feel natural and intuitive. This includes precise control over how NPCs respond to player actions in VR, such as gestures or voice commands.
Benefits for VR Developers
The integration of the Opposer VR script into VR projects offers numerous benefits for developers:
Streamlined Development Process: By providing a robust framework for NPC interactions, the Opposer VR script can significantly reduce development time. Developers can focus on other aspects of their project, knowing that interactions with virtual characters are handled in a sophisticated and realistic manner.
Enhanced Player Engagement: The more realistic and immersive interactions enabled by the Opposer VR script can lead to higher player engagement. Players are more likely to become invested in a game or experience when the interactions feel natural and intuitive.
Cross-Platform Compatibility: The script's compatibility with Unity ensures that developers can deploy their VR experiences across various platforms, including but not limited to, Oculus, Vive, and Daydream.
Conclusion
The Opposer VR script represents a significant advancement in VR development, particularly in the realm of NPC interactions. By leveraging this script, developers can create more immersive, engaging, and realistic VR experiences. As VR continues to evolve and become more mainstream, tools like the Opposer VR script will play a crucial role in shaping the future of virtual reality interactions.
Whether you're a seasoned VR developer or just starting to explore the possibilities of virtual reality, the Opposer VR script is definitely worth considering for your next project. Its ability to enhance NPC interactions and contribute to a more immersive VR experience makes it a valuable tool in the VR development toolkit.
While there isn't a single "official" script that publically mirrors the entire Opposer VR
framework (as it is a proprietary, highly complex system within Roblox), you can build a similar experience using modular kits and community resources.
Here is a blog post draft that breaks down how you can start scripting your own VR combat system inspired by Opposer VR. How to Script a VR Combat System Like Opposer VR If you’ve played Opposer VR
on Roblox, you know it’s the gold standard for physics-based VR combat. From manual chambering to realistic recoil, it bridges the gap between standard Roblox play and high-end titles like The mechanic: For every successful block by the
But how do you actually script that level of immersion? Let’s dive into the core components you’ll need. 1. Choose Your Foundation
Don’t start from scratch. Most top-tier Roblox VR games use established character models that handle the heavy lifting of Inverse Kinematics (IK). Nexus VR Character Model:
This is the most popular open-source framework. It tracks your head and hands while procedurally generating a body that moves with you. VR Development Kits: Newer kits are constantly emerging in Roblox Studio
that simplify the connection between your VR headset and the game environment. 2. Scripting Realistic Weapon Interaction
The "secret sauce" of Opposer VR is the tactile nature of its weapons. To replicate this, your script needs to handle: Manual Chambering: Instead of a simple "R" to reload, use ProximityPrompts
or collision detection to require the player to physically pull back a slide. Physics-Based Aiming:
Avoid "laser-pointer" aiming. Your script should calculate projectile paths based on the barrel's orientation, which encourages players to actually look down the sights for accuracy. 3. Handling the Physics of "Weight"
In VR, if a heavy object moves too easily, it feels like a toy. Use BodyMovers AlignPosition AlignOrientation
) to create a slight delay or "weight" when a player swings a sword or lifts a large gun. This makes the interaction feel grounded in the world. 4. Common Pitfalls to Avoid
Even the best systems have bugs. Community feedback on games like Opposer VR often points out issues like: Height Scaling:
Ensure your script resets the player's floor level correctly upon respawn, or they won't be able to pick up items from the ground. PC vs. VR Balance:
If you allow cross-play, your scripts need to balance the speed of mouse-aiming against the immersion of VR. Where to Learn More
The Roblox VR development community is small but dedicated. If you're stuck on a specific piece of code, the Roblox Developer Forum
is your best resource for troubleshooting IK scripts and VR service limitations. specific code snippet
(like a reloading mechanic or IK rig) to get started on your project? How do i start with VR development? - Scripting Support
In traditional flat-screen gaming, an enemy is often just a target. In Virtual Reality, an enemy is a presence. When a 7-foot-tall brute rushes toward you in VR, your amygdala doesn't care about polygons—it screams "danger." That survival instinct is what makes VR combat so exhilarating.
But creating that visceral fear requires more than just a hostile NPC. It requires an Opposer VR Script.
The term "Opposer VR Script" refers to a specialized block of code (usually in C# for Unity or C++ for Unreal) that governs how a non-player character (NPC) challenges the player. Unlike a standard AI patrol script, an Opposer script is designed specifically for head-to-head mechanics: blocking, parrying, dodging, and counter-attacking in a 6-DoF (six degrees of freedom) space.
This article will break down the anatomy of an effective Opposer VR Script, how to troubleshoot common physics bugs, and why traditional AI fails when ported to VR.