1"use strict";(self.webpackChunkrapier=self.webpackChunkrapier||[]).push([[2472],{10690:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var o=n(74848),a=n(28453);n(11470),n(19365);const r={id:"common_recipes",title:"Common recipes",sidebar_label:"Common recipes"},i=void 0,s={id:"user_guides/bevy_plugin/common_recipes",title:"Common recipes",description:"This page provides code snippets that might be relevant for typical usages of the libraries in games.",source:"@site/docs/user_guides/bevy_plugin/common_recipes.mdx",sourceDirName:"user_guides/bevy_plugin",slug:"/user_guides/bevy_plugin/common_recipes",permalink:"/docs/user_guides/bevy_plugin/common_recipes",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{id:"common_recipes",title:"Common recipes",sidebar_label:"Common recipes"},sidebar:"user_guide_bevy",previous:{title:"Common mistakes",permalink:"/docs/user_guides/bevy_plugin/common_mistakes"},next:{title:"Multiple physics contexts",permalink:"/docs/user_guides/bevy_plugin/multiple_contexts"}},l={},c=[{value:"Making a moving platform",id:"making-a-moving-platform",level:2},{value:"Making a one-way platform",id:"making-a-one-way-platform",level:2},{value:"Simulating a conveyor belt",id:"simulating-a-conveyor-belt",level:2}];function u(e){const t={a:"a",admonition:"admonition",code:"code",h2:"h2",p:"p",pre:"pre",strong:"strong",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.p,{children:"This page provides code snippets that might be relevant for typical usages of the libraries in games."}),"\n",(0,o.jsx)(t.h2,{id:"making-a-moving-platform",children:"Making a moving platform"}),"\n",(0,o.jsxs)(t.p,{children:["A moving platform must push the objects resting on it without being pushed back by them, which is\nexactly what a ",(0,o.jsxs)(t.a,{href:"/docs/user_guides/bevy_plugin/rigid_bodies#rigid-body-type",children:[(0,o.jsx)(t.strong,{children:"kinematic"})," rigid-body"]})," does: it is moved by your own code, and\nthe solver treats it as if it is immune to gravity and external forces."]}),"\n",(0,o.jsx)(t.p,{children:"A platform following a path is generally position-based: you give it the position it must reach at the end of the next\ntimestep, and the engine derives the velocity needed to get there, so the objects on top of it are pushed with the right\nvelocity:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"fn setup_moving_platform(mut commands: Commands) {\n commands.spawn((\n RigidBody::KinematicPositionBased,\n Transform::from_xyz(0.0, 1.0, 5.0),\n Collider::cuboid(2.0, 0.1, 2.0),\n MovingPlatform,\n ));\n}\n\nfn move_platform(time: Res<Time>, mut platforms: Query<&mut Transform, With<MovingPlatform>>) {\n for mut transform in platforms.iter_mut() {\n // Setting the next position of the platform: the physics engine derives the\n // velocity needed to reach it at the end of the next timestep.\n transform.translation.x = time.elapsed_secs().sin() * 2.0;\n }\n}\n"})}),"\n",(0,o.jsxs)(t.p,{children:["Alternately, if the platform's rigid-body was created with the ",(0,o.jsx)(t.code,{children:"RigidBody::KinematicVelocityBased"})," type, then it\nneeds to be controlled by setting its velocity (with its ",(0,o.jsx)(t.code,{children:"Velocity"})," component) directly instead of a target position."]}),"\n",(0,o.jsx)(t.admonition,{type:"warning",children:(0,o.jsxs)(t.p,{children:["Modifying the ",(0,o.jsx)(t.code,{children:"Transform"})," of a\nposition-based kinematic body sets its next kinematic position. However, modifying the ",(0,o.jsx)(t.code,{children:"Transform"})," of a\nvelocity-based kinematic body teleports it, so it goes through whatever is in the way instead of pushing it. Note as well that two kinematic bodies never collide with each other, and that a\nkinematic body pushing another kinematic body has no effect."]})}),"\n",(0,o.jsx)(t.h2,{id:"making-a-one-way-platform",children:"Making a one-way platform"}),"\n",(0,o.jsxs)(t.p,{children:["A one-way platform lets the character pass through it from below and holds it from\nabove. This can be done by looking at the contacts before they reach the solver, with the\n",(0,o.jsx)(t.a,{href:"/docs/user_guides/bevy_plugin/advanced_collision_detection#contact-modification",children:"contact modification"})," hook, and by discarding those whose\nnormal isn't the one the platform accepts. Rapier provides a helper function ",(0,o.jsx)(t.code,{children:"update_as_oneway_platform"})," for that\n(",(0,o.jsx)(t.code,{children:"ContactModificationContextView::update_as_oneway_platform"}),"):"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"#[derive(Component)]\nstruct OneWayPlatform;\n\n#[derive(SystemParam)]\nstruct OneWayPlatformHooks<'w, 's> {\n platforms: Query<'w, 's, (), With<OneWayPlatform>>,\n}\n\nimpl BevyPhysicsHooks for OneWayPlatformHooks<'_, '_> {\n fn modify_solver_contacts(&self, mut context: ContactModificationContextView) {\n // Keep only the contacts pushing along the local +y axis of the platform; the other\n // ones (the character arriving from below) are discarded. The normal is expressed in\n // the frame of the first collider of the pair, hence the flip.\n let allowed_local_n1 = if self.platforms.contains(context.collider1()) {\n Vec3::Y\n } else if self.platforms.contains(context.collider2()) {\n -Vec3::Y\n } else {\n return;\n };\n context.update_as_oneway_platform(allowed_local_n1, 0.1);\n }\n}\n"})}),"\n",(0,o.jsxs)(t.p,{children:["The hooks are then given to the ",(0,o.jsx)(t.code,{children:"RapierPhysicsPlugin"})," as its type\nparameter, and the platform's collider is flagged as asking for them:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:".add_plugins((\n DefaultPlugins,\n // The hooks are given to the physics plugin.\n RapierPhysicsPlugin::<OneWayPlatformHooks>::default(),\n))\n"})}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"fn setup_one_way_platform(mut commands: Commands) {\n // The hooks are only called for the colliders asking for them.\n commands.spawn((\n Transform::from_xyz(0.0, 1.0, 0.0),\
1n Collider::cuboid(2.0, 0.1, 2.0),\n ActiveHooks::MODIFY_SOLVER_CONTACTS,\n OneWayPlatform,\n ));\n}\n"})}),"\n",(0,o.jsx)(t.admonition,{type:"info",children:(0,o.jsxs)(t.p,{children:["The normal given to ",(0,o.jsx)(t.code,{children:"update_as_oneway_platform"})," is expressed in the local frame of the ",(0,o.jsx)(t.strong,{children:"first"})," collider of the pair,\ntherefore it must be flipped when the platform happens to be the second one. Don't forget to give the platform's\ncollider the ",(0,o.jsx)(t.code,{children:"ActiveHooks::MODIFY_SOLVER_CONTACTS"})," ",(0,o.jsx)(t.a,{href:"/docs/user_guides/bevy_plugin/colliders#active-hooks",children:"active hooks"}),", otherwise the hook is\nnever called for it."]})}),"\n",(0,o.jsx)(t.h2,{id:"simulating-a-conveyor-belt",children:"Simulating a conveyor belt"}),"\n",(0,o.jsx)(t.p,{children:"A conveyor belt is a surface that drags what rests on it without moving itself. This is modeled by an artificial surface\nvelocity, which is set on the solver contacts using a contact modification hook."}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"#[derive(SystemParam)]\nstruct ConveyorBeltHooks<'w, 's> {\n belts: Query<'w, 's, (), With<ConveyorBelt>>,\n}\n\n#[derive(Component)]\nstruct ConveyorBelt;\n\nimpl BevyPhysicsHooks for ConveyorBeltHooks<'_, '_> {\n fn modify_solver_contacts(&self, mut context: ContactModificationContextView) {\n if !self.belts.contains(context.collider1()) && !self.belts.contains(context.collider2())\n {\n return;\n }\n if let Some(contacts) = context.solver_contacts_mut() {\n for contact in contacts.iter_mut() {\n // The belt drags the objects along the world-space z axis at 12 m/s.\n contact.tangent_velocity.z = 12.0;\n }\n }\n }\n}\n"})})]})}function d(e={}){const{wrapper:t}={...(0,a.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(u,{...e})}):u(e)}},19365:(e,t,n)=>{n.d(t,{A:()=>i});n(96540);var o=n(18215);const a={tabItem:"tabItem_Ymn6"};var r=n(74848);function i(e){let{children:t,hidden:n,className:i}=e;return(0,r.jsx)("div",{role:"tabpanel",className:(0,o.A)(a.tabItem,i),hidden:n,children:t})}},11470:(e,t,n)=>{n.d(t,{A:()=>w});var o=n(96540),a=n(18215),r=n(23104),i=n(56347),s=n(205),l=n(57485),c=n(31682),u=n(70679);function d(e){return o.Children.toArray(e).filter((e=>"\n"!==e)).map((e=>{if(!e||(0,o.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,o.useMemo)((()=>{const e=t??function(e){return d(e).map((e=>{let{props:{value:t,label:n,attributes:o,default:a}}=e;return{value:t,label:n,attributes:o,default:a}}))}(n);return function(e){const t=(0,c.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 m(e){let{value:t,tabValues:n}=e;return n.some((e=>e.value===t))}function p(e){let{queryString:t=!1,groupId:n}=e;const a=(0,i.W6)(),r=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)(r),(0,o.useCallback)((e=>{if(!r)return;const t=new URLSearchParams(a.location.search);t.set(r,e),a.replace({...a.location,search:t.toString()})}),[r,a])]}function f(e){const{defaultValue:t,queryString:n=!1,groupId:a}=e,r=h(e),[i,l]=(0,o.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(!m({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 o=n.find((e=>e.default))??n[0];if(!o)throw new Error("Unexpected error: 0 tabValues");return o.value}({defaultValue:t,tabValues:r}))),[c,d]=p({queryString:n,groupId:a}),[f,g]=function(e){let{groupId:t}=e;const n=function(e){return e?`docusaurus.tab.${e}`:null}(t),[a,r]=(0,u.Dv)(n);return[a,(0,o.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:a}),v=(()=>{const e=c??f;return m({value:e,tabValues:r})?e:null})();(0,s.A)((()=>{v&&l(v)}),[v]);return{selectedValue:i,selectValue:(0,o.useCallback)((e=>{if(!m({value:e,tabValues:r}))throw new Error(`Can't select invalid tab value=${e}`);l(e),d(e),g(e)}),[d,g,r]),tabValues:r}}var g=n(92303);const v={tabList:"tabList__CuJ",tabItem:"tabItem_LNqP"};var b=n(74848);function y(e){let{className:t,block:n,selectedValue:o,selectValue:i,tabValues:s}=e;const l=[],{blockElementScrollPositionUntilNextRender:c}=(0,r.a_)(),u=e=>{const t=e.currentTarget,n=l.indexOf(t),a=s[n].value;a!==o&&(c(t),i(a))},d=e=>{let t=null;switch(e.key){case"Enter":u(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,b.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.A)("tabs",{"tabs--block":n},t),children:s.map((e=>{let{value:t,label:n,attributes:r}=e;return(0,b.jsx)("li",{role:"tab",tabIndex:o===t?0:-1,"aria-selected":o===t,ref:e=>
1l.push(e),onKeyDown:d,onClick:u,...r,className:(0,a.A)("tabs__item",v.tabItem,r?.className,{"tabs__item--active":o===t}),children:n??t},t)}))})}function x(e){let{lazy:t,children:n,selectedValue:r}=e;const i=(Array.isArray(n)?n:[n]).filter(Boolean);if(t){const e=i.find((e=>e.props.value===r));return e?(0,o.cloneElement)(e,{className:(0,a.A)("margin-top--md",e.props.className)}):null}return(0,b.jsx)("div",{className:"margin-top--md",children:i.map(((e,t)=>(0,o.cloneElement)(e,{key:t,hidden:e.props.value!==r})))})}function _(e){const t=f(e);return(0,b.jsxs)("div",{className:(0,a.A)("tabs-container",v.tabList),children:[(0,b.jsx)(y,{...t,...e}),(0,b.jsx)(x,{...t,...e})]})}function w(e){const t=(0,g.A)();return(0,b.jsx)(_,{...e,children:d(e.children)},String(t))}},28453:(e,t,n)=>{n.d(t,{R:()=>i,x:()=>s});var o=n(96540);const a={},r=o.createContext(a);function i(e){const t=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function s(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),o.createElement(r.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.