PageSourceSearch

https://vrc-beta-docs.netlify.app/assets/js/2eccef89.dd34165a.js

js vrc-beta-docs.netlify.app collected 2026-10-03 11:36:10 UTC 16,975 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkVRChat_Creator_Docs=self.webpackChunkVRChat_Creator_Docs||[]).push([[462],{63380:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>d,default:()=>p,frontMatter:()=>l,metadata:()=>r,toc:()=>u});const r=JSON.parse('{"id":"platforms/android/android-best-practices","title":"Mobile Best Practices","description":"Making your VRChat worlds cross-platform is a great way of allowing more players to enjoy it. Most VRChat players are on Android, so it\u2019s worth creating an Android of your VRChat world.","source":"@site/docs/platforms/android/android-best-practices.md","sourceDirName":"platforms/android","slug":"/platforms/android/android-best-practices","permalink":"/platforms/android/android-best-practices","draft":false,"unlisted":false,"editUrl":"https://github.com/vrchat-community/creator-docs/edit/main/Docs/docs/platforms/android/android-best-practices.md","tags":[],"version":"current","lastUpdatedAt":1722624936000,"sidebarPosition":1,"frontMatter":{"sidebar_position":1},"sidebar":"tutorialSidebar","previous":{"title":"Cross-Platform Setup","permalink":"/platforms/android/cross-platform-setup"},"next":{"title":"Android Content Limitations","permalink":"/platforms/android/quest-content-limitations"}}');var o=n(74848),a=n(28453),s=n(65537),i=n(79329);const l={sidebar_position:1},d="Mobile Best Practices",c={},u=[{value:"1. Publish Your VRChat World to Android",id:"1-publish-your-vrchat-world-to-android",level:2},{value:"2. Detect Mobile Players in Your World Automatically",id:"2-detect-mobile-players-in-your-world-automatically",level:2},{value:"3. Optimize Your World for Android",id:"3-optimize-your-world-for-android",level:2},{value:"4. Test Your World on Android",id:"4-test-your-world-on-android",level:2},{value:"5. Design Your User Interfaces for Touchscreen",id:"5-design-your-user-interfaces-for-touchscreen",level:2},{value:"In Conclusion: Give Players a Smooth User Experience",id:"in-conclusion-give-players-a-smooth-user-experience",level:2}];function h(e){const t={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",header:"header",hr:"hr",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"mobile-best-practices",children:"Mobile Best Practices"})}),"\n",(0,o.jsx)(t.p,{children:"Making your VRChat worlds cross-platform is a great way of allowing more players to enjoy it. Most VRChat players are on Android, so it\u2019s worth creating an Android of your VRChat world."}),"\n",(0,o.jsx)(t.p,{children:"However, mobile players and VR players will experience your world quite differently! In this guide, we\u2019ll explain a few ways to make your mobile VRChat world more comfortable and enjoyable."}),"\n",(0,o.jsx)(t.admonition,{type:"info",children:(0,o.jsxs)(t.p,{children:["VRChat is available on Android as an ",(0,o.jsx)(t.a,{href:"https://play.google.com/store/apps/details?id=com.vrchat.mobile.playstore",children:"open beta"}),"."]})}),"\n",(0,o.jsx)(t.h2,{id:"1-publish-your-vrchat-world-to-android",children:"1. Publish Your VRChat World to Android"}),"\n",(0,o.jsx)(t.p,{children:"To make your world accessible to more users, publish it on all platforms supported by VRChat."}),"\n",(0,o.jsx)(t.p,{children:"Any worlds uploaded to Android are available on Oculus Quest and Android mobile devices. If you\u2019ve previously published a Quest version of your world, it\u2019ll be available on phones and tablets as well!"}),"\n",(0,o.jsxs)(t.p,{children:["To learn how to upload your world to Android, please refer to our ",(0,o.jsx)(t.a,{href:"https://creators.vrchat.com/platforms/android/cross-platform-setup",children:"cross-platform setup documentation"}),"."]}),"\n",(0,o.jsxs)(t.admonition,{type:"note",children:[(0,o.jsx)(t.p,{children:"If you see the term \u2018Quest\u2019 in reference to the VRChat SDK, it generally applies to Android, too."}),(0,o.jsxs)(t.p,{children:["For example: Existing tools like ",(0,o.jsx)(t.a,{href:"https://vcc.docs.vrchat.com/vpm/curated-community-packages#easyquestswitch",children:"EasyQuestSwitch"})," are great for cross-platform development. A Meta Quest isn\u2019t required!"]})]}),"\n",(0,o.jsx)(t.h2,{id:"2-detect-mobile-players-in-your-world-automatically",children:"2. Detect Mobile Players in Your World Automatically"}),"\n",(0,o.jsx)(t.p,{children:"When an Android player joins your world, you may want to tweak certain aspects of it. Players on an Android mobile device won\u2019t have access to VR controllers, just like VR players won\u2019t have access to a touchscreen."}),"\n",(0,o.jsxs)(t.p,{children:["Use ",(0,o.jsx)(t.a,{href:"/worlds/udon/input-events/#oninputmethodchanged",children:"OnInputMethodChanged"}
1)," to detect whenever the player's input method has changed. For example:"]}),"\n",(0,o.jsxs)(s.A,{groupId:"udon-compiler-language",children:[(0,o.jsx)(i.A,{value:"graph",label:"Udon Graph",children:(0,o.jsx)(t.p,{children:(0,o.jsx)(t.img,{alt:"A screenshot of an Udon Graph. The OnInputMethodChanged event is used to branch the execution based on whether the inputMethod parameter is Touch.",src:n(83409).A+"",width:"1656",height:"467"})})}),(0,o.jsx)(i.A,{value:"cs",label:"UdonSharp",children:(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-cs",children:"public override void OnInputMethodChanged(VRCInputMethod inputMethod)  \n{  \n    if (inputMethod == VRCInputMethod.Touch)  \n    {  \n        // Run code for touch input  \n    }  \n    else  \n    {  \n        // Run code for non-touch input  \n    }  \n}\n"})})})]}),"\n",(0,o.jsx)(t.p,{children:"You can also use GetLastUsedInputMethod to detect the input method at any time. For example:"}),"\n",(0,o.jsxs)(s.A,{groupId:"udon-compiler-language",children:[(0,o.jsx)(i.A,{value:"graph",label:"Udon Graph",children:(0,o.jsx)(t.p,{children:(0,o.jsx)(t.img,{alt:"A screenshot of an Udon Graph. GetLastUsedInputMethod is used to branch the execution based on whether the inputMethod parameter is Touch.",src:n(72485).A+"",width:"1656",height:"467"})})}),(0,o.jsx)(i.A,{value:"cs",label:"UdonSharp",children:(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-cs",children:"if (VRC.SDKBase.InputManager.GetLastUsedInputMethod() == VRCInputMethod.Touch)  \n{  \n    // Run code for touch input\n}  \nelse  \n{  \n    // Run code for non-touch input  \n}\n"})})})]}),"\n",(0,o.jsx)(t.h2,{id:"3-optimize-your-world-for-android",children:"3. Optimize Your World for Android"}),"\n",(0,o.jsxs)(t.p,{children:["Android devices are usually less powerful than PCs. Read our ",(0,o.jsx)(t.a,{href:"https://creators.vrchat.com/platforms/android/quest-content-optimization/",children:"Quest Content Optimization guide"})," to optimize your world\u2019s performance."]}),"\n",(0,o.jsx)(t.p,{children:"Good news: If your world runs OK on the Quest, it will probably run well on Android mobile devices. Phones and tablets usually have a lower resolution than VR headsets, and performance issues will cause less nausea than in VR."}),"\n",(0,o.jsx)(t.p,{children:"You should still ensure that Quest players have an acceptable frame rate."}),"\n",(0,o.jsx)(t.h2,{id:"4-test-your-world-on-android",children:"4. Test Your World on Android"}),"\n",(0,o.jsx)(t.p,{children:"Testing your world on an Android phone will help you improve the experience of mobile players. Even if your world is still in the early stages of development, consider how it will play on a mobile device."}),"\n",(0,o.jsx)(t.p,{children:"When rapidly iterating on VRChat worlds, it may be convenient to test them in the Unity Editor or the Steam version of VRChat. A joystick gamepad can be an appropriate substitute for VRChat\u2019s mobile control screen."}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["On a touchscreen, ",(0,o.jsx)(t.em,{children:"virtual"})," joysticks control the player\u2019s movement and camera."]}),"\n",(0,o.jsx)(t.li,{children:"Touchscreens make screen-space interfaces easier to interact with other input devices."}),"\n",(0,o.jsx)(t.li,{children:"Playtest your world with mobile players"}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"5-design-your-user-interfaces-for-touchscreen",children:"5. Design Your User Interfaces for Touchscreen"}),"\n",(0,o.jsx)(t.p,{children:"Phone screens are different from PC and VR devices. You may want to adjust your user interfaces to make them easier for mobile users to read and interact with."}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsx)(t.li,{children:"Make text easy to read. Use legible fonts, large text sizes, and big buttons."}),"\n",(0,o.jsx)(t.li,{children:"Remove unnecessary text. Reading large amounts of text is difficult on a mobile device."}),"\n",(0,o.jsx)(t.li,{children:"Localize text. Mobile users user a much wider spectrum of languages than on other platforms."}),"\n",(0,o.jsx)(t.li,{children:"Avoid relying on interactions that require a VR device or complex camera movements."}),"\n",(0,o.jsxs)(t.li,{children:["Try using ",(0,o.jsx)(t.a,{href:"https://docs.unity3d.com/Packages/[email protected]/manual/UICanvas.html",children:"screen-space user interfaces"}),". On a touchscreen, they are easier to interact with than world space canvase
1s. Consider adding a user interface that users can open from anywhere without needing to walk over to an in-world menu panel."]}),"\n",(0,o.jsxs)(t.li,{children:["Use the ",(0,o.jsx)(t.code,{children:"OnScreenUpdateEvent"})," to get the orientation and resolution of the player's screen. This will trigger once when they first load into the world, and whenever the orientation of their device changes."]}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"in-conclusion-give-players-a-smooth-user-experience",children:"In Conclusion: Give Players a Smooth User Experience"}),"\n",(0,o.jsx)(t.p,{children:"When a player joins your world, try to make the experience as smooth as possible. Try to apply the other tips mentioned in this article, and think of how you could use them in your world."}),"\n",(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Don't"})," reupload your PC world without optimizing your materials."]}),"\n"]}),"\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Do"})," optimize your world for users on Android and enhance their experience."]}),"\n"]}),"\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Don't"})," force players to follow complex steps before enjoying your world."]}),"\n"]}),"\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Do"})," make your world enjoyable automatically, without any user input."]}),"\n"]}),"\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Don't"})," upload your world without testing it."]}),"\n"]}),"\n",(0,o.jsxs)(t.li,{children:["\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"Do"})," listen to user feedback."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(t.hr,{}),"\n",(0,o.jsx)(t.p,{children:"Following these steps will help make your world an excellent experience for mobile players."}),"\n",(0,o.jsx)(t.p,{children:"Do you have any tips or tricks you\u2019d like us to include in this article? Submit your feedback below, and we\u2019ll do our best to help share your knowledge with the VRChat Community."})]})}function p(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},79329:(e,t,n)=>{n.d(t,{A:()=>s});n(96540);var r=n(18215);const o={tabItem:"tabItem_Ymn6"};var a=n(74848);function s(e){let{children:t,hidden:n,className:s}=e;return(0,a.jsx)("div",{role:"tabpanel",className:(0,r.A)(o.tabItem,s),hidden:n,children:t})}},65537:(e,t,n)=>{n.d(t,{A:()=>j});var r=n(96540),o=n(18215),a=n(65627),s=n(56347),i=n(50372),l=n(30604),d=n(11861),c=n(78749);function u(e){return r.Children.toArray(e).filter((e=>"\n"!==e)).map((e=>{if(!e||(0,r.isValidElement)(e)&&function(e){const{props:t}=e;return!!t&&"object"==typeof t&&"value"in t}(e))return e;throw new Error(`Docusaurus error: Bad <Tabs> child <${"string"==typeof e.type?e.type:e.type.name}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.`)}))?.filter(Boolean)??[]}function h(e){const{values:t,children:n}=e;return(0,r.useMemo)((()=>{const e=t??function(e){return u(e).map((e=>{let{props:{value:t,label:n,attributes:r,default:o}}=e;return{value:t,label:n,attributes:r,default:o}}))}(n);return function(e){const t=(0,d.XI)(e,((e,t)=>e.value===t.value));if(t.length>0)throw new Error(`Docusaurus error: Duplicate values "${t.map((e=>e.value)).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e}),[t,n])}function p(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function m(e){let{queryString:t=!1,groupId:n}=e;const o=(0,s.W6)(),a=function(e){let{queryString:t=!1,groupId:n}=e;if("string"==typeof t)return t;if(!1===t)return null;if(!0===t&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:t,groupId:n});return[(0,l.aZ)(a),(0,r.useCallback)((e=>{if(!a)return;const t=new URLSearchParams(o.location.search);t.set(a,e),o.replace({...o.location,search:t.toString()})}),[a,o])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:o}=e,a=h(e),[s,l]=(0,r.useState)((()=>function(e){let{defaultValue:t,tabValues:n}=e;if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(t){if(!p({value:t,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${t}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return t}const r=n.find((e=>e.default))??n[0];if(!r)throw new Error("Unexpected error: 0 tabValues");return r.value}({defaultValue:t,tabValues:a}))),[d,u]=m({queryString:n,groupId:o}),[f,y]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[o,a]=(0,c.Dv)(n);return[o,(0,r.useCallback)((e=>{n&&a.set(e)}),[n,a])]}({groupId:o}),b=(()=>{const e=d??f;return p({value:e,tabValues:a})?e:null})();(0,i.A)((()=>{b&&l(b)}),[b]);return{selectedValue:s,selectValue:(0,r.useCallback)((e=>{if(!p({value:e,tabValues:a}))throw new Error(`Can't select invalid tab value=${e}`);l(e),u(e),y(e)}),[u,y,a]),tabValues:a}}var y=n(9136);const b={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var x=n(74848);function v(e){let{className:t,block:n,selectedValue:r,selectValue:s,tabValues:i}=e;const l=[],{blockElementScrollPositionUntilNextRender:d}=(0,a.a_)(),c=e=>{const t=e.currentTarget,n=l.indexOf(t),o=i[n].value;
1o!==r&&(d(t),s(o))},u=e=>{let t=null;switch(e.key){case"Enter":c(e);break;case"ArrowRight":{const n=l.indexOf(e.currentTarget)+1;t=l[n]??l[0];break}case"ArrowLeft":{const n=l.indexOf(e.currentTarget)-1;t=l[n]??l[l.length-1];break}}t?.focus()};return(0,x.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,o.A)("tabs",{"tabs--block":n},t),children:i.map((e=>{let{value:t,label:n,attributes:a}=e;return(0,x.jsx)("li",{role:"tab",tabIndex:r===t?0:-1,"aria-selected":r===t,ref:e=>{l.push(e)},onKeyDown:u,onClick:c,...a,className:(0,o.A)("tabs__item",b.tabItem,a?.className,{"tabs__item--active":r===t}),children:n??t},t)}))})}function g(e){let{lazy:t,children:n,selectedValue:a}=e;const s=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=s.find((e=>e.props.value===a));return e?(0,r.cloneElement)(e,{className:(0,o.A)("margin-top--md",e.props.className)}):null}return(0,x.jsx)("div",{className:"margin-top--md",children:s.map(((e,t)=>(0,r.cloneElement)(e,{key:t,hidden:e.props.value!==a})))})}function w(e){const t=f(e);return(0,x.jsxs)("div",{className:(0,o.A)("tabs-container",b.tabList),children:[(0,x.jsx)(v,{...t,...e}),(0,x.jsx)(g,{...t,...e})]})}function j(e){const t=(0,y.A)();return(0,x.jsx)(w,{...e,children:u(e.children)},String(t))}},72485:(e,t,n)=>{n.d(t,{A:()=>r});const r=n.p+"assets/images/GetLastUsedInputMethod-7e13ee26703456903c6df10a16b01e22.png"},83409:(e,t,n)=>{n.d(t,{A:()=>r});const r=n.p+"assets/images/OnInputMethodChanged-a23b8096d1401d0e12b5771b4b054315.png"},28453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>i});var r=n(96540);const o={},a=r.createContext(o);function s(e){const t=r.useContext(a);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function i(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),r.createElement(a.Provider,{value:t},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.